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/examples/01-basic/01-minimal/package.json b/examples/01-basic/01-minimal/package.json
index bbf86f0736..c44135bd8f 100644
--- a/examples/01-basic/01-minimal/package.json
+++ b/examples/01-basic/01-minimal/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/01-basic/02-block-objects/package.json b/examples/01-basic/02-block-objects/package.json
index 39c9a0307b..7039e7673b 100644
--- a/examples/01-basic/02-block-objects/package.json
+++ b/examples/01-basic/02-block-objects/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/01-basic/03-multi-column/package.json b/examples/01-basic/03-multi-column/package.json
index e0326a2f50..6f47510954 100644
--- a/examples/01-basic/03-multi-column/package.json
+++ b/examples/01-basic/03-multi-column/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@blocknote/xl-multi-column": "latest"
diff --git a/examples/01-basic/04-default-blocks/package.json b/examples/01-basic/04-default-blocks/package.json
index 546687894e..07bd550965 100644
--- a/examples/01-basic/04-default-blocks/package.json
+++ b/examples/01-basic/04-default-blocks/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/01-basic/05-removing-default-blocks/package.json b/examples/01-basic/05-removing-default-blocks/package.json
index 192f80ec02..84bea27041 100644
--- a/examples/01-basic/05-removing-default-blocks/package.json
+++ b/examples/01-basic/05-removing-default-blocks/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/01-basic/06-block-manipulation/package.json b/examples/01-basic/06-block-manipulation/package.json
index 977f2d333d..aa5d3d3660 100644
--- a/examples/01-basic/06-block-manipulation/package.json
+++ b/examples/01-basic/06-block-manipulation/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/01-basic/07-selection-blocks/package.json b/examples/01-basic/07-selection-blocks/package.json
index b9721da77c..0ad8dcd672 100644
--- a/examples/01-basic/07-selection-blocks/package.json
+++ b/examples/01-basic/07-selection-blocks/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/01-basic/08-ariakit/package.json b/examples/01-basic/08-ariakit/package.json
index 1d246711f8..5b3b14ccbe 100644
--- a/examples/01-basic/08-ariakit/package.json
+++ b/examples/01-basic/08-ariakit/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/01-basic/09-shadcn/package.json b/examples/01-basic/09-shadcn/package.json
index 65e0647624..8d842252d1 100644
--- a/examples/01-basic/09-shadcn/package.json
+++ b/examples/01-basic/09-shadcn/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"tailwindcss": "^4.1.14",
diff --git a/examples/01-basic/10-localization/package.json b/examples/01-basic/10-localization/package.json
index 3df0f75296..839e85bc1a 100644
--- a/examples/01-basic/10-localization/package.json
+++ b/examples/01-basic/10-localization/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/01-basic/11-custom-placeholder/package.json b/examples/01-basic/11-custom-placeholder/package.json
index fe75d85dae..8eea656745 100644
--- a/examples/01-basic/11-custom-placeholder/package.json
+++ b/examples/01-basic/11-custom-placeholder/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/01-basic/12-multi-editor/package.json b/examples/01-basic/12-multi-editor/package.json
index 531ec98b2c..01d46a5515 100644
--- a/examples/01-basic/12-multi-editor/package.json
+++ b/examples/01-basic/12-multi-editor/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/01-basic/13-custom-paste-handler/package.json b/examples/01-basic/13-custom-paste-handler/package.json
index bfcea4ce3d..047bc04b60 100644
--- a/examples/01-basic/13-custom-paste-handler/package.json
+++ b/examples/01-basic/13-custom-paste-handler/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/01-basic/testing/package.json b/examples/01-basic/testing/package.json
index e9ee6710fa..1d7ad1ef14 100644
--- a/examples/01-basic/testing/package.json
+++ b/examples/01-basic/testing/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/02-backend/01-file-uploading/package.json b/examples/02-backend/01-file-uploading/package.json
index 4421054008..8fa3abe662 100644
--- a/examples/02-backend/01-file-uploading/package.json
+++ b/examples/02-backend/01-file-uploading/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/02-backend/02-saving-loading/package.json b/examples/02-backend/02-saving-loading/package.json
index b8af2de7f2..33c35e1db5 100644
--- a/examples/02-backend/02-saving-loading/package.json
+++ b/examples/02-backend/02-saving-loading/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/02-backend/03-s3/package.json b/examples/02-backend/03-s3/package.json
index 4e75860468..4f0aea247d 100644
--- a/examples/02-backend/03-s3/package.json
+++ b/examples/02-backend/03-s3/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@aws-sdk/client-s3": "^3.609.0",
diff --git a/examples/02-backend/04-rendering-static-documents/package.json b/examples/02-backend/04-rendering-static-documents/package.json
index ed960b24d2..bf5f3099d8 100644
--- a/examples/02-backend/04-rendering-static-documents/package.json
+++ b/examples/02-backend/04-rendering-static-documents/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@blocknote/server-util": "latest"
diff --git a/examples/03-ui-components/01-ui-elements-remove/package.json b/examples/03-ui-components/01-ui-elements-remove/package.json
index bc949a4c77..88cb38e4ba 100644
--- a/examples/03-ui-components/01-ui-elements-remove/package.json
+++ b/examples/03-ui-components/01-ui-elements-remove/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/03-ui-components/02-formatting-toolbar-buttons/package.json b/examples/03-ui-components/02-formatting-toolbar-buttons/package.json
index 359e17aa52..a799ee1920 100644
--- a/examples/03-ui-components/02-formatting-toolbar-buttons/package.json
+++ b/examples/03-ui-components/02-formatting-toolbar-buttons/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json b/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json
index 36ec799996..dd7baf25c5 100644
--- a/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json
+++ b/examples/03-ui-components/03-formatting-toolbar-block-type-items/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-icons": "^5.2.1"
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 4b2af03fbe..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,8 +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.
@@ -53,14 +54,18 @@ export const alertTypes = [
export const Alert = createReactBlockSpec(
{
type: "alert",
- propSchema: {
- textAlignment: defaultProps.textAlignment,
- textColor: defaultProps.textColor,
- type: {
- default: "warning",
- values: ["warning", "error", "info", "success"],
- },
- },
+ 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/04-side-menu-buttons/package.json b/examples/03-ui-components/04-side-menu-buttons/package.json
index 29ec731747..7ba5a3024e 100644
--- a/examples/03-ui-components/04-side-menu-buttons/package.json
+++ b/examples/03-ui-components/04-side-menu-buttons/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-icons": "^5.2.1"
diff --git a/examples/03-ui-components/05-side-menu-drag-handle-items/package.json b/examples/03-ui-components/05-side-menu-drag-handle-items/package.json
index 02659f1458..7f180069fb 100644
--- a/examples/03-ui-components/05-side-menu-drag-handle-items/package.json
+++ b/examples/03-ui-components/05-side-menu-drag-handle-items/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-icons": "^5.2.1"
diff --git a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json
index c8965682db..23bb23b5a0 100644
--- a/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json
+++ b/examples/03-ui-components/06-suggestion-menus-slash-menu-items/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-icons": "^5.2.1"
diff --git a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json
index 25b99a562d..5944d1fc55 100644
--- a/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json
+++ b/examples/03-ui-components/07-suggestion-menus-slash-menu-component/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json
index 777afaa140..487190c478 100644
--- a/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json
+++ b/examples/03-ui-components/08-suggestion-menus-emoji-picker-columns/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json
index 68e10b5905..59aba8278f 100644
--- a/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json
+++ b/examples/03-ui-components/09-suggestion-menus-emoji-picker-component/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json b/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json
index 5dcdb24ae9..6182529517 100644
--- a/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json
+++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
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 b19366ff65..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,14 +1,16 @@
+import { createPropSchemaFromZod } from "@blocknote/core";
import { createReactInlineContentSpec } from "@blocknote/react";
+import { z } from "zod/v4";
// The Mention inline content.
export const Mention = createReactInlineContentSpec(
{
type: "mention",
- propSchema: {
- user: {
- default: "Unknown",
- },
- },
+ propSchema: createPropSchemaFromZod(
+ z.object({
+ user: z.string().default("Unknown"),
+ }),
+ ),
content: "none",
},
{
diff --git a/examples/03-ui-components/11-uppy-file-panel/package.json b/examples/03-ui-components/11-uppy-file-panel/package.json
index 973ccae768..cb1dcd13d3 100644
--- a/examples/03-ui-components/11-uppy-file-panel/package.json
+++ b/examples/03-ui-components/11-uppy-file-panel/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@uppy/core": "^3.13.1",
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 e73723a9f9..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,8 +1,11 @@
import {
- BlockSchema,
+ baseFileZodPropSchema,
blockHasType,
+ BlockSchema,
InlineContentSchema,
+ optionalFileZodPropSchema,
StyleSchema,
+ createPropSchemaFromZod
} from "@blocknote/core";
import {
useBlockNoteEditor,
@@ -41,7 +44,15 @@ export const FileReplaceButton = () => {
if (
block === undefined ||
- !blockHasType(block, editor, "file", { url: "string" }) ||
+ !blockHasType(
+ block,
+ editor,
+ block.type,
+ // TODO
+ createPropSchemaFromZod(baseFileZodPropSchema.extend({
+ ...optionalFileZodPropSchema.pick({ url: true }).shape,
+ })),
+ ) ||
!editor.isEditable
) {
return null;
diff --git a/examples/03-ui-components/12-static-formatting-toolbar/package.json b/examples/03-ui-components/12-static-formatting-toolbar/package.json
index 582274d79b..0603faaa3c 100644
--- a/examples/03-ui-components/12-static-formatting-toolbar/package.json
+++ b/examples/03-ui-components/12-static-formatting-toolbar/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/03-ui-components/13-custom-ui/package.json b/examples/03-ui-components/13-custom-ui/package.json
index e94e0a9c37..7897783575 100644
--- a/examples/03-ui-components/13-custom-ui/package.json
+++ b/examples/03-ui-components/13-custom-ui/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@emotion/react": "^11.11.4",
diff --git a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/package.json b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/package.json
index 0caf0c2ab1..7711231ee2 100644
--- a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/package.json
+++ b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/03-ui-components/15-advanced-tables/package.json b/examples/03-ui-components/15-advanced-tables/package.json
index b058bf0b88..28c2e61e10 100644
--- a/examples/03-ui-components/15-advanced-tables/package.json
+++ b/examples/03-ui-components/15-advanced-tables/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/03-ui-components/16-link-toolbar-buttons/package.json b/examples/03-ui-components/16-link-toolbar-buttons/package.json
index dbc39e3c0b..c1954b7111 100644
--- a/examples/03-ui-components/16-link-toolbar-buttons/package.json
+++ b/examples/03-ui-components/16-link-toolbar-buttons/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/03-ui-components/17-advanced-tables-2/package.json b/examples/03-ui-components/17-advanced-tables-2/package.json
index 821d89d21b..96a6c18048 100644
--- a/examples/03-ui-components/17-advanced-tables-2/package.json
+++ b/examples/03-ui-components/17-advanced-tables-2/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/04-theming/01-theming-dom-attributes/package.json b/examples/04-theming/01-theming-dom-attributes/package.json
index ae80460c25..099e9a5be0 100644
--- a/examples/04-theming/01-theming-dom-attributes/package.json
+++ b/examples/04-theming/01-theming-dom-attributes/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/04-theming/02-changing-font/package.json b/examples/04-theming/02-changing-font/package.json
index 2712c03568..4d6c701aca 100644
--- a/examples/04-theming/02-changing-font/package.json
+++ b/examples/04-theming/02-changing-font/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/04-theming/03-theming-css/package.json b/examples/04-theming/03-theming-css/package.json
index 316e6e1660..b6e9e69e06 100644
--- a/examples/04-theming/03-theming-css/package.json
+++ b/examples/04-theming/03-theming-css/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/04-theming/04-theming-css-variables/package.json b/examples/04-theming/04-theming-css-variables/package.json
index 154970eb18..22c4b8c560 100644
--- a/examples/04-theming/04-theming-css-variables/package.json
+++ b/examples/04-theming/04-theming-css-variables/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/04-theming/05-theming-css-variables-code/package.json b/examples/04-theming/05-theming-css-variables-code/package.json
index 201a46de4f..a3f437083e 100644
--- a/examples/04-theming/05-theming-css-variables-code/package.json
+++ b/examples/04-theming/05-theming-css-variables-code/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/04-theming/06-code-block/package.json b/examples/04-theming/06-code-block/package.json
index 7aa6ac20bc..d6e52bbb8c 100644
--- a/examples/04-theming/06-code-block/package.json
+++ b/examples/04-theming/06-code-block/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@blocknote/code-block": "latest"
diff --git a/examples/04-theming/07-custom-code-block/package.json b/examples/04-theming/07-custom-code-block/package.json
index 61de00be1b..1007569457 100644
--- a/examples/04-theming/07-custom-code-block/package.json
+++ b/examples/04-theming/07-custom-code-block/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@blocknote/code-block": "latest",
diff --git a/examples/05-interoperability/01-converting-blocks-to-html/package.json b/examples/05-interoperability/01-converting-blocks-to-html/package.json
index 3338a38b05..2d83db1776 100644
--- a/examples/05-interoperability/01-converting-blocks-to-html/package.json
+++ b/examples/05-interoperability/01-converting-blocks-to-html/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/05-interoperability/02-converting-blocks-from-html/package.json b/examples/05-interoperability/02-converting-blocks-from-html/package.json
index 77f69540ce..59a8a16b39 100644
--- a/examples/05-interoperability/02-converting-blocks-from-html/package.json
+++ b/examples/05-interoperability/02-converting-blocks-from-html/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/05-interoperability/03-converting-blocks-to-md/package.json b/examples/05-interoperability/03-converting-blocks-to-md/package.json
index faa7202aa2..80556f5b3a 100644
--- a/examples/05-interoperability/03-converting-blocks-to-md/package.json
+++ b/examples/05-interoperability/03-converting-blocks-to-md/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/05-interoperability/04-converting-blocks-from-md/package.json b/examples/05-interoperability/04-converting-blocks-from-md/package.json
index 642727a0b3..cc15ad88d8 100644
--- a/examples/05-interoperability/04-converting-blocks-from-md/package.json
+++ b/examples/05-interoperability/04-converting-blocks-from-md/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/package.json b/examples/05-interoperability/05-converting-blocks-to-pdf/package.json
index bae781d726..783cf86511 100644
--- a/examples/05-interoperability/05-converting-blocks-to-pdf/package.json
+++ b/examples/05-interoperability/05-converting-blocks-to-pdf/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@blocknote/xl-pdf-exporter": "latest",
diff --git a/examples/05-interoperability/06-converting-blocks-to-docx/package.json b/examples/05-interoperability/06-converting-blocks-to-docx/package.json
index 542f835b4d..82b63fdab0 100644
--- a/examples/05-interoperability/06-converting-blocks-to-docx/package.json
+++ b/examples/05-interoperability/06-converting-blocks-to-docx/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@blocknote/xl-docx-exporter": "latest",
diff --git a/examples/05-interoperability/06-converting-blocks-to-docx/src/App.tsx b/examples/05-interoperability/06-converting-blocks-to-docx/src/App.tsx
index 9c91a99aff..1ac704f67a 100644
--- a/examples/05-interoperability/06-converting-blocks-to-docx/src/App.tsx
+++ b/examples/05-interoperability/06-converting-blocks-to-docx/src/App.tsx
@@ -32,7 +32,7 @@ export default function App() {
// Creates a new editor instance.
const editor = useCreateBlockNote({
// Adds support for page breaks & multi-column blocks.
- schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())),
+ schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())) as any,
dropCursor: multiColumnDropCursor,
dictionary: {
...locales.en,
@@ -397,7 +397,10 @@ export default function App() {
// Exports the editor content to DOCX and downloads it.
const onDownloadClick = async () => {
- const exporter = new DOCXExporter(editor.schema, docxDefaultSchemaMappings);
+ const exporter = new DOCXExporter(
+ editor.schema,
+ docxDefaultSchemaMappings as any,
+ );
const blob = await exporter.toBlob(editor.document);
const link = document.createElement("a");
diff --git a/examples/05-interoperability/07-converting-blocks-to-odt/package.json b/examples/05-interoperability/07-converting-blocks-to-odt/package.json
index dbafdd7aee..063e66c0b8 100644
--- a/examples/05-interoperability/07-converting-blocks-to-odt/package.json
+++ b/examples/05-interoperability/07-converting-blocks-to-odt/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@blocknote/xl-odt-exporter": "latest",
diff --git a/examples/05-interoperability/07-converting-blocks-to-odt/src/App.tsx b/examples/05-interoperability/07-converting-blocks-to-odt/src/App.tsx
index 9ece22d905..6d4361eb59 100644
--- a/examples/05-interoperability/07-converting-blocks-to-odt/src/App.tsx
+++ b/examples/05-interoperability/07-converting-blocks-to-odt/src/App.tsx
@@ -396,7 +396,10 @@ export default function App() {
// Exports the editor content to ODT and downloads it.
const onDownloadClick = async () => {
- const exporter = new ODTExporter(editor.schema, odtDefaultSchemaMappings);
+ const exporter = new ODTExporter(
+ editor.schema,
+ odtDefaultSchemaMappings as any,
+ );
const blob = await exporter.toODTDocument(editor.document);
const link = document.createElement("a");
diff --git a/examples/05-interoperability/08-converting-blocks-to-react-email/package.json b/examples/05-interoperability/08-converting-blocks-to-react-email/package.json
index 0e77d883f5..7e859a9ec6 100644
--- a/examples/05-interoperability/08-converting-blocks-to-react-email/package.json
+++ b/examples/05-interoperability/08-converting-blocks-to-react-email/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@blocknote/xl-email-exporter": "latest",
diff --git a/examples/06-custom-schema/01-alert-block/package.json b/examples/06-custom-schema/01-alert-block/package.json
index 67958db05b..9b02d18541 100644
--- a/examples/06-custom-schema/01-alert-block/package.json
+++ b/examples/06-custom-schema/01-alert-block/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-icons": "^5.2.1"
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 079cb8bebc..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,7 +1,8 @@
-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";
+import { z } from "zod/v4";
import "./styles.css";
@@ -53,14 +54,18 @@ export const alertTypes = [
export const createAlert = createReactBlockSpec(
{
type: "alert",
- propSchema: {
- textAlignment: defaultProps.textAlignment,
- textColor: defaultProps.textColor,
- type: {
- default: "warning",
- values: ["warning", "error", "info", "success"],
- },
- },
+ 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/package.json b/examples/06-custom-schema/02-suggestion-menus-mentions/package.json
index 7604218654..ad306997fd 100644
--- a/examples/06-custom-schema/02-suggestion-menus-mentions/package.json
+++ b/examples/06-custom-schema/02-suggestion-menus-mentions/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
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 b19366ff65..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,14 +1,16 @@
+import { createPropSchemaFromZod } from "@blocknote/core";
import { createReactInlineContentSpec } from "@blocknote/react";
+import { z } from "zod/v4";
// The Mention inline content.
export const Mention = createReactInlineContentSpec(
{
type: "mention",
- propSchema: {
- user: {
- default: "Unknown",
- },
- },
+ propSchema: createPropSchemaFromZod(
+ z.object({
+ user: z.string().default("Unknown"),
+ }),
+ ),
content: "none",
},
{
diff --git a/examples/06-custom-schema/03-font-style/package.json b/examples/06-custom-schema/03-font-style/package.json
index 88be55cfdc..d5a38dad6c 100644
--- a/examples/06-custom-schema/03-font-style/package.json
+++ b/examples/06-custom-schema/03-font-style/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-icons": "^5.2.1"
diff --git a/examples/06-custom-schema/04-pdf-file-block/package.json b/examples/06-custom-schema/04-pdf-file-block/package.json
index 32641f7b5b..588ce092df 100644
--- a/examples/06-custom-schema/04-pdf-file-block/package.json
+++ b/examples/06-custom-schema/04-pdf-file-block/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-icons": "^5.2.1"
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 f17619d8ba..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,9 +1,15 @@
-import { FileBlockConfig } from "@blocknote/core";
+import {
+ baseFileZodPropSchema,
+ createPropSchemaFromZod,
+ FileBlockConfig,
+ optionalFileZodPropSchema,
+} from "@blocknote/core";
import {
createReactBlockSpec,
ReactCustomBlockRenderProps,
ResizableFileBlockWrapper,
} from "@blocknote/react";
+import { z } from "zod/v4";
import { RiFilePdfFill } from "react-icons/ri";
@@ -33,24 +39,16 @@ export const PDFPreview = (
export const PDF = createReactBlockSpec(
{
type: "pdf",
- propSchema: {
- name: {
- default: "" as const,
- },
- url: {
- default: "" as const,
- },
- caption: {
- default: "" as const,
- },
- showPreview: {
- default: true,
- },
- previewWidth: {
- default: undefined,
- type: "number",
- },
- },
+ 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/package.json b/examples/06-custom-schema/05-alert-block-full-ux/package.json
index 3ae14b72a9..06546c5c98 100644
--- a/examples/06-custom-schema/05-alert-block-full-ux/package.json
+++ b/examples/06-custom-schema/05-alert-block-full-ux/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-icons": "^5.2.1"
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 079cb8bebc..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,7 +1,8 @@
-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";
+import { z } from "zod/v4";
import "./styles.css";
@@ -53,14 +54,18 @@ export const alertTypes = [
export const createAlert = createReactBlockSpec(
{
type: "alert",
- propSchema: {
- textAlignment: defaultProps.textAlignment,
- textColor: defaultProps.textColor,
- type: {
- default: "warning",
- values: ["warning", "error", "info", "success"],
- },
- },
+ 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/package.json b/examples/06-custom-schema/06-toggleable-blocks/package.json
index 39d91e1845..749d94496b 100644
--- a/examples/06-custom-schema/06-toggleable-blocks/package.json
+++ b/examples/06-custom-schema/06-toggleable-blocks/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
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 244661f841..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,
- },
+ propSchema: defaultPropSchema,
content: "inline",
},
{
diff --git a/examples/06-custom-schema/07-configuring-blocks/package.json b/examples/06-custom-schema/07-configuring-blocks/package.json
index 72ff5c631c..6a84292347 100644
--- a/examples/06-custom-schema/07-configuring-blocks/package.json
+++ b/examples/06-custom-schema/07-configuring-blocks/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/06-custom-schema/draggable-inline-content/package.json b/examples/06-custom-schema/draggable-inline-content/package.json
index def570b94f..28d8671212 100644
--- a/examples/06-custom-schema/draggable-inline-content/package.json
+++ b/examples/06-custom-schema/draggable-inline-content/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
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 03af1b7088..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,20 +1,25 @@
-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";
import {
createReactInlineContentSpec,
useCreateBlockNote,
} from "@blocknote/react";
-import { BlockNoteView } from "@blocknote/mantine";
-import "@blocknote/mantine/style.css";
+import { z } from "zod/v4";
const draggableButton = createReactInlineContentSpec(
{
type: "draggableButton",
- propSchema: {
- title: {
- default: "",
- },
- },
+ propSchema: createPropSchemaFromZod(
+ z.object({
+ title: z.string().default(""),
+ }),
+ ),
content: "none",
},
{
diff --git a/examples/06-custom-schema/react-custom-blocks/package.json b/examples/06-custom-schema/react-custom-blocks/package.json
index 980b1f4b4c..b44e75db5c 100644
--- a/examples/06-custom-schema/react-custom-blocks/package.json
+++ b/examples/06-custom-schema/react-custom-blocks/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
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 dd0573877d..17ae045fd6 100644
--- a/examples/06-custom-schema/react-custom-blocks/src/App.tsx
+++ b/examples/06-custom-schema/react-custom-blocks/src/App.tsx
@@ -1,12 +1,15 @@
import {
BlockNoteSchema,
+ createPropSchemaFromZod,
defaultBlockSpecs,
- defaultProps,
+ defaultPropSchema,
+ defaultZodPropSchema,
} from "@blocknote/core";
import "@blocknote/core/fonts/inter.css";
-import { createReactBlockSpec, useCreateBlockNote } from "@blocknote/react";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
+import { createReactBlockSpec, useCreateBlockNote } from "@blocknote/react";
+import { z } from "zod/v4";
import "./styles.css";
@@ -37,14 +40,18 @@ const alertTypes = {
export const alertBlock = createReactBlockSpec(
{
type: "alert",
- propSchema: {
- textAlignment: defaultProps.textAlignment,
- textColor: defaultProps.textColor,
- type: {
- default: "warning",
- values: ["warning", "error", "info", "success"],
- } as const,
- },
+ propSchema: createPropSchemaFromZod(
+ defaultZodPropSchema
+ .pick({
+ textAlignment: true,
+ textColor: true,
+ })
+ .extend({
+ type: z
+ .enum(["warning", "error", "info", "success"])
+ .default("warning"),
+ }),
+ ),
content: "inline",
},
{
@@ -76,15 +83,48 @@ export const alertBlock = createReactBlockSpec(
},
);
+// TODO
+export const advancedBlock = createReactBlockSpec(
+ {
+ type: "advanced",
+ propSchema: createPropSchemaFromZod(
+ z.object({
+ nested: z.object({
+ type: z.enum(["warning", "error", "info", "success"]),
+ message: z.string(),
+ }),
+ }),
+ ),
+ content: "inline",
+ },
+ {
+ render: (props) => (
+
+
{props.block.props.nested.message}
+
+
+ ),
+ },
+);
+
const simpleImageBlock = createReactBlockSpec(
{
type: "simpleImage",
- propSchema: {
- src: {
- 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",
},
{
@@ -102,9 +142,7 @@ export const bracketsParagraphBlock = createReactBlockSpec(
{
type: "bracketsParagraph",
content: "inline",
- propSchema: {
- ...defaultProps,
- },
+ propSchema: defaultPropSchema,
},
{
render: (props) => (
@@ -125,6 +163,7 @@ const schema = BlockNoteSchema.create({
alert: alertBlock(),
simpleImage: simpleImageBlock(),
bracketsParagraph: bracketsParagraphBlock(),
+ advanced: advancedBlock(),
},
});
@@ -132,6 +171,16 @@ export default function App() {
const editor = useCreateBlockNote({
schema,
initialContent: [
+ {
+ type: "advanced",
+ props: {
+ nested: {
+ type: "warning",
+ message: "Warning",
+ },
+ },
+ content: "Advanced",
+ },
{
type: "alert",
props: {
@@ -139,6 +188,7 @@ export default function App() {
},
content: "Alert",
},
+
{
type: "simpleImage",
props: {
diff --git a/examples/06-custom-schema/react-custom-inline-content/package.json b/examples/06-custom-schema/react-custom-inline-content/package.json
index 6679d0e794..575d94c56d 100644
--- a/examples/06-custom-schema/react-custom-inline-content/package.json
+++ b/examples/06-custom-schema/react-custom-inline-content/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
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 202252cf43..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,20 +1,25 @@
-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";
import {
createReactInlineContentSpec,
useCreateBlockNote,
} from "@blocknote/react";
-import { BlockNoteView } from "@blocknote/mantine";
-import "@blocknote/mantine/style.css";
+import { z } from "zod/v4";
const mention = createReactInlineContentSpec(
{
type: "mention",
- propSchema: {
- user: {
- default: "",
- },
- },
+ propSchema: createPropSchemaFromZod(
+ z.object({
+ user: z.string().default(""),
+ }),
+ ),
content: "none",
},
{
@@ -27,7 +32,7 @@ const mention = createReactInlineContentSpec(
const tag = createReactInlineContentSpec(
{
type: "tag",
- propSchema: {},
+ propSchema: createPropSchemaFromZod(z.object({})),
content: "styled",
},
{
diff --git a/examples/06-custom-schema/react-custom-styles/package.json b/examples/06-custom-schema/react-custom-styles/package.json
index d026bc292c..98b64c083e 100644
--- a/examples/06-custom-schema/react-custom-styles/package.json
+++ b/examples/06-custom-schema/react-custom-styles/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/07-collaboration/01-partykit/package.json b/examples/07-collaboration/01-partykit/package.json
index 68b38c8bbf..d47d21e635 100644
--- a/examples/07-collaboration/01-partykit/package.json
+++ b/examples/07-collaboration/01-partykit/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"y-partykit": "^0.0.25",
diff --git a/examples/07-collaboration/02-liveblocks/package.json b/examples/07-collaboration/02-liveblocks/package.json
index 1ceb9d9892..ee2c40a973 100644
--- a/examples/07-collaboration/02-liveblocks/package.json
+++ b/examples/07-collaboration/02-liveblocks/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@liveblocks/client": "3.7.1-tiptap3",
diff --git a/examples/07-collaboration/03-y-sweet/package.json b/examples/07-collaboration/03-y-sweet/package.json
index 10c3a15eed..ee4b213edf 100644
--- a/examples/07-collaboration/03-y-sweet/package.json
+++ b/examples/07-collaboration/03-y-sweet/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@y-sweet/react": "^0.6.3"
diff --git a/examples/07-collaboration/04-electric-sql/package.json b/examples/07-collaboration/04-electric-sql/package.json
index cf8f631184..8ead1c9b27 100644
--- a/examples/07-collaboration/04-electric-sql/package.json
+++ b/examples/07-collaboration/04-electric-sql/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/examples/07-collaboration/05-comments/package.json b/examples/07-collaboration/05-comments/package.json
index 5a4575bab1..c450aac661 100644
--- a/examples/07-collaboration/05-comments/package.json
+++ b/examples/07-collaboration/05-comments/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@y-sweet/react": "^0.6.3"
diff --git a/examples/07-collaboration/06-comments-with-sidebar/package.json b/examples/07-collaboration/06-comments-with-sidebar/package.json
index 7902a3cdc2..8a47a34616 100644
--- a/examples/07-collaboration/06-comments-with-sidebar/package.json
+++ b/examples/07-collaboration/06-comments-with-sidebar/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@y-sweet/react": "^0.6.3"
diff --git a/examples/07-collaboration/07-ghost-writer/package.json b/examples/07-collaboration/07-ghost-writer/package.json
index fb66b39740..79f3b40057 100644
--- a/examples/07-collaboration/07-ghost-writer/package.json
+++ b/examples/07-collaboration/07-ghost-writer/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"y-partykit": "^0.0.25",
diff --git a/examples/07-collaboration/08-forking/package.json b/examples/07-collaboration/08-forking/package.json
index ae0c5ecf12..133e12ce0f 100644
--- a/examples/07-collaboration/08-forking/package.json
+++ b/examples/07-collaboration/08-forking/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"y-partykit": "^0.0.25",
diff --git a/examples/08-extensions/01-tiptap-arrow-conversion/package.json b/examples/08-extensions/01-tiptap-arrow-conversion/package.json
index e7e4ce2470..29bf01680a 100644
--- a/examples/08-extensions/01-tiptap-arrow-conversion/package.json
+++ b/examples/08-extensions/01-tiptap-arrow-conversion/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@tiptap/core": "^3.7.2"
diff --git a/examples/09-ai/01-minimal/package.json b/examples/09-ai/01-minimal/package.json
index 5bab6bca1b..837de8e6c1 100644
--- a/examples/09-ai/01-minimal/package.json
+++ b/examples/09-ai/01-minimal/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@blocknote/xl-ai": "latest",
diff --git a/examples/09-ai/02-playground/package.json b/examples/09-ai/02-playground/package.json
index 7a2dfa0b45..31811d8dc7 100644
--- a/examples/09-ai/02-playground/package.json
+++ b/examples/09-ai/02-playground/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@blocknote/xl-ai": "latest",
diff --git a/examples/09-ai/03-custom-ai-menu-items/package.json b/examples/09-ai/03-custom-ai-menu-items/package.json
index b1d5273df3..9600dcf561 100644
--- a/examples/09-ai/03-custom-ai-menu-items/package.json
+++ b/examples/09-ai/03-custom-ai-menu-items/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@blocknote/xl-ai": "latest",
diff --git a/examples/09-ai/04-with-collaboration/package.json b/examples/09-ai/04-with-collaboration/package.json
index 38e48b84ff..0981513aed 100644
--- a/examples/09-ai/04-with-collaboration/package.json
+++ b/examples/09-ai/04-with-collaboration/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@blocknote/xl-ai": "latest",
diff --git a/examples/09-ai/05-manual-execution/package.json b/examples/09-ai/05-manual-execution/package.json
index c4d513fc84..2c6e4d37ff 100644
--- a/examples/09-ai/05-manual-execution/package.json
+++ b/examples/09-ai/05-manual-execution/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@blocknote/xl-ai": "latest",
diff --git a/examples/09-ai/06-client-side-transport/package.json b/examples/09-ai/06-client-side-transport/package.json
index 80d61fbf19..5d0c8f4b7c 100644
--- a/examples/09-ai/06-client-side-transport/package.json
+++ b/examples/09-ai/06-client-side-transport/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@ai-sdk/groq": "^2.0.16",
diff --git a/examples/09-ai/07-server-promptbuilder/package.json b/examples/09-ai/07-server-promptbuilder/package.json
index 32124ef536..21c1a94165 100644
--- a/examples/09-ai/07-server-promptbuilder/package.json
+++ b/examples/09-ai/07-server-promptbuilder/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@blocknote/xl-ai": "latest",
diff --git a/examples/vanilla-js/react-vanilla-custom-blocks/package.json b/examples/vanilla-js/react-vanilla-custom-blocks/package.json
index 8e714ad676..e8d4793533 100644
--- a/examples/vanilla-js/react-vanilla-custom-blocks/package.json
+++ b/examples/vanilla-js/react-vanilla-custom-blocks/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
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 3386861a88..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,13 +1,16 @@
import {
BlockNoteSchema,
createBlockSpec,
+ createPropSchemaFromZod,
defaultBlockSpecs,
- defaultProps,
+ defaultPropSchema,
+ defaultZodPropSchema,
} from "@blocknote/core";
import "@blocknote/core/fonts/inter.css";
-import { useCreateBlockNote } from "@blocknote/react";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
+import { useCreateBlockNote } from "@blocknote/react";
+import { z } from "zod/v4";
import "./styles.css";
@@ -38,14 +41,18 @@ const alertTypes = {
const alertBlock = createBlockSpec(
{
type: "alert",
- propSchema: {
- textAlignment: defaultProps.textAlignment,
- textColor: defaultProps.textColor,
- type: {
- default: "warning",
- values: ["warning", "error", "info", "success"] as const,
- },
- },
+ propSchema: createPropSchemaFromZod(
+ defaultZodPropSchema
+ .pick({
+ textAlignment: true,
+ textColor: true,
+ })
+ .extend({
+ type: z
+ .enum(["warning", "error", "info", "success"])
+ .default("warning"),
+ }),
+ ),
content: "inline",
},
{
@@ -113,12 +120,15 @@ const alertBlock = createBlockSpec(
const simpleImageBlock = createBlockSpec(
{
type: "simpleImage",
- propSchema: {
- src: {
- 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",
},
{
@@ -139,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/package.json b/examples/vanilla-js/react-vanilla-custom-inline-content/package.json
index 5190a3594c..d9c84668c9 100644
--- a/examples/vanilla-js/react-vanilla-custom-inline-content/package.json
+++ b/examples/vanilla-js/react-vanilla-custom-inline-content/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
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 867fa2f1a2..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,21 +1,23 @@
import {
BlockNoteSchema,
createInlineContentSpec,
+ createPropSchemaFromZod,
defaultInlineContentSpecs,
} from "@blocknote/core";
import "@blocknote/core/fonts/inter.css";
-import { useCreateBlockNote } from "@blocknote/react";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
+import { useCreateBlockNote } from "@blocknote/react";
+import { z } from "zod/v4";
const mention = createInlineContentSpec(
{
type: "mention",
- propSchema: {
- user: {
- default: "",
- },
- },
+ propSchema: createPropSchemaFromZod(
+ z.object({
+ user: z.string().default(""),
+ }),
+ ),
content: "none",
},
{
@@ -33,7 +35,7 @@ const mention = createInlineContentSpec(
const tag = createInlineContentSpec(
{
type: "tag",
- propSchema: {},
+ propSchema: createPropSchemaFromZod(z.object({})),
content: "styled",
},
{
diff --git a/examples/vanilla-js/react-vanilla-custom-styles/package.json b/examples/vanilla-js/react-vanilla-custom-styles/package.json
index 19f9554bbf..9c23f31fa0 100644
--- a/examples/vanilla-js/react-vanilla-custom-styles/package.json
+++ b/examples/vanilla-js/react-vanilla-custom-styles/package.json
@@ -19,6 +19,7 @@
"@mantine/core": "^8.3.4",
"@mantine/hooks": "^8.3.4",
"@mantine/utils": "^6.0.22",
+ "zod": "^4.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/package.json b/package.json
index 099c4b7e26..2463501ee8 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,8 @@
"serve": "14.2.4",
"typescript": "^5.9.3",
"vitest": "^2.1.9",
- "wait-on": "8.0.3"
+ "wait-on": "8.0.3",
+ "zod": "^4.0.0"
},
"pnpm": {
"ignoredBuiltDependencies": [
diff --git a/packages/core/package.json b/packages/core/package.json
index 2a74d1317c..4d0a44f162 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -117,7 +117,8 @@
"uuid": "^8.3.2",
"y-prosemirror": "^1.3.7",
"y-protocols": "^1.0.6",
- "yjs": "^13.6.27"
+ "yjs": "^13.6.27",
+ "zod": "^4.0.0"
},
"devDependencies": {
"@types/emoji-mart": "^3.0.14",
diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap
index dd575d1041..d0bb1203c3 100644
--- a/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap
+++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap
@@ -339,6 +339,7 @@ exports[`Test insertBlocks > Insert multiple blocks after 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -1014,6 +1015,7 @@ exports[`Test insertBlocks > Insert multiple blocks before 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -1609,6 +1611,7 @@ exports[`Test insertBlocks > Insert single basic block after 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -2216,6 +2219,7 @@ exports[`Test insertBlocks > Insert single basic block before (without type) 2`]
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -2811,6 +2815,7 @@ exports[`Test insertBlocks > Insert single basic block before 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -3522,6 +3527,7 @@ exports[`Test insertBlocks > Insert single complex block after 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -4233,6 +4239,7 @@ exports[`Test insertBlocks > Insert single complex block before 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts
index 81390947bf..cbd2a9a960 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,
+ partialBlockToBlock,
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,
@@ -27,7 +28,10 @@ export function insertBlocks<
typeof referenceBlock === "string" ? referenceBlock : referenceBlock.id;
const pmSchema = getPmSchema(tr);
const nodesToInsert = blocksToInsert.map((block) =>
- blockToNode(block, pmSchema),
+ blockToNode(
+ partialBlockToBlock(getBlockNoteSchema(pmSchema), block),
+ pmSchema,
+ ),
);
const posInfo = getNodeById(id, tr.doc);
diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap
index 690c00017e..66a2d11110 100644
--- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap
+++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap
@@ -215,6 +215,7 @@ exports[`Test mergeBlocks > Basic 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -766,6 +767,7 @@ exports[`Test mergeBlocks > Blocks have different types 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -1317,6 +1319,7 @@ exports[`Test mergeBlocks > First block has children 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -1867,6 +1870,7 @@ exports[`Test mergeBlocks > Second block has children 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -2435,6 +2439,7 @@ exports[`Test mergeBlocks > Second block is empty 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
diff --git a/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap
index 902463bbc1..0de9234962 100644
--- a/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap
+++ b/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap
@@ -232,6 +232,7 @@ exports[`Test moveBlocksDown > Basic 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -800,6 +801,7 @@ exports[`Test moveBlocksDown > Into children 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -1368,6 +1370,7 @@ exports[`Test moveBlocksDown > Last block 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -1936,6 +1939,7 @@ exports[`Test moveBlocksDown > Multiple blocks 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -2504,6 +2508,7 @@ exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`]
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -3072,6 +3077,7 @@ exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -3650,6 +3656,7 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -4218,6 +4225,7 @@ exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -4785,6 +4793,7 @@ exports[`Test moveBlocksDown > Multiple blocks starting in nested block 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -5353,6 +5362,7 @@ exports[`Test moveBlocksDown > Out of children 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -5920,6 +5930,7 @@ exports[`Test moveBlocksUp > Basic 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -6488,6 +6499,7 @@ exports[`Test moveBlocksUp > First block 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -7056,6 +7068,7 @@ exports[`Test moveBlocksUp > Into children 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -7624,6 +7637,7 @@ exports[`Test moveBlocksUp > Multiple blocks 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -8192,6 +8206,7 @@ exports[`Test moveBlocksUp > Multiple blocks ending in block with children 1`] =
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -8760,6 +8775,7 @@ exports[`Test moveBlocksUp > Multiple blocks ending in nested block 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -9310,6 +9326,7 @@ exports[`Test moveBlocksUp > Multiple blocks starting and ending in nested block
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -9895,6 +9912,7 @@ exports[`Test moveBlocksUp > Multiple blocks starting in block with children 1`]
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -10462,6 +10480,7 @@ exports[`Test moveBlocksUp > Multiple blocks starting in nested block 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -11030,6 +11049,7 @@ exports[`Test moveBlocksUp > Out of children 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
diff --git a/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts b/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts
index 8d4591123e..5d67179a00 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";
@@ -171,7 +170,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/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap
index d255acf235..2e7c7470c1 100644
--- a/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap
+++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap
@@ -145,6 +145,7 @@ exports[`Test replaceBlocks > Remove multiple consecutive blocks 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -696,6 +697,7 @@ exports[`Test replaceBlocks > Remove multiple non-consecutive blocks 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -993,6 +995,7 @@ exports[`Test replaceBlocks > Remove single block 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -1525,6 +1528,7 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with multiple
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -2017,6 +2021,7 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single ba
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -2567,6 +2572,7 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single co
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -3169,6 +3175,7 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with multi
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -3477,6 +3484,7 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with singl
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -3843,6 +3851,7 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with singl
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -4191,6 +4200,7 @@ exports[`Test replaceBlocks > Replace single block with multiple 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -4753,6 +4763,7 @@ exports[`Test replaceBlocks > Replace single block with single basic 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -5373,6 +5384,7 @@ exports[`Test replaceBlocks > Replace single block with single complex 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts
index 4ba8107636..00eec9070f 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 {
+ partialBlockToBlock,
+ 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(
+ partialBlockToBlock(getBlockNoteSchema(pmSchema), block),
+ pmSchema,
+ ),
);
const idsOfBlocksToRemove = new Set(
diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap
index 60c3d1c1ed..c3c611f64b 100644
--- a/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap
+++ b/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap
@@ -249,6 +249,7 @@ exports[`Test splitBlocks > Basic 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -834,6 +835,7 @@ exports[`Test splitBlocks > Block has children 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -1419,6 +1421,7 @@ exports[`Test splitBlocks > Don't keep props 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -2004,6 +2007,7 @@ exports[`Test splitBlocks > Don't keep type 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -2583,6 +2587,7 @@ exports[`Test splitBlocks > End of content 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -3170,6 +3175,7 @@ exports[`Test splitBlocks > Keep type 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap
index 3246168815..5a70f8600c 100644
--- a/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap
+++ b/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap
@@ -304,6 +304,7 @@ exports[`Test updateBlock > Revert all props 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -944,6 +945,7 @@ exports[`Test updateBlock > Revert single prop 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -1584,6 +1586,7 @@ exports[`Test updateBlock > Update all props 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -2224,6 +2227,7 @@ exports[`Test updateBlock > Update children 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -2569,6 +2573,7 @@ exports[`Test updateBlock > Update inline content to no content 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "",
@@ -2587,6 +2592,7 @@ exports[`Test updateBlock > Update inline content to no content 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "",
@@ -2806,6 +2812,7 @@ exports[`Test updateBlock > Update inline content to no content 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -3730,6 +3737,7 @@ exports[`Test updateBlock > Update inline content to table content 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -6978,6 +6986,7 @@ exports[`Test updateBlock > Update partial (offset start + end) 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -7546,6 +7555,7 @@ exports[`Test updateBlock > Update partial (offset start) 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -8107,6 +8117,7 @@ exports[`Test updateBlock > Update partial (props + offset end) 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -8668,6 +8679,7 @@ exports[`Test updateBlock > Update partial (table cell) 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -9236,6 +9248,7 @@ exports[`Test updateBlock > Update partial (table row) 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -9876,6 +9889,7 @@ exports[`Test updateBlock > Update single prop 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -10458,6 +10472,7 @@ exports[`Test updateBlock > Update table content to empty inline content 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -10872,6 +10887,7 @@ exports[`Test updateBlock > Update table content to inline content 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -11049,6 +11065,7 @@ exports[`Test updateBlock > Update table content to no content 1`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "",
@@ -11289,6 +11306,7 @@ exports[`Test updateBlock > Update table content to no content 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -11320,6 +11338,7 @@ exports[`Test updateBlock > Update table content to no content 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "",
@@ -11756,6 +11775,7 @@ exports[`Test updateBlock > Update type 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -12380,6 +12400,7 @@ exports[`Test updateBlock > Update with plain content 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
@@ -13006,6 +13027,7 @@ exports[`Test updateBlock > Update with styled content 2`] = `
"backgroundColor": "default",
"caption": "",
"name": "",
+ "previewWidth": undefined,
"showPreview": true,
"textAlignment": "left",
"url": "https://via.placeholder.com/150",
diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts
index 95ea4dc4e7..6b4f6e99b7 100644
--- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts
+++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts
@@ -50,6 +50,22 @@ describe("Test updateBlock typing", () => {
} catch (e) {
// ID doesn't exist, which is fine - this is a compile-time check
}
+
+ try {
+ // TODO: props.level type should propagate correctly
+ // @TODOts-expect-error level 8 doesn't exist
+ getEditor().updateBlock(
+ { id: "placeholder-id" },
+ {
+ type: "heading",
+ props: {
+ level: 8,
+ },
+ },
+ );
+ } catch (e) {
+ // ID doesn't exist, which is fine - this is a compile-time check
+ }
});
});
diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts
index 4318b19ca7..3991f2a2bb 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 {
+ partialBlockToBlock,
+ partialInlineContentToInlineContent,
+ partialTableContentToTableContent,
+} from "../../../../schema/partialBlockToBlock.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 = partialBlockToBlock(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(partialBlockToBlock(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..811288c85f 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),
@@ -682,7 +680,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 +742,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 +798,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 +833,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 +872,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/clipboard/fromClipboard/handleFileInsertion.ts b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts
index 76a52d6ac6..180d91b70d 100644
--- a/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts
+++ b/packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts
@@ -139,7 +139,7 @@ export async function handleFileInsertion<
props: {
name: file.name,
},
- } as PartialBlock;
+ } as unknown as PartialBlock;
let insertedBlockId: string | undefined = undefined;
@@ -187,7 +187,7 @@ export async function handleFileInsertion<
props: {
url: updateData,
},
- } as PartialBlock)
+ } as unknown as PartialBlock)
: { ...updateData };
editor.updateBlock(insertedBlockId, updatedFileBlock);
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 2149c884e7..fb6208d79a 100644
--- a/packages/core/src/api/exporters/html/externalHTMLExporter.ts
+++ b/packages/core/src/api/exporters/html/externalHTMLExporter.ts
@@ -1,12 +1,13 @@
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,
InlineContent,
InlineContentSchema,
StyleSchema,
+ TableContent,
} from "../../../schema/index.js";
import {
serializeBlocksExternalHTML,
@@ -40,7 +41,7 @@ export const createExternalHTMLExporter = <
return {
exportBlocks: (
- blocks: PartialBlock[],
+ blocks: Block[],
options: { document?: Document },
) => {
const html = serializeBlocksExternalHTML(
@@ -57,12 +58,12 @@ export const createExternalHTMLExporter = <
},
exportInlineContent: (
- inlineContent: InlineContent[],
+ inlineContent: InlineContent[] | TableContent,
options: { document?: Document },
) => {
const domFragment = serializeInlineContentExternalHTML(
editor,
- inlineContent as any,
+ inlineContent,
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 17ab49fe69..923dddc7d1 100644
--- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts
+++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts
@@ -1,12 +1,13 @@
import { DOMSerializer, Fragment, Node } from "prosemirror-model";
-
-import { PartialBlock } from "../../../../blocks/defaultBlocks.js";
+import { Block } from "../../../../blocks/index.js";
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,8 +35,8 @@ export function serializeInlineContentExternalHTML<
I extends InlineContentSchema,
S extends StyleSchema,
>(
- editor: BlockNoteEditor,
- blockContent: PartialBlock["content"],
+ editor: BlockNoteEditor,
+ blockContent: InlineContent[] | TableContent,
serializer: DOMSerializer,
options?: { document?: Document },
) {
@@ -44,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") {
@@ -166,7 +165,7 @@ function serializeBlock<
>(
fragment: DocumentFragment,
editor: BlockNoteEditor,
- block: PartialBlock,
+ block: Block,
serializer: DOMSerializer,
orderedListItemBlockTypes: Set,
unorderedListItemBlockTypes: Set,
@@ -175,20 +174,10 @@ function serializeBlock<
const doc = options?.document ?? document;
const BC_NODE = editor.pmSchema.nodes["blockContainer"];
- // set default props in case we were passed a partial block
- const props = block.props || {};
- for (const [name, spec] of Object.entries(
- editor.schema.blockSchema[block.type as any].propSchema,
- )) {
- if (!(name in props) && spec.default !== undefined) {
- (props as any)[name] = spec.default;
- }
- }
-
const bc = BC_NODE.spec?.toDOM?.(
BC_NODE.create({
id: block.id,
- ...props,
+ ...block.props,
}),
) as {
dom: HTMLElement;
@@ -204,14 +193,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();
@@ -240,11 +225,10 @@ function serializeBlock<
} else {
elementFragment.append(ret.dom);
}
-
if (ret.contentDOM && block.content) {
const ic = serializeInlineContentExternalHTML(
editor,
- block.content as any, // TODO
+ block.content,
serializer,
options,
);
@@ -265,11 +249,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);
}
@@ -319,7 +303,7 @@ const serializeBlocksToFragment = <
>(
fragment: DocumentFragment,
editor: BlockNoteEditor,
- blocks: PartialBlock[],
+ blocks: Block[],
serializer: DOMSerializer,
orderedListItemBlockTypes: Set,
unorderedListItemBlockTypes: Set,
@@ -344,7 +328,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 0f890b77ab..48f0dcf5b9 100644
--- a/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts
+++ b/packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts
@@ -1,11 +1,13 @@
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,
+ InlineContent,
InlineContentSchema,
StyleSchema,
+ TableContent,
} from "../../../../schema/index.js";
import { UnreachableCaseError } from "../../../../util/typescript.js";
import {
@@ -19,8 +21,8 @@ export function serializeInlineContentInternalHTML<
I extends InlineContentSchema,
S extends StyleSchema,
>(
- editor: BlockNoteEditor,
- blockContent: PartialBlock["content"],
+ editor: BlockNoteEditor,
+ 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") {
@@ -132,37 +132,26 @@ function serializeBlock<
S extends StyleSchema,
>(
editor: BlockNoteEditor,
- block: PartialBlock,
+ block: Block,
serializer: DOMSerializer,
options?: { document?: Document },
) {
const BC_NODE = editor.pmSchema.nodes["blockContainer"];
- // set default props in case we were passed a partial block
- const props = block.props || {};
- for (const [name, spec] of Object.entries(
- editor.schema.blockSchema[block.type as any].propSchema,
- )) {
- if (!(name in props) && spec.default !== undefined) {
- (props as any)[name] = spec.default;
- }
- }
- const children = block.children || [];
-
const impl = editor.blockImplementations[block.type as any].implementation;
const ret = impl.render.call(
{
renderType: "dom",
props: undefined,
},
- { ...block, props, children } as any,
+ block,
editor as any,
);
if (ret.contentDOM && block.content) {
const ic = serializeInlineContentInternalHTML(
editor,
- block.content as any, // TODO
+ block.content,
serializer,
block.type,
options,
@@ -190,7 +179,7 @@ function serializeBlock<
const bc = BC_NODE.spec?.toDOM?.(
BC_NODE.create({
id: block.id,
- ...props,
+ ...block.props,
}),
) as {
dom: HTMLElement;
@@ -213,7 +202,7 @@ function serializeBlocks<
S extends StyleSchema,
>(
editor: BlockNoteEditor,
- blocks: PartialBlock[],
+ blocks: Block[],
serializer: DOMSerializer,
options?: { document?: Document },
) {
@@ -234,7 +223,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/getBlocksChangedByTransaction.ts b/packages/core/src/api/getBlocksChangedByTransaction.ts
index c45af4cb71..de184ed5b4 100644
--- a/packages/core/src/api/getBlocksChangedByTransaction.ts
+++ b/packages/core/src/api/getBlocksChangedByTransaction.ts
@@ -171,7 +171,7 @@ function collectSnapshot<
if (!childrenByParent[key]) {
childrenByParent[key] = [];
}
- const block = nodeToBlock(node, pmSchema);
+ const block = nodeToBlock(node, pmSchema);
byId[node.attrs.id] = { block, parentId };
childrenByParent[key].push(node.attrs.id);
return true;
diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts
index 660d97406e..6aff41d076 100644
--- a/packages/core/src/api/nodeConversions/blockToNode.ts
+++ b/packages/core/src/api/nodeConversions/blockToNode.ts
@@ -1,22 +1,22 @@
import { Attrs, Fragment, Mark, Node, Schema } from "@tiptap/pm/model";
-import UniqueID from "../../extensions/UniqueID/UniqueID.js";
import type {
+ BlockSchema,
+ 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 +83,7 @@ function styledTextToNodes(
* prosemirror text nodes with the appropriate marks
*/
function linkToNodes(
- link: PartialLink,
+ link: Link,
schema: Schema,
styleSchema: StyleSchema,
): Node[] {
@@ -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,7 +145,8 @@ export function inlineContentToNodes<
I extends InlineContentSchema,
S extends StyleSchema,
>(
- blockContent: PartialInlineContent,
+ // this is lenient to "partial" inline content. FIXME: let's simplify and remove support for `string[]` here
+ blockContent: string[] | InlineContent[],
schema: Schema,
blockType?: string,
styleSchema: S = getStyleSchema(schema),
@@ -153,10 +155,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 +181,7 @@ export function tableContentToNodes<
I extends InlineContentSchema,
S extends StyleSchema,
>(
- tableContent: PartialTableContent,
+ tableContent: TableContent,
schema: Schema,
styleSchema: StyleSchema = getStyleSchema(schema),
): Node[] {
@@ -227,7 +230,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 +261,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,19 +276,12 @@ export function tableContentToNodes<
}
function blockOrInlineContentToContentNode(
- block:
- | PartialBlock
- | PartialCustomInlineContentFromConfig,
+ block: Block | CustomInlineContentFromConfig,
schema: Schema,
styleSchema: StyleSchema,
) {
+ const type = block.type;
let contentNode: Node;
- let type = block.type;
-
- // TODO: needed? came from previous code
- if (type === undefined) {
- type = "paragraph";
- }
if (!schema.nodes[type]) {
throw new Error(`node type ${type} not found in schema`);
@@ -321,17 +317,15 @@ function blockOrInlineContentToContentNode(
/**
* Converts a BlockNote block to a Prosemirror node.
*/
-export function blockToNode(
- block: PartialBlock,
+export function blockToNode<
+ BSchema extends BlockSchema,
+ I extends InlineContentSchema,
+ S extends StyleSchema,
+>(
+ block: Block,
schema: Schema,
- styleSchema: StyleSchema = getStyleSchema(schema),
+ styleSchema: S = getStyleSchema(schema),
) {
- let id = block.id;
-
- if (id === undefined) {
- id = UniqueID.options.generateID();
- }
-
const children: Node[] = [];
if (block.children) {
@@ -360,7 +354,7 @@ export function blockToNode(
return schema.nodes["blockContainer"].createChecked(
{
- id: id,
+ id: block.id,
...block.props,
},
groupNode ? [contentNode, groupNode] : contentNode,
@@ -369,7 +363,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/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts
index 1f5d2c75d4..907df635d4 100644
--- a/packages/core/src/api/nodeConversions/nodeToBlock.ts
+++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts
@@ -1,4 +1,5 @@
import { Mark, Node, Schema, Slice } from "@tiptap/pm/model";
+import * as z from "zod/v4/core";
import type { Block } from "../../blocks/defaultBlocks.js";
import UniqueID from "../../extensions/UniqueID/UniqueID.js";
import type {
@@ -25,7 +26,6 @@ import {
getInlineContentSchema,
getStyleSchema,
} from "../pmUtil.js";
-
/**
* Converts an internal (prosemirror) table node contentto a BlockNote Tablecontent
*/
@@ -355,7 +355,7 @@ export function nodeToCustomInlineContent<
throw Error("ic node is of an unrecognized type: " + node.type.name);
}
- const propSchema = icConfig.propSchema;
+ const propSchema = icConfig.propSchema._zodSource._zod.def.shape;
if (attr in propSchema) {
props[attr] = value;
@@ -424,20 +424,12 @@ export function nodeToBlock<
throw Error("Block is of an unrecognized type: " + blockInfo.blockNoteType);
}
- const props: any = {};
- for (const [attr, value] of Object.entries({
+ const rawAttrs = {
...node.attrs,
...(blockInfo.isBlockContainer ? blockInfo.blockContent.node.attrs : {}),
- })) {
- const propSchema = blockSpec.propSchema;
+ };
- if (
- attr in propSchema &&
- !(propSchema[attr].default === undefined && value === undefined)
- ) {
- props[attr] = value;
- }
- }
+ const props = z.parse(blockSpec.propSchema._zodSource, rawAttrs);
const blockConfig = blockSchema[blockInfo.blockNoteType];
diff --git a/packages/core/src/api/pmUtil.ts b/packages/core/src/api/pmUtil.ts
index e47a3f2a75..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 { BlockNoteSchema } from "../blocks/BlockNoteSchema.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";
@@ -25,12 +25,8 @@ export function getBlockNoteSchema<
BSchema extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema,
->(schema: Schema): BlockNoteSchema {
- return getBlockNoteEditor(schema).schema as unknown as BlockNoteSchema<
- BSchema,
- I,
- S
- >;
+>(schema: Schema): CustomBlockNoteSchema {
+ return getBlockNoteEditor(schema).schema as any;
}
export function getBlockSchema(
diff --git a/packages/core/src/blocks/Audio/block.ts b/packages/core/src/blocks/Audio/block.ts
index f271fcb16a..65e35d034a 100644
--- a/packages/core/src/blocks/Audio/block.ts
+++ b/packages/core/src/blocks/Audio/block.ts
@@ -1,10 +1,15 @@
-import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
+import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
import {
- BlockFromConfig,
+ type BlockFromConfig,
createBlockConfig,
createBlockSpec,
+ createPropSchemaFromZod,
} from "../../schema/index.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";
@@ -20,29 +25,23 @@ export interface AudioOptions {
export type AudioBlockConfig = ReturnType;
+const audioZodPropSchema = defaultZodPropSchema
+ .pick({
+ backgroundColor: true,
+ })
+ .extend({
+ ...baseFileZodPropSchema.shape,
+ ...optionalFileZodPropSchema.pick({
+ url: true,
+ showPreview: true,
+ }).shape,
+ });
+
export const createAudioBlockConfig = createBlockConfig(
(_ctx: AudioOptions) =>
({
type: "audio" as const,
- propSchema: {
- backgroundColor: defaultProps.backgroundColor,
- // File name.
- name: {
- default: "" as const,
- },
- // File url.
- url: {
- default: "" as const,
- },
- // File caption.
- caption: {
- default: "" as const,
- },
-
- showPreview: {
- default: true,
- },
- },
+ propSchema: createPropSchemaFromZod(audioZodPropSchema),
content: "none",
}) as const,
);
diff --git a/packages/core/src/blocks/BlockNoteSchema.ts b/packages/core/src/blocks/BlockNoteSchema.ts
index 37b60220da..3b2a29b11f 100644
--- a/packages/core/src/blocks/BlockNoteSchema.ts
+++ b/packages/core/src/blocks/BlockNoteSchema.ts
@@ -38,7 +38,7 @@ export class BlockNoteSchema<
* A list of custom Styles that should be available in the editor.
*/
styleSpecs?: SSpecs;
- }): BlockNoteSchema<
+ }): CustomBlockNoteSchema<
BSpecs extends undefined
? BlockSchemaFromSpecs
: BlockSchemaFromSpecs>,
@@ -49,7 +49,7 @@ export class BlockNoteSchema<
? StyleSchemaFromSpecs
: StyleSchemaFromSpecs>
> {
- return new BlockNoteSchema({
+ return new CustomBlockNoteSchema({
blockSpecs: options?.blockSpecs ?? defaultBlockSpecs,
inlineContentSpecs:
options?.inlineContentSpecs ?? defaultInlineContentSpecs,
diff --git a/packages/core/src/blocks/Code/block.ts b/packages/core/src/blocks/Code/block.ts
index 1458257e48..ca858e676a 100644
--- a/packages/core/src/blocks/Code/block.ts
+++ b/packages/core/src/blocks/Code/block.ts
@@ -1,8 +1,13 @@
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";
-import { DOMParser } from "@tiptap/pm/model";
export type CodeBlockOptions = {
/**
@@ -57,11 +62,11 @@ export const createCodeBlockConfig = createBlockConfig(
({ defaultLanguage = "text" }: CodeBlockOptions) =>
({
type: "codeBlock" as const,
- propSchema: {
- language: {
- 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 3de2211d3f..6443ac1164 100644
--- a/packages/core/src/blocks/Divider/block.ts
+++ b/packages/core/src/blocks/Divider/block.ts
@@ -1,5 +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;
@@ -7,7 +12,7 @@ export const createDividerBlockConfig = createBlockConfig(
() =>
({
type: "divider" as const,
- propSchema: {},
+ 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 a506cc45a3..d409e4995c 100644
--- a/packages/core/src/blocks/File/block.ts
+++ b/packages/core/src/blocks/File/block.ts
@@ -1,5 +1,13 @@
-import { createBlockConfig, createBlockSpec } from "../../schema/index.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";
@@ -7,25 +15,22 @@ import { createLinkWithCaption } from "./helpers/toExternalHTML/createLinkWithCa
export type FileBlockConfig = ReturnType;
+const fileZodPropSchema = defaultZodPropSchema
+ .pick({
+ backgroundColor: true,
+ })
+ .extend({
+ ...baseFileZodPropSchema.shape,
+ ...optionalFileZodPropSchema.pick({
+ url: true,
+ }).shape,
+ });
+
export const createFileBlockConfig = createBlockConfig(
() =>
({
type: "file" as const,
- propSchema: {
- backgroundColor: defaultProps.backgroundColor,
- // File name.
- name: {
- default: "" as const,
- },
- // File url.
- url: {
- default: "" as const,
- },
- // File caption.
- caption: {
- default: "" as const,
- },
- },
+ propSchema: createPropSchemaFromZod(fileZodPropSchema),
content: "none" as const,
}) as const,
);
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 cbb347acff..a97de01b81 100644
--- a/packages/core/src/blocks/File/helpers/render/createFileBlockWrapper.ts
+++ b/packages/core/src/blocks/File/helpers/render/createFileBlockWrapper.ts
@@ -1,22 +1,24 @@
import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js";
-import {
+import type {
BlockConfig,
- BlockFromConfigNoChildren,
+ BlockFromConfig,
+ PropSchemaFromZod,
} from "../../../../schema/index.js";
+import {
+ baseFileZodPropSchema,
+ optionalFileZodPropSchema,
+} from "../../../defaultFileProps.js";
import { createAddFileButton } from "./createAddFileButton.js";
import { createFileNameWithIcon } from "./createFileNameWithIcon.js";
+const requiredZodPropSchema = baseFileZodPropSchema.extend({
+ ...optionalFileZodPropSchema.pick({ url: true }).shape,
+});
export const createFileBlockWrapper = (
- block: BlockFromConfigNoChildren<
+ block: BlockFromConfig<
BlockConfig<
string,
- {
- backgroundColor: { default: "default" };
- name: { default: "" };
- url: { default: "" };
- caption: { default: "" };
- showPreview?: { default: true };
- },
+ PropSchemaFromZod,
"none"
>,
any,
@@ -58,7 +60,7 @@ export const createFileBlockWrapper = (
const ret: { dom: HTMLElement; destroy?: () => void } = { dom: wrapper };
// Show the file preview, or the file name and icon.
- if (block.props.showPreview === false || !element) {
+ if ((block.props as any).showPreview === false || !element) {
// Show file name and icon.
const fileNameWithIcon = createFileNameWithIcon(block);
wrapper.appendChild(fileNameWithIcon.dom);
diff --git a/packages/core/src/blocks/File/helpers/render/createFileNameWithIcon.ts b/packages/core/src/blocks/File/helpers/render/createFileNameWithIcon.ts
index 05ba0d6281..c9a4f8ff7c 100644
--- a/packages/core/src/blocks/File/helpers/render/createFileNameWithIcon.ts
+++ b/packages/core/src/blocks/File/helpers/render/createFileNameWithIcon.ts
@@ -1,17 +1,17 @@
import {
BlockConfig,
- BlockFromConfigNoChildren,
+ BlockFromConfig,
+ PropSchemaFromZod,
} from "../../../../schema/index.js";
+import { baseFileZodPropSchema } from "../../../defaultFileProps.js";
export const FILE_ICON_SVG = ``;
export const createFileNameWithIcon = (
- block: BlockFromConfigNoChildren<
+ block: BlockFromConfig<
BlockConfig<
string,
- {
- name: { default: "" };
- },
+ PropSchemaFromZod,
"none"
>,
any,
diff --git a/packages/core/src/blocks/File/helpers/render/createResizableFileBlockWrapper.ts b/packages/core/src/blocks/File/helpers/render/createResizableFileBlockWrapper.ts
index 0ee0a18394..4a16a03280 100644
--- a/packages/core/src/blocks/File/helpers/render/createResizableFileBlockWrapper.ts
+++ b/packages/core/src/blocks/File/helpers/render/createResizableFileBlockWrapper.ts
@@ -1,23 +1,28 @@
import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js";
import {
BlockConfig,
- BlockFromConfigNoChildren,
+ BlockFromConfig,
+ PropSchemaFromZod,
} from "../../../../schema/index.js";
+import {
+ baseFileZodPropSchema,
+ optionalFileZodPropSchema,
+} from "../../../defaultFileProps.js";
import { createFileBlockWrapper } from "./createFileBlockWrapper.js";
+const requiredZodPropSchema = baseFileZodPropSchema.extend({
+ ...optionalFileZodPropSchema.pick({
+ url: true,
+ previewWidth: true,
+ showPreview: true,
+ }).shape,
+});
+
export const createResizableFileBlockWrapper = (
- block: BlockFromConfigNoChildren<
+ block: BlockFromConfig<
BlockConfig<
string,
- {
- backgroundColor: { default: "default" };
- name: { default: "" };
- url: { default: "" };
- caption: { default: "" };
- showPreview?: { default: true };
- previewWidth?: { default: number };
- textAlignment?: { default: "left" };
- },
+ PropSchemaFromZod,
"none"
>,
any,
@@ -69,7 +74,7 @@ export const createResizableFileBlockWrapper = (
initialClientX: number;
}
| undefined;
- let width = block.props.previewWidth! as number;
+ let width = block.props.previewWidth;
// Updates the element width with an updated width depending on the cursor X
// offset from when the resize began, and which resize handle is being used.
@@ -92,7 +97,7 @@ export const createResizableFileBlockWrapper = (
const clientX =
"touches" in event ? event.touches[0].clientX : event.clientX;
- if (block.props.textAlignment === "center") {
+ if ((block.props as any).textAlignment === "center") {
if (resizeParams.handleUsed === "left") {
newWidth =
resizeParams.initialWidth +
diff --git a/packages/core/src/blocks/Heading/block.ts b/packages/core/src/blocks/Heading/block.ts
index 7181298d1f..4880f9fea0 100644
--- a/packages/core/src/blocks/Heading/block.ts
+++ b/packages/core/src/blocks/Heading/block.ts
@@ -1,8 +1,13 @@
-import { createBlockConfig, createBlockSpec } from "../../schema/index.js";
+import { z } from "zod/v4";
import { createBlockNoteExtension } from "../../editor/BlockNoteExtension.js";
+import {
+ createBlockConfig,
+ createBlockSpec,
+ createPropSchemaFromZod,
+} from "../../schema/index.js";
import {
addDefaultPropsExternalHTML,
- defaultProps,
+ defaultZodPropSchema,
parseDefaultProps,
} from "../defaultProps.js";
import { createToggleWrapper } from "../ToggleWrapper/createToggleWrapper.js";
@@ -26,13 +31,16 @@ export const createHeadingBlockConfig = createBlockConfig(
}: HeadingOptions = {}) =>
({
type: "heading" as const,
- propSchema: {
- ...defaultProps,
- level: { default: defaultLevel, values: levels },
- ...(allowToggleHeadings
- ? { isToggleable: { default: false, optional: true } as const }
- : {}),
- },
+ 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 83138c8842..c66968411d 100644
--- a/packages/core/src/blocks/Image/block.ts
+++ b/packages/core/src/blocks/Image/block.ts
@@ -3,8 +3,13 @@ import {
BlockFromConfig,
createBlockConfig,
createBlockSpec,
+ createPropSchemaFromZod,
} from "../../schema/index.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";
@@ -20,35 +25,25 @@ export interface ImageOptions {
export type ImageBlockConfig = ReturnType;
+const imageZodPropSchema = defaultZodPropSchema
+ .pick({
+ textAlignment: true,
+ backgroundColor: true,
+ })
+ .extend({
+ ...baseFileZodPropSchema.shape,
+ ...optionalFileZodPropSchema.pick({
+ url: true,
+ showPreview: true,
+ previewWidth: true,
+ }).shape,
+ });
+
export const createImageBlockConfig = createBlockConfig(
(_ctx: ImageOptions = {}) =>
({
type: "image" as const,
- propSchema: {
- textAlignment: defaultProps.textAlignment,
- backgroundColor: defaultProps.backgroundColor,
- // File name.
- name: {
- default: "" as const,
- },
- // File url.
- url: {
- default: "" as const,
- },
- // File caption.
- caption: {
- default: "" as const,
- },
-
- showPreview: {
- default: true,
- },
- // File preview width in px.
- previewWidth: {
- default: undefined,
- type: "number" as const,
- },
- },
+ 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 029538d4a3..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,9 +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 7887144b1b..c1d71a0697 100644
--- a/packages/core/src/blocks/ListItem/CheckListItem/block.ts
+++ b/packages/core/src/blocks/ListItem/CheckListItem/block.ts
@@ -1,8 +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";
@@ -16,10 +21,11 @@ export const createCheckListItemConfig = createBlockConfig(
() =>
({
type: "checkListItem" as const,
- propSchema: {
- ...defaultProps,
- checked: { default: false, type: "boolean" },
- },
+ 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 70f1bcaa90..bab950698f 100644
--- a/packages/core/src/blocks/ListItem/NumberedListItem/block.ts
+++ b/packages/core/src/blocks/ListItem/NumberedListItem/block.ts
@@ -1,9 +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";
@@ -18,10 +23,11 @@ export const createNumberedListItemBlockConfig = createBlockConfig(
() =>
({
type: "numberedListItem" as const,
- propSchema: {
- ...defaultProps,
- start: { default: undefined, type: "number" } as const,
- },
+ 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 e70da1b4b9..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,9 +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 49d7d2bd94..fe4c676ae1 100644
--- a/packages/core/src/blocks/PageBreak/block.ts
+++ b/packages/core/src/blocks/PageBreak/block.ts
@@ -1,11 +1,13 @@
+import { z } from "zod/v4";
import {
BlockSchema,
createBlockConfig,
createBlockSpec,
+ createPropSchemaFromZod,
+ CustomBlockNoteSchema,
InlineContentSchema,
StyleSchema,
} from "../../schema/index.js";
-import { BlockNoteSchema } from "../BlockNoteSchema.js";
export type PageBreakBlockConfig = ReturnType<
typeof createPageBreakBlockConfig
@@ -15,7 +17,7 @@ export const createPageBreakBlockConfig = createBlockConfig(
() =>
({
type: "pageBreak" as const,
- propSchema: {},
+ propSchema: createPropSchemaFromZod(z.object({})),
content: "none",
}) as const,
);
@@ -62,7 +64,7 @@ export const withPageBreak = <
I extends InlineContentSchema,
S extends StyleSchema,
>(
- schema: BlockNoteSchema,
+ schema: CustomBlockNoteSchema,
) => {
return schema.extend({
blockSpecs: {
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 a0f6d6cb4a..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: {
- backgroundColor: defaultProps.backgroundColor,
- textColor: defaultProps.textColor,
- },
+ 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 d101c5144f..4e314a1aac 100644
--- a/packages/core/src/blocks/Table/block.ts
+++ b/packages/core/src/blocks/Table/block.ts
@@ -1,4 +1,4 @@
-import { Node, mergeAttributes } from "@tiptap/core";
+import { mergeAttributes, Node } from "@tiptap/core";
import { DOMParser, Fragment, Node as PMNode, Schema } from "prosemirror-model";
import { CellSelection, TableView } from "prosemirror-tables";
import { NodeView } from "prosemirror-view";
@@ -6,16 +6,19 @@ 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 = {
- textColor: defaultProps.textColor,
-};
+export const tableZodPropSchema = defaultZodPropSchema.pick({
+ textColor: true,
+});
+
+const tablePropSchema = createPropSchemaFromZod(tableZodPropSchema);
const TiptapTableHeader = Node.create<{
HTMLAttributes: Record;
@@ -372,11 +375,7 @@ function parseTableContent(node: HTMLElement, schema: Schema) {
export type TableBlockConfig = BlockConfig<
"table",
- {
- textColor: {
- default: "default";
- };
- },
+ typeof tablePropSchema,
"table"
>;
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 026b333ba5..ae6f7164a1 100644
--- a/packages/core/src/blocks/Video/block.ts
+++ b/packages/core/src/blocks/Video/block.ts
@@ -1,5 +1,14 @@
-import { createBlockConfig, createBlockSpec } from "../../schema/index.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";
@@ -15,18 +24,24 @@ export interface VideoOptions {
export type VideoBlockConfig = ReturnType;
+const videoZodPropSchema = defaultZodPropSchema
+ .pick({
+ textAlignment: true,
+ backgroundColor: true,
+ })
+ .extend({
+ ...baseFileZodPropSchema.shape,
+ ...optionalFileZodPropSchema.pick({
+ url: true,
+ showPreview: true,
+ previewWidth: true,
+ }).shape,
+ });
+
export const createVideoBlockConfig = createBlockConfig(
(_ctx: VideoOptions) => ({
type: "video" as const,
- propSchema: {
- textAlignment: defaultProps.textAlignment,
- backgroundColor: defaultProps.backgroundColor,
- name: { default: "" as const },
- url: { default: "" as const },
- caption: { default: "" as const },
- showPreview: { default: true },
- previewWidth: { default: undefined, type: "number" as const },
- },
+ propSchema: createPropSchemaFromZod(videoZodPropSchema),
content: "none" as const,
}),
);
@@ -92,7 +107,9 @@ export const createVideoBlockSpec = createBlockSpec(
video.controls = true;
video.contentEditable = "false";
video.draggable = false;
- video.width = block.props.previewWidth;
+ if (block.props.previewWidth) {
+ video.width = block.props.previewWidth;
+ }
videoWrapper.appendChild(video);
return createResizableFileBlockWrapper(
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/defaultBlockTypeGuards.ts b/packages/core/src/blocks/defaultBlockTypeGuards.ts
index e9a4f8e9b5..c3ca03fdda 100644
--- a/packages/core/src/blocks/defaultBlockTypeGuards.ts
+++ b/packages/core/src/blocks/defaultBlockTypeGuards.ts
@@ -1,35 +1,20 @@
+import { Selection } from "prosemirror-state";
import { CellSelection } from "prosemirror-tables";
import type { BlockNoteEditor } from "../editor/BlockNoteEditor.js";
-import { BlockConfig, PropSchema, PropSpec } from "../schema/index.js";
+import { BlockConfig, PropSchema } from "../schema/index.js";
import { Block } from "./defaultBlocks.js";
-import { Selection } from "prosemirror-state";
+// TODO: matthew review + tests
export function editorHasBlockWithType<
BType extends string,
- Props extends
- | PropSchema
- | Record
- | undefined = undefined,
+ Props extends PropSchema,
>(
editor: BlockNoteEditor,
blockType: BType,
props?: Props,
): editor is BlockNoteEditor<
{
- [BT in BType]: Props extends PropSchema
- ? BlockConfig
- : Props extends Record
- ? BlockConfig<
- BT,
- {
- [PN in keyof Props]: {
- default: undefined;
- type: Props[PN];
- values?: any[];
- };
- }
- >
- : BlockConfig;
+ [BT in BType]: BlockConfig;
},
any,
any
@@ -42,115 +27,36 @@ export function editorHasBlockWithType<
return true;
}
- for (const [propName, propSpec] of Object.entries(props)) {
- if (!(propName in editor.schema.blockSpecs[blockType].config.propSchema)) {
- return false;
- }
-
- if (typeof propSpec === "string") {
- if (
- editor.schema.blockSpecs[blockType].config.propSchema[propName]
- .default !== undefined &&
- typeof editor.schema.blockSpecs[blockType].config.propSchema[propName]
- .default !== propSpec
- ) {
- return false;
- }
-
- if (
- editor.schema.blockSpecs[blockType].config.propSchema[propName].type !==
- undefined &&
- editor.schema.blockSpecs[blockType].config.propSchema[propName].type !==
- propSpec
- ) {
- return false;
- }
- } else {
- if (
- editor.schema.blockSpecs[blockType].config.propSchema[propName]
- .default !== propSpec.default
- ) {
- return false;
- }
-
- if (
- editor.schema.blockSpecs[blockType].config.propSchema[propName]
- .default === undefined &&
- propSpec.default === undefined
- ) {
- if (
- editor.schema.blockSpecs[blockType].config.propSchema[propName]
- .type !== propSpec.type
- ) {
- return false;
- }
- }
-
- if (
- typeof editor.schema.blockSpecs[blockType].config.propSchema[propName]
- .values !== typeof propSpec.values
- ) {
- return false;
- }
+ const editorProps: PropSchema =
+ editor.schema.blockSpecs[blockType].config.propSchema;
- if (
- typeof editor.schema.blockSpecs[blockType].config.propSchema[propName]
- .values === "object" &&
- typeof propSpec.values === "object"
- ) {
- for (const value of propSpec.values) {
- if (
- !editor.schema.blockSpecs[blockType].config.propSchema[
- propName
- ].values.includes(value)
- ) {
- return false;
- }
- }
- }
- }
- }
-
- return true;
+ // make sure every prop in the requested prop appears in the editor schema block props
+ 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<
- BType extends string,
- Props extends
- | PropSchema
- | Record
- | undefined = undefined,
->(
+export function blockHasType(
block: Block,
editor: BlockNoteEditor,
blockType: BType,
props?: Props,
): block is Block<
{
- [BT in BType]: Props extends PropSchema
- ? BlockConfig
- : Props extends Record
- ? BlockConfig<
- BT,
- {
- [PN in keyof Props]: PropSpec<
- Props[PN] extends "boolean"
- ? boolean
- : Props[PN] extends "number"
- ? number
- : Props[PN] extends "string"
- ? string
- : never
- >;
- }
- >
- : BlockConfig;
+ [BT in BType]: BlockConfig;
},
any,
any
> {
return (
- editorHasBlockWithType(editor, blockType, props) && block.type === blockType
+ block.type === blockType && editorHasBlockWithType(editor, blockType, props)
);
}
diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts
index 459b987f00..5d7453ff55 100644
--- a/packages/core/src/blocks/defaultBlocks.ts
+++ b/packages/core/src/blocks/defaultBlocks.ts
@@ -1,3 +1,4 @@
+import type { Mark } from "@tiptap/core";
import Bold from "@tiptap/extension-bold";
import Code from "@tiptap/extension-code";
import Italic from "@tiptap/extension-italic";
@@ -7,15 +8,15 @@ import { COLORS_DEFAULT } from "../editor/defaultColors.js";
import {
BlockNoDefaults,
BlockSchema,
+ createStyleSpec,
+ createStyleSpecFromTipTapMark,
+ getInlineContentSchemaFromSpecs,
+ getStyleSchemaFromSpecs,
InlineContentSchema,
InlineContentSpecs,
PartialBlockNoDefaults,
StyleSchema,
StyleSpecs,
- createStyleSpec,
- createStyleSpecFromTipTapMark,
- getInlineContentSchemaFromSpecs,
- getStyleSchemaFromSpecs,
} from "../schema/index.js";
import {
createAudioBlockSpec,
@@ -31,11 +32,12 @@ import {
createQuoteBlockSpec,
createToggleListItemBlockSpec,
createVideoBlockSpec,
- defaultProps,
+ defaultZodPropSchema,
} from "./index.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(),
bulletListItem: createBulletListItemBlockSpec(),
checkListItem: createCheckListItemBlockSpec(),
@@ -75,7 +77,10 @@ const TextColor = createStyleSpec(
},
toExternalHTML: (value) => {
const span = document.createElement("span");
- if (value !== defaultProps.textColor.default) {
+ // const defaultValue = defaultProps.parse({}).textColor;
+ const defaultValue =
+ defaultZodPropSchema.shape.textColor.def.defaultValue;
+ if (value !== defaultValue) {
span.style.color =
value in COLORS_DEFAULT ? COLORS_DEFAULT[value].text : value;
}
@@ -111,7 +116,11 @@ const BackgroundColor = createStyleSpec(
},
toExternalHTML: (value) => {
const span = document.createElement("span");
- if (value !== defaultProps.backgroundColor.default) {
+ // TODO
+ // const defaultValues = defaultProps.parse({});
+ const defaultValue =
+ defaultZodPropSchema.shape.backgroundColor.def.defaultValue;
+ if (value !== defaultValue) {
span.style.backgroundColor =
value in COLORS_DEFAULT ? COLORS_DEFAULT[value].background : value;
}
@@ -132,11 +141,26 @@ const BackgroundColor = createStyleSpec(
);
export const defaultStyleSpecs = {
- bold: createStyleSpecFromTipTapMark(Bold, "boolean"),
- italic: createStyleSpecFromTipTapMark(Italic, "boolean"),
- underline: createStyleSpecFromTipTapMark(Underline, "boolean"),
- strike: createStyleSpecFromTipTapMark(Strike, "boolean"),
- code: createStyleSpecFromTipTapMark(Code, "boolean"),
+ bold: createStyleSpecFromTipTapMark(
+ Bold as Mark & { name: "bold" },
+ "boolean",
+ ),
+ italic: createStyleSpecFromTipTapMark(
+ Italic as Mark & { name: "italic" },
+ "boolean",
+ ),
+ underline: createStyleSpecFromTipTapMark(
+ Underline as Mark & { name: "underline" },
+ "boolean",
+ ),
+ strike: createStyleSpecFromTipTapMark(
+ Strike as Mark & { name: "strike" },
+ "boolean",
+ ),
+ code: createStyleSpecFromTipTapMark(
+ Code as Mark & { name: "code" },
+ "boolean",
+ ),
textColor: TextColor,
backgroundColor: BackgroundColor,
} satisfies StyleSpecs;
diff --git a/packages/core/src/blocks/defaultFileProps.ts b/packages/core/src/blocks/defaultFileProps.ts
new file mode 100644
index 0000000000..596d6b5c51
--- /dev/null
+++ b/packages/core/src/blocks/defaultFileProps.ts
@@ -0,0 +1,18 @@
+import * as z from "zod/v4";
+
+export const baseFileZodPropSchema = z.object({
+ caption: z.string().default(""), // TODO: "" as defaults?
+ name: z.string().default(""),
+});
+
+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(""),
+ // Whether to show the file preview or the name only.
+ // This is useful for some file blocks, but not all
+ // (e.g.: not relevant for default "file" block which doesn;'t show previews)
+ showPreview: z.boolean().default(true),
+ // File preview width in px.
+ previewWidth: z.number().optional(),
+});
diff --git a/packages/core/src/blocks/defaultProps.ts b/packages/core/src/blocks/defaultProps.ts
index 5d55d21d35..384a5cc73d 100644
--- a/packages/core/src/blocks/defaultProps.ts
+++ b/packages/core/src/blocks/defaultProps.ts
@@ -1,28 +1,26 @@
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 = {
- backgroundColor: {
- default: "default" as const,
- },
- textColor: {
- default: "default" as const,
- },
- textAlignment: {
- default: "left" as const,
- values: ["left", "center", "right", "justify"] as const,
- },
-} satisfies PropSchema;
+export const defaultZodPropSchema = z.object({
+ backgroundColor: z.string().default("default"),
+ textColor: z.string().default("default"),
+ textAlignment: z.enum(["left", "center", "right", "justify"]).default("left"),
+});
+
+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.
@@ -42,22 +40,26 @@ export const parseDefaultProps = (element: HTMLElement) => {
props.textColor = element.style.color;
}
- props.textAlignment = defaultProps.textAlignment.values.includes(
- element.style.textAlign as DefaultProps["textAlignment"],
- )
- ? (element.style.textAlign as DefaultProps["textAlignment"])
+ props.textAlignment = defaultZodPropSchema.shape.textAlignment._zod.values
+ .values()
+ .some(
+ (value) =>
+ value ===
+ (element.style.textAlign as DefaultPropSchema["textAlignment"]),
+ )
+ ? (element.style.textAlign as DefaultPropSchema["textAlignment"])
: undefined;
return props;
};
export const addDefaultPropsExternalHTML = (
- props: Partial,
+ props: Partial,
element: HTMLElement,
) => {
if (
props.backgroundColor &&
- props.backgroundColor !== defaultProps.backgroundColor.default
+ props.backgroundColor !== defaultValues.backgroundColor
) {
// The color can be any string. If the string matches one of the default
// theme color names, set the theme color. Otherwise, set the color as-is
@@ -68,7 +70,7 @@ export const addDefaultPropsExternalHTML = (
: props.backgroundColor;
}
- if (props.textColor && props.textColor !== defaultProps.textColor.default) {
+ if (props.textColor && props.textColor !== defaultValues.textColor) {
// The color can be any string. If the string matches one of the default
// theme color names, set the theme color. Otherwise, set the color as-is
// (may be a CSS color name, hex value, RGB value, etc).
@@ -80,7 +82,7 @@ export const addDefaultPropsExternalHTML = (
if (
props.textAlignment &&
- props.textAlignment !== defaultProps.textAlignment.default
+ props.textAlignment !== defaultValues.textAlignment
) {
element.style.textAlign = props.textAlignment;
}
@@ -89,7 +91,7 @@ export const addDefaultPropsExternalHTML = (
export const getBackgroundColorAttribute = (
attributeName = "backgroundColor",
): Attribute => ({
- default: defaultProps.backgroundColor.default,
+ default: defaultValues.backgroundColor,
parseHTML: (element) => {
if (element.hasAttribute("data-background-color")) {
return element.getAttribute("data-background-color")!;
@@ -99,10 +101,10 @@ export const getBackgroundColorAttribute = (
return element.style.backgroundColor;
}
- return defaultProps.backgroundColor.default;
+ return defaultValues.backgroundColor;
},
renderHTML: (attributes) => {
- if (attributes[attributeName] === defaultProps.backgroundColor.default) {
+ if (attributes[attributeName] === defaultValues.backgroundColor) {
return {};
}
@@ -115,7 +117,7 @@ export const getBackgroundColorAttribute = (
export const getTextColorAttribute = (
attributeName = "textColor",
): Attribute => ({
- default: defaultProps.textColor.default,
+ default: defaultValues.textColor,
parseHTML: (element) => {
if (element.hasAttribute("data-text-color")) {
return element.getAttribute("data-text-color")!;
@@ -125,10 +127,10 @@ export const getTextColorAttribute = (
return element.style.color;
}
- return defaultProps.textColor.default;
+ return defaultValues.textColor;
},
renderHTML: (attributes) => {
- if (attributes[attributeName] === defaultProps.textColor.default) {
+ if (attributes[attributeName] === defaultValues.textColor) {
return {};
}
@@ -141,7 +143,7 @@ export const getTextColorAttribute = (
export const getTextAlignmentAttribute = (
attributeName = "textAlignment",
): Attribute => ({
- default: defaultProps.textAlignment.default,
+ default: defaultValues.textAlignment,
parseHTML: (element) => {
if (element.hasAttribute("data-text-alignment")) {
return element.getAttribute("data-text-alignment");
@@ -151,10 +153,10 @@ export const getTextAlignmentAttribute = (
return element.style.textAlign;
}
- return defaultProps.textAlignment.default;
+ return defaultValues.textAlignment;
},
renderHTML: (attributes) => {
- if (attributes[attributeName] === defaultProps.textAlignment.default) {
+ if (attributes[attributeName] === defaultValues.textAlignment) {
return {};
}
diff --git a/packages/core/src/blocks/index.ts b/packages/core/src/blocks/index.ts
index 56f4c6de3c..92911fae07 100644
--- a/packages/core/src/blocks/index.ts
+++ b/packages/core/src/blocks/index.ts
@@ -15,13 +15,14 @@ export * from "./Quote/block.js";
export * from "./Table/block.js";
export * from "./Video/block.js";
-export { EMPTY_CELL_HEIGHT, EMPTY_CELL_WIDTH } from "./Table/TableExtension.js";
-export * from "./ToggleWrapper/createToggleWrapper.js";
export * from "./File/helpers/uploadToTmpFilesDotOrg_DEV_ONLY.js";
export * from "./PageBreak/getPageBreakSlashMenuItems.js";
+export { EMPTY_CELL_HEIGHT, EMPTY_CELL_WIDTH } from "./Table/TableExtension.js";
+export * from "./ToggleWrapper/createToggleWrapper.js";
export * from "./BlockNoteSchema.js";
export * from "./defaultBlockHelpers.js";
export * from "./defaultBlocks.js";
export * from "./defaultBlockTypeGuards.js";
+export * from "./defaultFileProps.js";
export * from "./defaultProps.js";
diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts
index 9a5451763e..6c2880f7cd 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 {
+ partialBlockToBlock,
+ 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";
@@ -156,7 +157,7 @@ export type BlockNoteEditorOptions<
* @remarks `CommentsOptions`
*/
comments?: {
- schema?: BlockNoteSchema;
+ schema?: CustomBlockNoteSchema;
threadStore: ThreadStore;
};
@@ -447,7 +448,7 @@ export class BlockNoteEditor<
/**
* The schema of the editor. The schema defines which Blocks, InlineContent, and Styles are available in the editor.
*/
- public readonly schema: BlockNoteSchema;
+ public readonly schema: CustomBlockNoteSchema;
public readonly blockImplementations: BlockSpecs;
public readonly inlineContentImplementations: InlineContentSpecs;
@@ -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(
+ partialBlockToBlock(this.schema, b),
+ schema,
+ this.schema.styleSchema,
+ ).toJSON(),
);
const doc = createDocument(
{
@@ -1499,7 +1504,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);
}
@@ -1514,7 +1519,7 @@ export class BlockNoteEditor<
* @returns The blocks, serialized as an HTML string.
*/
public blocksToFullHTML(
- blocks: PartialBlock[] = this.document,
+ blocks: Block[] = this.document,
): string {
return this._exportManager.blocksToFullHTML(blocks);
}
@@ -1538,7 +1543,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/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts
index 754c90b428..b85746a814 100644
--- a/packages/core/src/editor/BlockNoteExtensions.ts
+++ b/packages/core/src/editor/BlockNoteExtensions.ts
@@ -49,6 +49,7 @@ import {
BlockNoteDOMAttributes,
BlockSchema,
BlockSpecs,
+ CustomBlockNoteSchema,
InlineContentSchema,
InlineContentSpecs,
StyleSchema,
@@ -59,7 +60,6 @@ import type {
BlockNoteEditorOptions,
SupportedExtension,
} from "./BlockNoteEditor.js";
-import { BlockNoteSchema } from "../blocks/BlockNoteSchema.js";
type ExtensionOptions<
BSchema extends BlockSchema,
@@ -94,7 +94,7 @@ type ExtensionOptions<
>;
tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
comments?: {
- schema?: BlockNoteSchema;
+ schema?: CustomBlockNoteSchema;
threadStore: ThreadStore;
resolveUsers?: (userIds: string[]) => Promise;
};
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 3fe1ee2f0e..2f943005bd 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,
@@ -54,7 +53,7 @@ export class ExportManager<
* @returns The blocks, serialized as an HTML string.
*/
public blocksToFullHTML(
- blocks: PartialBlock[] = this.editor.document,
+ blocks: Block[] = this.editor.document,
): string {
const exporter = createInternalHTMLSerializer(
this.editor.pmSchema,
@@ -83,7 +82,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/Exporter.ts b/packages/core/src/exporter/Exporter.ts
index f42e89e6f4..500d507ef9 100644
--- a/packages/core/src/exporter/Exporter.ts
+++ b/packages/core/src/exporter/Exporter.ts
@@ -1,8 +1,8 @@
-import { BlockNoteSchema } from "../blocks/BlockNoteSchema.js";
import { COLORS_DEFAULT } from "../editor/defaultColors.js";
import {
BlockFromConfig,
BlockSchema,
+ CustomBlockNoteSchema,
InlineContent,
InlineContentSchema,
StyleSchema,
@@ -45,7 +45,7 @@ export abstract class Exporter<
TS,
> {
public constructor(
- _schema: BlockNoteSchema, // only used for type inference
+ _schema: CustomBlockNoteSchema, // only used for type inference
protected readonly mappings: {
blockMapping: BlockMapping;
inlineContentMapping: InlineContentMapping;
diff --git a/packages/core/src/exporter/mapping.ts b/packages/core/src/exporter/mapping.ts
index 0dca63ebc3..1a39a0b302 100644
--- a/packages/core/src/exporter/mapping.ts
+++ b/packages/core/src/exporter/mapping.ts
@@ -1,7 +1,7 @@
-import { BlockNoteSchema } from "../blocks/BlockNoteSchema.js";
import {
- BlockFromConfigNoChildren,
+ BlockFromConfig,
BlockSchema,
+ CustomBlockNoteSchema,
InlineContentFromConfig,
InlineContentSchema,
StyleSchema,
@@ -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)
@@ -64,7 +64,7 @@ export function mappingFactory<
B extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema,
->(_schema: BlockNoteSchema) {
+>(_schema: CustomBlockNoteSchema) {
return {
createBlockMapping: (mapping: BlockMapping) =>
mapping,
diff --git a/packages/core/src/extensions/Collaboration/schemaMigration/migrationRules/moveColorAttributes.ts b/packages/core/src/extensions/Collaboration/schemaMigration/migrationRules/moveColorAttributes.ts
index 69dc3b5964..54f31d67e1 100644
--- a/packages/core/src/extensions/Collaboration/schemaMigration/migrationRules/moveColorAttributes.ts
+++ b/packages/core/src/extensions/Collaboration/schemaMigration/migrationRules/moveColorAttributes.ts
@@ -1,7 +1,7 @@
import * as Y from "yjs";
+import { defaultZodPropSchema } from "../../../../blocks/defaultProps.js";
import { MigrationRule } from "./migrationRule.js";
-import { defaultProps } from "../../../../blocks/defaultProps.js";
// Helper function to recursively traverse a `Y.XMLElement` and its descendant
// elements.
@@ -45,10 +45,12 @@ export const moveColorAttributes: MigrationRule = (fragment, tr) => {
backgroundColor: element.getAttribute("backgroundColor"),
};
- if (colors.textColor === defaultProps.textColor.default) {
+ // TODO: TBD best way to extract defaults
+ const defaultValues = defaultZodPropSchema.parse({});
+ if (colors.textColor === defaultValues.textColor) {
colors.textColor = undefined;
}
- if (colors.backgroundColor === defaultProps.backgroundColor.default) {
+ if (colors.backgroundColor === defaultValues.backgroundColor) {
colors.backgroundColor = undefined;
}
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/SideMenu/dragging.ts b/packages/core/src/extensions/SideMenu/dragging.ts
index 54dc3eeae2..d3a5779c7f 100644
--- a/packages/core/src/extensions/SideMenu/dragging.ts
+++ b/packages/core/src/extensions/SideMenu/dragging.ts
@@ -201,7 +201,7 @@ export function dragStart<
const externalHTMLExporter = createExternalHTMLExporter(schema, editor);
- const blocks = fragmentToBlocks(selectedSlice.content);
+ const blocks = fragmentToBlocks(selectedSlice.content);
const externalHTML = externalHTMLExporter.exportBlocks(blocks, {});
const plainText = cleanHTMLToMarkdown(externalHTML);
diff --git a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts
index 921d181d1a..df955b55e7 100644
--- a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts
+++ b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts
@@ -1,11 +1,14 @@
import { Block, PartialBlock } from "../../blocks/defaultBlocks.js";
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
+import { z } from "zod/v4";
import { editorHasBlockWithType } from "../../blocks/defaultBlockTypeGuards.js";
+import { optionalFileZodPropSchema } from "../../blocks/defaultFileProps.js";
import {
BlockSchema,
InlineContentSchema,
StyleSchema,
+ createPropSchemaFromZod,
isStyledTextInlineContent,
} from "../../schema/index.js";
import { formatKeyboardShortcut } from "../../util/browser.js";
@@ -87,44 +90,30 @@ export function getDefaultSlashMenuItems<
>(editor: BlockNoteEditor) {
const items: DefaultSuggestionItem[] = [];
- if (editorHasBlockWithType(editor, "heading", { level: "number" })) {
- items.push(
- {
- onItemClick: () => {
- insertOrUpdateBlock(editor, {
- type: "heading",
- props: { level: 1 },
- });
- },
- badge: formatKeyboardShortcut("Mod-Alt-1"),
- key: "heading",
- ...editor.dictionary.slash_menu.heading,
- },
- {
- onItemClick: () => {
- insertOrUpdateBlock(editor, {
- type: "heading",
- props: { level: 2 },
- });
- },
- badge: formatKeyboardShortcut("Mod-Alt-2"),
- key: "heading_2",
- ...editor.dictionary.slash_menu.heading_2,
- },
- {
- onItemClick: () => {
- insertOrUpdateBlock(editor, {
- type: "heading",
- props: { level: 3 },
- });
- },
- badge: formatKeyboardShortcut("Mod-Alt-3"),
- key: "heading_3",
- ...editor.dictionary.slash_menu.heading_3,
- },
- );
+ if (
+ editorHasBlockWithType(
+ editor,
+ "heading",
+ createPropSchemaFromZod(z.object({ level: z.number() })),
+ )
+ ) {
+ const headingProps = editor.schema.blockSchema.heading.propSchema;
+ for (const level of [1, 2, 3, 4, 5, 6] as const) {
+ if (z.safeParse(headingProps._zodSource, { level }).success) {
+ items.push({
+ onItemClick: () => {
+ insertOrUpdateBlock(editor, {
+ type: "heading",
+ props: { level },
+ });
+ },
+ badge: formatKeyboardShortcut("Mod-Alt-1"),
+ key: `heading_${level}`,
+ ...editor.dictionary.slash_menu[`heading_${level}`],
+ });
+ }
+ }
}
-
if (editorHasBlockWithType(editor, "quote")) {
items.push({
onItemClick: () => {
@@ -249,7 +238,14 @@ export function getDefaultSlashMenuItems<
});
}
- if (editorHasBlockWithType(editor, "image", { url: "string" })) {
+ if (
+ editorHasBlockWithType(
+ editor,
+ "image",
+ // TODO: review
+ createPropSchemaFromZod(optionalFileZodPropSchema.pick({ url: true })),
+ )
+ ) {
items.push({
onItemClick: () => {
const insertedBlock = insertOrUpdateBlock(editor, {
@@ -268,7 +264,13 @@ export function getDefaultSlashMenuItems<
});
}
- if (editorHasBlockWithType(editor, "video", { url: "string" })) {
+ if (
+ editorHasBlockWithType(
+ editor,
+ "video",
+ createPropSchemaFromZod(optionalFileZodPropSchema.pick({ url: true })),
+ )
+ ) {
items.push({
onItemClick: () => {
const insertedBlock = insertOrUpdateBlock(editor, {
@@ -287,7 +289,13 @@ export function getDefaultSlashMenuItems<
});
}
- if (editorHasBlockWithType(editor, "audio", { url: "string" })) {
+ if (
+ editorHasBlockWithType(
+ editor,
+ "audio",
+ createPropSchemaFromZod(optionalFileZodPropSchema.pick({ url: true })),
+ )
+ ) {
items.push({
onItemClick: () => {
const insertedBlock = insertOrUpdateBlock(editor, {
@@ -306,7 +314,13 @@ export function getDefaultSlashMenuItems<
});
}
- if (editorHasBlockWithType(editor, "file", { url: "string" })) {
+ if (
+ editorHasBlockWithType(
+ editor,
+ "file",
+ createPropSchemaFromZod(optionalFileZodPropSchema.pick({ url: true })),
+ )
+ ) {
items.push({
onItemClick: () => {
const insertedBlock = insertOrUpdateBlock(editor, {
@@ -326,23 +340,32 @@ export function getDefaultSlashMenuItems<
}
if (
- editorHasBlockWithType(editor, "heading", {
- level: "number",
- isToggleable: "boolean",
- })
+ editorHasBlockWithType(
+ editor,
+ "heading",
+ createPropSchemaFromZod(
+ z.object({
+ isToggleable: z.boolean().default(false),
+ level: z.number(), // TODO
+ }),
+ ),
+ )
) {
- items.push(
- {
+ const headingProps = editor.schema.blockSchema.heading.propSchema;
+ if (z.safeParse(headingProps._zodSource, { level: 1 }).success) {
+ items.push({
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "heading",
props: { level: 1, isToggleable: true },
});
},
- key: "toggle_heading",
- ...editor.dictionary.slash_menu.toggle_heading,
- },
- {
+ key: "toggle_heading_1",
+ ...editor.dictionary.slash_menu.toggle_heading_1,
+ });
+ }
+ if (z.safeParse(headingProps._zodSource, { level: 2 }).success) {
+ items.push({
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "heading",
@@ -352,8 +375,10 @@ export function getDefaultSlashMenuItems<
key: "toggle_heading_2",
...editor.dictionary.slash_menu.toggle_heading_2,
- },
- {
+ });
+ }
+ if (z.safeParse(headingProps._zodSource, { level: 3 }).success) {
+ items.push({
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "heading",
@@ -362,25 +387,8 @@ export function getDefaultSlashMenuItems<
},
key: "toggle_heading_3",
...editor.dictionary.slash_menu.toggle_heading_3,
- },
- );
- }
-
- if (editorHasBlockWithType(editor, "heading", { level: "number" })) {
- (editor.schema.blockSchema.heading.propSchema.level.values || [])
- .filter((level): level is 4 | 5 | 6 => level > 3)
- .forEach((level) => {
- items.push({
- onItemClick: () => {
- insertOrUpdateBlock(editor, {
- type: "heading",
- props: { level: level },
- });
- },
- key: `heading_${level}`,
- ...editor.dictionary.slash_menu[`heading_${level}`],
- });
});
+ }
}
items.push({
diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts
index f90fe9e95b..589eefcead 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,7 @@ export class TableHandlesView<
this.editor.schema.styleSchema,
);
- if (editorHasBlockWithType(this.editor, "table")) {
+ if (blockHasType(block, this.editor, "table")) {
this.tablePos = pmNodeInfo.posBeforeNode + 1;
tableBlock = block;
}
@@ -535,10 +539,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 +561,7 @@ export class TableHandlesView<
return;
}
+ this.state.block = block;
const { height: rowCount, width: colCount } = getDimensionsOfTable(
this.state.block,
);
@@ -626,7 +637,7 @@ export class TableHandlesProsemirrorPlugin<
constructor(
private readonly editor: BlockNoteEditor<
- BlockSchemaWithBlock<"table", DefaultBlockSchema["table"]>,
+ BlockSchemaWithBlock<"table", TableBlockConfig>,
I,
S
>,
@@ -921,7 +932,7 @@ export class TableHandlesProsemirrorPlugin<
};
getCellsAtRowHandle = (
- block: BlockFromConfigNoChildren,
+ block: BlockFromConfig,
relativeRowIndex: RelativeCellIndices["row"],
) => {
return getCellsAtRowHandle(block, relativeRowIndex);
@@ -931,7 +942,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 +1172,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 +1203,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/i18n/locales/ar.ts b/packages/core/src/i18n/locales/ar.ts
index 00574debed..8cee70bff4 100644
--- a/packages/core/src/i18n/locales/ar.ts
+++ b/packages/core/src/i18n/locales/ar.ts
@@ -2,7 +2,7 @@ import type { Dictionary } from "../dictionary.js";
export const ar: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "عنوان 1",
subtext: "يستخدم لعناوين المستوى الأعلى",
aliases: ["ع", "عنوان1", "ع1"],
@@ -38,7 +38,7 @@ export const ar: Dictionary = {
aliases: ["ع6", "عنوان6", "العنوان الفرعي الأدنى"],
group: "العناوين الفرعية",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "عنوان قابل للطي 1",
subtext: "عنوان قابل للطي لإظهار وإخفاء المحتوى",
aliases: ["ع", "عنوان1", "ع1", "قابل للطي", "طي"],
diff --git a/packages/core/src/i18n/locales/de.ts b/packages/core/src/i18n/locales/de.ts
index cac48dd80a..2aaec1c039 100644
--- a/packages/core/src/i18n/locales/de.ts
+++ b/packages/core/src/i18n/locales/de.ts
@@ -2,7 +2,7 @@ import { Dictionary } from "../dictionary.js";
export const de: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Überschrift 1",
subtext: "Hauptebene Überschrift",
aliases: ["h", "überschrift1", "h1"],
@@ -38,7 +38,7 @@ export const de: Dictionary = {
aliases: ["h6", "überschrift6", "unterüberschrift6"],
group: "Unterüberschriften",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Aufklappbare Überschrift 1",
subtext: "Aufklappbare Hauptebene Überschrift",
aliases: ["h", "überschrift1", "h1", "aufklappbar", "einklappbar"],
diff --git a/packages/core/src/i18n/locales/en.ts b/packages/core/src/i18n/locales/en.ts
index af7927d207..d888608b2f 100644
--- a/packages/core/src/i18n/locales/en.ts
+++ b/packages/core/src/i18n/locales/en.ts
@@ -1,6 +1,6 @@
export const en = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Heading 1",
subtext: "Top-level heading",
aliases: ["h", "heading1", "h1"],
@@ -36,7 +36,7 @@ export const en = {
aliases: ["h6", "heading6", "subheading6"],
group: "Subheadings",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Toggle Heading 1",
subtext: "Toggleable top-level heading",
aliases: ["h", "heading1", "h1", "collapsable"],
diff --git a/packages/core/src/i18n/locales/es.ts b/packages/core/src/i18n/locales/es.ts
index 9e830b406b..efc2f57aa6 100644
--- a/packages/core/src/i18n/locales/es.ts
+++ b/packages/core/src/i18n/locales/es.ts
@@ -2,7 +2,7 @@ import { Dictionary } from "../dictionary.js";
export const es: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Encabezado 1",
subtext: "Encabezado de primer nivel",
aliases: ["h", "encabezado1", "h1"],
@@ -38,7 +38,7 @@ export const es: Dictionary = {
aliases: ["h6", "encabezado6", "subencabezado6"],
group: "Subencabezados",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Encabezado Plegable 1",
subtext: "Encabezado de primer nivel que se puede plegar",
aliases: ["h", "encabezado1", "h1", "plegable", "contraible"],
diff --git a/packages/core/src/i18n/locales/fr.ts b/packages/core/src/i18n/locales/fr.ts
index b56e6942f6..bba08cb2a5 100644
--- a/packages/core/src/i18n/locales/fr.ts
+++ b/packages/core/src/i18n/locales/fr.ts
@@ -2,7 +2,7 @@ import { Dictionary } from "../dictionary.js";
export const fr: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Titre 1",
subtext: "Utilisé pour un titre de premier niveau",
aliases: ["h", "titre1", "h1"],
@@ -39,7 +39,7 @@ export const fr: Dictionary = {
aliases: ["h6", "titre6", "sous-titre6"],
group: "Sous-titres",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Titre Repliable 1",
subtext:
"Titre de premier niveau qui peut être replié pour masquer son contenu",
diff --git a/packages/core/src/i18n/locales/he.ts b/packages/core/src/i18n/locales/he.ts
index 553fc42941..f5cea6168d 100644
--- a/packages/core/src/i18n/locales/he.ts
+++ b/packages/core/src/i18n/locales/he.ts
@@ -2,7 +2,7 @@ import { Dictionary } from "../dictionary.js";
export const he: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "כותרת 1",
subtext: "כותרת ראשית",
aliases: ["h", "heading1", "h1"],
@@ -20,7 +20,7 @@ export const he: Dictionary = {
aliases: ["h3", "heading3", "subheading"],
group: "כותרות",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "כותרת מתקפלת 1",
subtext: "כותרת ראשית מתקפלת",
aliases: ["h", "heading1", "h1", "collapsable"],
diff --git a/packages/core/src/i18n/locales/hr.ts b/packages/core/src/i18n/locales/hr.ts
index 31c0b71159..ea0895903a 100644
--- a/packages/core/src/i18n/locales/hr.ts
+++ b/packages/core/src/i18n/locales/hr.ts
@@ -2,7 +2,7 @@ import { Dictionary } from "../dictionary.js";
export const hr: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Naslov 1",
subtext: "Glavni naslov",
aliases: ["h", "naslov1", "h1"],
@@ -38,7 +38,7 @@ export const hr: Dictionary = {
aliases: ["h6", "naslov6", "podnaslov6"],
group: "Podnaslovi",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Proširivi Naslov 1",
subtext: "Proširivi glavni naslov",
aliases: ["h", "naslov1", "h1", "proširivi"],
diff --git a/packages/core/src/i18n/locales/is.ts b/packages/core/src/i18n/locales/is.ts
index 25060d651f..d1599e5f6b 100644
--- a/packages/core/src/i18n/locales/is.ts
+++ b/packages/core/src/i18n/locales/is.ts
@@ -2,7 +2,7 @@ import type { Dictionary } from "../dictionary.js";
export const is: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Fyrirsögn 1",
subtext: "Notað fyrir efstu fyrirsögn",
aliases: ["h", "fyrirsogn1", "h1"],
@@ -38,7 +38,7 @@ export const is: Dictionary = {
aliases: ["h6", "fyrirsogn6", "undirfyrirsogn6"],
group: "Undirfyrirsagnir",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Fellanleg Fyrirsögn 1",
subtext:
"Fellanleg efsta fyrirsögn sem hægt er að sýna eða fela innihald",
diff --git a/packages/core/src/i18n/locales/it.ts b/packages/core/src/i18n/locales/it.ts
index 45d9dcd277..5e9fc75290 100644
--- a/packages/core/src/i18n/locales/it.ts
+++ b/packages/core/src/i18n/locales/it.ts
@@ -2,7 +2,7 @@ import { Dictionary } from "../dictionary.js";
export const it: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Intestazione 1",
subtext: "Intestazione di primo livello",
aliases: ["h", "intestazione1", "h1"],
@@ -38,7 +38,7 @@ export const it: Dictionary = {
aliases: ["h6", "intestazione6", "sottotitolo6"],
group: "Sottotitoli",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Intestazione Espandibile 1",
subtext:
"Intestazione di primo livello che può essere espansa o compressa per mostrare il contenuto",
diff --git a/packages/core/src/i18n/locales/ja.ts b/packages/core/src/i18n/locales/ja.ts
index 236b834443..fa23229b75 100644
--- a/packages/core/src/i18n/locales/ja.ts
+++ b/packages/core/src/i18n/locales/ja.ts
@@ -2,7 +2,7 @@ import { Dictionary } from "../dictionary.js";
export const ja: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "見出し1",
subtext: "トップレベルの見出しに使用",
aliases: ["h", "見出し1", "h1", "大見出し"],
@@ -38,7 +38,7 @@ export const ja: Dictionary = {
aliases: ["h6", "見出し6", "subheading6", "小見出し6"],
group: "サブ見出し",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "折りたたみ見出し1",
subtext: "内容の表示/非表示が切り替え可能なトップレベルの見出し",
aliases: ["h", "見出し1", "h1", "大見出し", "折りたたみ", "トグル"],
diff --git a/packages/core/src/i18n/locales/ko.ts b/packages/core/src/i18n/locales/ko.ts
index cce4c8f7c6..33eb5e1ce7 100644
--- a/packages/core/src/i18n/locales/ko.ts
+++ b/packages/core/src/i18n/locales/ko.ts
@@ -2,7 +2,7 @@ import { Dictionary } from "../dictionary.js";
export const ko: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "제목1",
subtext: "섹션 제목(대)",
aliases: ["h", "제목1", "h1", "대제목"],
@@ -38,7 +38,7 @@ export const ko: Dictionary = {
aliases: ["h6", "제목6", "소제목6"],
group: "소제목",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "접을 수 있는 제목1",
subtext: "내용을 표시하거나 숨길 수 있는 섹션 제목(대)",
aliases: ["h", "제목1", "h1", "대제목", "접기", "토글"],
diff --git a/packages/core/src/i18n/locales/nl.ts b/packages/core/src/i18n/locales/nl.ts
index 6d1f48cde2..93776f2f4a 100644
--- a/packages/core/src/i18n/locales/nl.ts
+++ b/packages/core/src/i18n/locales/nl.ts
@@ -2,7 +2,7 @@ import type { Dictionary } from "../dictionary.js";
export const nl: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Kop 1",
subtext: "Gebruikt voor een hoofdkop",
aliases: ["h", "kop1", "h1"],
@@ -38,7 +38,7 @@ export const nl: Dictionary = {
aliases: ["h6", "kop6", "subkop6"],
group: "Subkoppen",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Uitklapbare Kop 1",
subtext: "Hoofdkop die kan worden uit- en ingeklapt om inhoud te tonen",
aliases: ["h", "kop1", "h1", "uitklapbaar", "inklapbaar"],
diff --git a/packages/core/src/i18n/locales/no.ts b/packages/core/src/i18n/locales/no.ts
index c28cac2b9f..6bfbec346e 100644
--- a/packages/core/src/i18n/locales/no.ts
+++ b/packages/core/src/i18n/locales/no.ts
@@ -2,7 +2,7 @@ import { Dictionary } from "../dictionary.js";
export const no: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Overskrift 1",
subtext: "Toppnivåoverskrift",
aliases: ["h", "overskrift1", "h1"],
@@ -38,7 +38,7 @@ export const no: Dictionary = {
aliases: ["h6", "overskrift6", "underoverskrift6"],
group: "Underoverskrifter",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Sammenleggbar Overskrift 1",
subtext: "Toppnivåoverskrift som kan vises eller skjules",
aliases: ["h", "overskrift1", "h1", "sammenleggbar", "toggle"],
diff --git a/packages/core/src/i18n/locales/pl.ts b/packages/core/src/i18n/locales/pl.ts
index 35bf1f255a..47de5ccd0d 100644
--- a/packages/core/src/i18n/locales/pl.ts
+++ b/packages/core/src/i18n/locales/pl.ts
@@ -2,7 +2,7 @@ import type { Dictionary } from "../dictionary.js";
export const pl: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Nagłówek 1",
subtext: "Używany dla nagłówka najwyższego poziomu",
aliases: ["h", "naglowek1", "h1"],
@@ -38,7 +38,7 @@ export const pl: Dictionary = {
aliases: ["h6", "naglowek6", "podnaglowek6"],
group: "Podnagłówki",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Nagłówek rozwijany 1",
subtext: "Rozwijany nagłówek najwyższego poziomu",
aliases: ["h", "naglowek1", "h1", "rozwijany"],
diff --git a/packages/core/src/i18n/locales/pt.ts b/packages/core/src/i18n/locales/pt.ts
index 7801cd2d36..9a22161de5 100644
--- a/packages/core/src/i18n/locales/pt.ts
+++ b/packages/core/src/i18n/locales/pt.ts
@@ -2,7 +2,7 @@ import type { Dictionary } from "../dictionary.js";
export const pt: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Título",
subtext: "Usado para um título de nível superior",
aliases: ["h", "titulo1", "h1"],
@@ -38,7 +38,7 @@ export const pt: Dictionary = {
aliases: ["h6", "titulo6", "subtitulo6"],
group: "Subtítulos",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Título Expansível",
subtext: "Título expansível de nível superior",
aliases: ["h", "titulo1", "h1", "expansível"],
diff --git a/packages/core/src/i18n/locales/ru.ts b/packages/core/src/i18n/locales/ru.ts
index b150359714..28a16d0a75 100644
--- a/packages/core/src/i18n/locales/ru.ts
+++ b/packages/core/src/i18n/locales/ru.ts
@@ -2,7 +2,7 @@ import { Dictionary } from "../dictionary.js";
export const ru: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Заголовок 1 уровня",
subtext: "Используется для заголовка верхнего уровня",
aliases: ["h", "heading1", "h1", "заголовок1"],
@@ -38,7 +38,7 @@ export const ru: Dictionary = {
aliases: ["h6", "heading6", "subheading6", "заголовок6", "подзаголовок6"],
group: "Подзаголовки",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Сворачиваемый заголовок 1 уровня",
subtext: "Заголовок верхнего уровня, который можно свернуть/развернуть",
aliases: ["h", "heading1", "h1", "заголовок1", "сворачиваемый"],
diff --git a/packages/core/src/i18n/locales/sk.ts b/packages/core/src/i18n/locales/sk.ts
index cbdd0b706f..658438eac2 100644
--- a/packages/core/src/i18n/locales/sk.ts
+++ b/packages/core/src/i18n/locales/sk.ts
@@ -1,6 +1,6 @@
export const sk = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Nadpis 1",
subtext: "Nadpis najvyššej úrovne",
aliases: ["h", "nadpis1", "h1"],
@@ -36,7 +36,7 @@ export const sk = {
aliases: ["h6", "nadpis6", "podnadpis"],
group: "Podnáslovi",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Rozbaľovací Nadpis 1",
subtext: "Rozbaľovací nadpis najvyššej úrovne",
aliases: ["h", "nadpis1", "h1", "rozbaľovací"],
diff --git a/packages/core/src/i18n/locales/uk.ts b/packages/core/src/i18n/locales/uk.ts
index a99a4259c6..5c363fc38d 100644
--- a/packages/core/src/i18n/locales/uk.ts
+++ b/packages/core/src/i18n/locales/uk.ts
@@ -2,7 +2,7 @@ import { Dictionary } from "../dictionary.js";
export const uk: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Заголовок 1",
subtext: "Заголовок найвищого рівня",
aliases: ["h", "heading1", "h1", "заголовок1"],
@@ -38,7 +38,7 @@ export const uk: Dictionary = {
aliases: ["h6", "heading6", "subheading6", "заголовок6", "підзаголовок6"],
group: "Підзаголовки",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Розгортаємий заголовок 1",
subtext: "Розгортаємий заголовок найвищого рівня",
aliases: ["h", "heading1", "h1", "заголовок1", "розгортаємий"],
diff --git a/packages/core/src/i18n/locales/vi.ts b/packages/core/src/i18n/locales/vi.ts
index b300fdcfd0..df1b547b16 100644
--- a/packages/core/src/i18n/locales/vi.ts
+++ b/packages/core/src/i18n/locales/vi.ts
@@ -2,7 +2,7 @@ import type { Dictionary } from "../dictionary.js";
export const vi: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "Tiêu đề H1",
subtext: "Sử dụng cho tiêu đề cấp cao nhất",
aliases: ["h", "tieude1", "dd1"],
@@ -38,7 +38,7 @@ export const vi: Dictionary = {
aliases: ["h6", "tieude6", "tieudephu6"],
group: "Tiêu đề phụ",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "Tiêu đề có thể thu gọn H1",
subtext: "Tiêu đề cấp cao nhất có thể thu gọn",
aliases: ["h", "tieude1", "dd1", "thugon"],
diff --git a/packages/core/src/i18n/locales/zh-tw.ts b/packages/core/src/i18n/locales/zh-tw.ts
index e9aa1e8ac6..f8ca3ec3aa 100644
--- a/packages/core/src/i18n/locales/zh-tw.ts
+++ b/packages/core/src/i18n/locales/zh-tw.ts
@@ -2,7 +2,7 @@ import type { Dictionary } from "../dictionary.js";
export const zhTW: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "一級標題",
subtext: "用於頂級標題",
aliases: ["h", "heading1", "h1", "標題", "一級標題"],
@@ -38,7 +38,7 @@ export const zhTW: Dictionary = {
aliases: ["h6", "heading6", "subheading", "標題", "六級標題"],
group: "副標題",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "可摺疊一級標題",
subtext: "可摺疊的頂級標題",
aliases: ["h", "heading1", "h1", "標題", "一級標題", "摺疊"],
diff --git a/packages/core/src/i18n/locales/zh.ts b/packages/core/src/i18n/locales/zh.ts
index b44c81aa36..f9be961c22 100644
--- a/packages/core/src/i18n/locales/zh.ts
+++ b/packages/core/src/i18n/locales/zh.ts
@@ -2,7 +2,7 @@ import type { Dictionary } from "../dictionary.js";
export const zh: Dictionary = {
slash_menu: {
- heading: {
+ heading_1: {
title: "一级标题",
subtext: "用于顶级标题",
aliases: ["h", "heading1", "h1", "标题", "一级标题"],
@@ -38,7 +38,7 @@ export const zh: Dictionary = {
aliases: ["h6", "heading6", "subheading6", "六级标题"],
group: "副标题",
},
- toggle_heading: {
+ toggle_heading_1: {
title: "可折叠一级标题",
subtext: "可折叠的顶级标题",
aliases: ["h", "heading1", "h1", "标题", "一级标题", "折叠"],
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 11fe0e5460..9845d206d3 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -55,3 +55,5 @@ export * from "./api/parsers/markdown/parseMarkdown.js";
// TODO: for ai, remove?
export * from "./api/blockManipulation/getBlock/getBlock.js";
export * from "./api/positionMapping.js";
+
+// export type * from "zod";
diff --git a/packages/core/src/schema/schema.ts b/packages/core/src/schema/CustomBlockNoteSchema.ts
similarity index 77%
rename from packages/core/src/schema/schema.ts
rename to packages/core/src/schema/CustomBlockNoteSchema.ts
index 92d0a7ab88..1453a7dc4c 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) {
@@ -26,23 +25,50 @@ function removeUndefined | undefined>(obj: T): T {
) as T;
}
+/**
+ * Do note that this is separate from the `BlockNoteSchema` which is now reduced to a simple factory for instantiating a {@link CustomBlockNoteSchema} instance.
+ * In the future, we will rename the `BlockNoteSchema` to `DefaultBlockNoteSchema` and this will be the default schema that is used when no schema is passed to the editor.
+ * At that time, `CustomBlockNoteSchema` will be renamed to `BlockNoteSchema` and this will be the base class for all schemas.
+ */
+
+/**
+ * The CustomBlockNoteSchema class defines the shape of the schema that BlockNote uses, it defines all of the blocks, inline content, and styles that are available in the editor.
+ * You can create a custom schema by extending the CustomBlockNoteSchema class and passing in the blocks, inline content, and styles that you want to use.
+ *
+ * @example
+ * ```typescript
+ * const schema = new CustomBlockNoteSchema({
+ * blockSpecs: {
+ * block: { type: "block", content: "styled" },
+ * },
+ * });
+ * // extending the schema
+ * const extendedSchema = schema.extend({
+ * blockSpecs: {
+ * block: { type: "block", content: "styled" },
+ * },
+ * });
+ *
+ * // using the schema
+ * const editor = new BlockNoteEditor({
+ * schema: extendedSchema,
+ * });
+ * ```
+ */
export class CustomBlockNoteSchema<
- BSchema extends BlockSchema,
- ISchema extends InlineContentSchema,
- SSchema extends StyleSchema,
+ BSchema extends BlockSchema = Record,
+ ISchema extends InlineContentSchema = Record,
+ SSchema extends StyleSchema = Record,
> {
// Helper so that you can use typeof schema.BlockNoteEditor
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/internal.ts b/packages/core/src/schema/blocks/internal.ts
index 3cfb268d6f..8b4bd37e20 100644
--- a/packages/core/src/schema/blocks/internal.ts
+++ b/packages/core/src/schema/blocks/internal.ts
@@ -1,5 +1,7 @@
import { Attribute, Attributes, Editor, Node } from "@tiptap/core";
+import * as z from "zod/v4/core";
import { defaultBlockToHTML } from "../../blocks/defaultBlockHelpers.js";
+
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
import { mergeCSSClasses } from "../../util/browser.js";
@@ -9,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
@@ -20,62 +22,53 @@ import {
export function propsToAttributes(propSchema: PropSchema): Attributes {
const tiptapAttributes: Record = {};
- Object.entries(propSchema).forEach(([name, spec]) => {
- tiptapAttributes[name] = {
- default: spec.default,
- 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;
- }
-
- if (
- (spec.default === undefined && spec.type === "boolean") ||
- (spec.default !== undefined && typeof spec.default === "boolean")
- ) {
- if (value === "true") {
- return true;
+ 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.
+ // TODO: needed? (seems tiptap specific)
+ parseHTML: (element) => {
+ const value = element.getAttribute(camelToDataKebab(name));
+
+ if (value === null) {
+ return null;
}
- if (value === "false") {
- return false;
+ // 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);
}
-
- return null;
- }
-
- if (
- (spec.default === undefined && spec.type === "number") ||
- (spec.default !== undefined && typeof spec.default === "number")
- ) {
- const asNumber = parseFloat(value);
- const isNumeric =
- !Number.isNaN(asNumber) && Number.isFinite(asNumber);
-
- if (isNumeric) {
- return asNumber;
+ },
+ // TODO: needed? (seems tiptap specific)
+ renderHTML: (attributes) => {
+ // don't render to html if the value is the same as the default
+ if (attributes[name] === def) {
+ return {};
}
-
- return null;
- }
-
- return value;
- },
- renderHTML: (attributes) => {
- // don't render to html if the value is the same as the default
- return attributes[name] !== spec.default
- ? {
- [camelToDataKebab(name)]: attributes[name],
- }
- : {};
- },
- };
- });
+ if (typeof attributes[name] === "object") {
+ return {
+ [camelToDataKebab(name)]: JSON.stringify(attributes[name]),
+ };
+ }
+ return {
+ [camelToDataKebab(name)]: attributes[name],
+ };
+ },
+ };
+ },
+ );
return tiptapAttributes;
}
@@ -109,9 +102,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
>;
@@ -167,10 +159,18 @@ 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[prop];
- const defaultValue = spec.default;
+ const spec = propSchema._zodSource._zod.def.shape[prop];
+ const defaultValue =
+ spec instanceof z.$ZodDefault ? spec._zod.def.defaultValue : undefined;
if (value !== defaultValue) {
- blockContent.setAttribute(camelToDataKebab(prop), value);
+ if (typeof value === "string") {
+ blockContent.setAttribute(camelToDataKebab(prop), value);
+ } else {
+ blockContent.setAttribute(
+ camelToDataKebab(prop),
+ JSON.stringify(value),
+ );
+ }
}
}
// Adds file block attribute
diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts
index 87ec01ab1b..afa252e0bf 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"
@@ -72,7 +73,7 @@ export interface BlockConfig<
type: T;
/**
* The properties that the block supports
- * @todo will be zod schema in the future
+ * Now uses Zod schema for validation and type inference
*/
readonly propSchema: PS;
/**
@@ -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,
@@ -305,44 +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[];
-};
-
-/** 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,
@@ -376,32 +313,11 @@ export type PartialBlockNoDefaults<
BSchema extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema,
-> = PartialBlocksWithoutChildren<
- BSchema,
- I,
- S
->[keyof PartialBlocksWithoutChildren] &
+> = PartialBlocksWithoutChildren[keyof BSchema] &
Partial<{
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..f3cf43f95f 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 "./partialBlockToBlock.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 f4522936a4..b53c006190 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 "../partialBlockToBlock.js";
import { Props } from "../propTypes.js";
import { StyleSchema } from "../styles/types.js";
import {
@@ -193,7 +194,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/internal.ts b/packages/core/src/schema/inlineContent/internal.ts
index 9d10c7cb4e..7692e163a4 100644
--- a/packages/core/src/schema/inlineContent/internal.ts
+++ b/packages/core/src/schema/inlineContent/internal.ts
@@ -1,4 +1,5 @@
import { KeyboardShortcutCommand, Node } from "@tiptap/core";
+import * as z from "zod/v4/core";
import { camelToDataKebab } from "../../util/string.js";
import { PropSchema, Props } from "../propTypes.js";
@@ -32,15 +33,18 @@ export function addInlineContentAttributes<
element.dom.setAttribute("data-inline-content-type", inlineContentType);
// Adds props as HTML attributes in kebab-case with "data-" prefix. Skips props
// set to their default values.
- Object.entries(inlineContentProps)
- .filter(([prop, value]) => {
- const spec = propSchema[prop];
- return value !== spec.default;
- })
- .map(([prop, value]) => {
- return [camelToDataKebab(prop), value];
- })
- .forEach(([prop, value]) => element.dom.setAttribute(prop, value));
+ for (const [prop, value] of Object.entries(inlineContentProps)) {
+ const spec = propSchema._zodSource._zod.def.shape[prop];
+ const defaultValue =
+ spec instanceof z.$ZodDefault ? spec._zod.def.defaultValue : undefined;
+ if (value !== defaultValue) {
+ if (typeof value === "string") {
+ element.dom.setAttribute(camelToDataKebab(prop), value);
+ } else {
+ element.dom.setAttribute(camelToDataKebab(prop), JSON.stringify(value));
+ }
+ }
+ }
if (element.contentDOM) {
element.contentDOM.setAttribute("data-editable", "");
diff --git a/packages/core/src/schema/inlineContent/types.ts b/packages/core/src/schema/inlineContent/types.ts
index 4dca7a0aa3..00565932f4 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/partialBlockToBlock.ts b/packages/core/src/schema/partialBlockToBlock.ts
new file mode 100644
index 0000000000..c634e7a544
--- /dev/null
+++ b/packages/core/src/schema/partialBlockToBlock.ts
@@ -0,0 +1,259 @@
+import * as z from "zod/v4/core";
+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";
+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._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;
+ }
+ }
+ },
+ );
+ 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 || undefined;
+
+ 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 partialBlockToBlock<
+ BSchema extends BlockSchema,
+ I extends InlineContentSchema,
+ S extends StyleSchema,
+>(
+ schema: CustomBlockNoteSchema,
+ partialBlock: PartialBlock,
+): Block {
+ const id = partialBlock.id || UniqueID.options.generateID();
+
+ // Note: we might want to make "type" required for partial blocks and remove this default
+ 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) => partialBlockToBlock(schema, child)) ||
+ [];
+
+ return {
+ id,
+ type,
+ props,
+ content,
+ children,
+ } as Block