From 5187307f4a6405636a339476fb17d3796cfc81a7 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Fri, 31 May 2024 18:23:10 +0200 Subject: [PATCH] Refactored `contentRef` prop into `useContent` hook --- .../Alert.tsx | 150 ++++++++------ .../05-custom-schema/01-alert-block/Alert.tsx | 151 ++++++++------ .../05-custom-schema/03-font-style/Font.tsx | 12 +- .../react-custom-blocks/App.tsx | 107 +++++++--- .../react-custom-inline-content/App.tsx | 19 +- .../react-custom-styles/App.tsx | 23 ++- packages/react/src/hooks/useContent.tsx | 9 + packages/react/src/index.ts | 1 + .../react/src/schema/@util/ReactRenderUtil.ts | 9 +- packages/react/src/schema/ReactBlockSpec.tsx | 27 +-- .../src/schema/ReactInlineContentSpec.tsx | 8 +- packages/react/src/schema/ReactStyleSpec.tsx | 6 +- .../src/test/testCases/customReactBlocks.tsx | 29 ++- .../testCases/customReactInlineContent.tsx | 19 +- .../src/test/testCases/customReactStyles.tsx | 23 ++- tests/src/utils/customblocks/ReactAlert.tsx | 190 ++++++++++-------- tests/src/utils/customblocks/ReactImage.tsx | 79 +++++--- 17 files changed, 493 insertions(+), 369 deletions(-) create mode 100644 packages/react/src/hooks/useContent.tsx diff --git a/examples/02-ui-components/03-formatting-toolbar-block-type-items/Alert.tsx b/examples/02-ui-components/03-formatting-toolbar-block-type-items/Alert.tsx index 44d1d5d69..33596a6e8 100644 --- a/examples/02-ui-components/03-formatting-toolbar-block-type-items/Alert.tsx +++ b/examples/02-ui-components/03-formatting-toolbar-block-type-items/Alert.tsx @@ -1,5 +1,14 @@ -import { defaultProps } from "@blocknote/core"; -import { createReactBlockSpec } from "@blocknote/react"; +import { + BlockConfig, + DefaultInlineContentSchema, + defaultProps, + DefaultStyleSchema, +} from "@blocknote/core"; +import { + createReactBlockSpec, + ReactCustomBlockRenderProps, + useContent, +} from "@blocknote/react"; import { Menu } from "@mantine/core"; import { MdCancel, MdCheckCircle, MdError, MdInfo } from "react-icons/md"; @@ -49,72 +58,79 @@ export const alertTypes = [ }, ] as const; -// The Alert block. -export const Alert = createReactBlockSpec( - { - type: "alert", - propSchema: { - textAlignment: defaultProps.textAlignment, - textColor: defaultProps.textColor, - type: { - default: "warning", - values: ["warning", "error", "info", "success"], - }, +const alertBlockConfig = { + type: "alert", + propSchema: { + textAlignment: defaultProps.textAlignment, + textColor: defaultProps.textColor, + type: { + default: "warning", + values: ["warning", "error", "info", "success"], }, - content: "inline", }, - { - render: (props) => { - const alertType = alertTypes.find( - (a) => a.value === props.block.props.type - )!; - const Icon = alertType.icon; + content: "inline", +} satisfies BlockConfig; - return ( -
- {/*Icon which opens a menu to choose the Alert type*/} - - -
- -
-
- {/*Dropdown to change the Alert type*/} - - Alert Type - - {alertTypes.map((type) => { - const ItemIcon = type.icon; +const RenderAlert = ( + props: ReactCustomBlockRenderProps< + typeof alertBlockConfig, + DefaultInlineContentSchema, + DefaultStyleSchema + > +) => { + const contentProps = useContent(); - return ( - - } - onClick={() => - props.editor.updateBlock(props.block, { - type: "alert", - props: { type: type.value }, - }) - }> - {type.title} - - ); - })} - -
- {/*Rich text field for user to type in*/} -
-
- ); - }, - } -); + const alertType = alertTypes.find((a) => a.value === props.block.props.type)!; + const Icon = alertType.icon; + + return ( +
+ {/*Icon which opens a menu to choose the Alert type*/} + + +
+ +
+
+ {/*Dropdown to change the Alert type*/} + + Alert Type + + {alertTypes.map((type) => { + const ItemIcon = type.icon; + + return ( + + } + onClick={() => + props.editor.updateBlock(props.block, { + type: "alert", + props: { type: type.value }, + }) + }> + {type.title} + + ); + })} + +
+ {/*Rich text field for user to type in*/} +
+
+ ); +}; + +// The Alert block. +export const Alert = createReactBlockSpec(alertBlockConfig, { + render: RenderAlert, +}); diff --git a/examples/05-custom-schema/01-alert-block/Alert.tsx b/examples/05-custom-schema/01-alert-block/Alert.tsx index 99868fda2..8c43c08bd 100644 --- a/examples/05-custom-schema/01-alert-block/Alert.tsx +++ b/examples/05-custom-schema/01-alert-block/Alert.tsx @@ -1,5 +1,14 @@ -import { defaultProps } from "@blocknote/core"; -import { createReactBlockSpec } from "@blocknote/react"; +import { + BlockConfig, + DefaultInlineContentSchema, + defaultProps, + DefaultStyleSchema, +} from "@blocknote/core"; +import { + createReactBlockSpec, + ReactCustomBlockRenderProps, + useContent, +} from "@blocknote/react"; import { Menu } from "@mantine/core"; import { MdCancel, MdCheckCircle, MdError, MdInfo } from "react-icons/md"; import "./styles.css"; @@ -48,71 +57,79 @@ export const alertTypes = [ }, ] as const; -// The Alert block. -export const Alert = createReactBlockSpec( - { - type: "alert", - propSchema: { - textAlignment: defaultProps.textAlignment, - textColor: defaultProps.textColor, - type: { - default: "warning", - values: ["warning", "error", "info", "success"], - }, +const alertBlockConfig = { + type: "alert", + propSchema: { + textAlignment: defaultProps.textAlignment, + textColor: defaultProps.textColor, + type: { + default: "warning", + values: ["warning", "error", "info", "success"], }, - content: "inline", }, - { - render: (props) => { - const alertType = alertTypes.find( - (a) => a.value === props.block.props.type - )!; - const Icon = alertType.icon; - return ( -
- {/*Icon which opens a menu to choose the Alert type*/} - - -
- -
-
- {/*Dropdown to change the Alert type*/} - - Alert Type - - {alertTypes.map((type) => { - const ItemIcon = type.icon; + content: "inline", +} satisfies BlockConfig; - return ( - - } - onClick={() => - props.editor.updateBlock(props.block, { - type: "alert", - props: { type: type.value }, - }) - }> - {type.title} - - ); - })} - -
- {/*Rich text field for user to type in*/} -
-
- ); - }, - } -); +const RenderAlert = ( + props: ReactCustomBlockRenderProps< + typeof alertBlockConfig, + DefaultInlineContentSchema, + DefaultStyleSchema + > +) => { + const contentProps = useContent(); + + const alertType = alertTypes.find((a) => a.value === props.block.props.type)!; + const Icon = alertType.icon; + + return ( +
+ {/*Icon which opens a menu to choose the Alert type*/} + + +
+ +
+
+ {/*Dropdown to change the Alert type*/} + + Alert Type + + {alertTypes.map((type) => { + const ItemIcon = type.icon; + + return ( + + } + onClick={() => + props.editor.updateBlock(props.block, { + type: "alert", + props: { type: type.value }, + }) + }> + {type.title} + + ); + })} + +
+ {/*Rich text field for user to type in*/} +
+
+ ); +}; + +// The Alert block. +export const Alert = createReactBlockSpec(alertBlockConfig, { + render: RenderAlert, +}); diff --git a/examples/05-custom-schema/03-font-style/Font.tsx b/examples/05-custom-schema/03-font-style/Font.tsx index 9cd344f7f..5f58c2d03 100644 --- a/examples/05-custom-schema/03-font-style/Font.tsx +++ b/examples/05-custom-schema/03-font-style/Font.tsx @@ -1,4 +1,10 @@ -import { createReactStyleSpec } from "@blocknote/react"; +import { createReactStyleSpec, useContent } from "@blocknote/react"; + +const RenderFont = (props: { value: string }) => { + const { style, ...rest } = useContent(); + + return ; +}; // The Font style. export const Font = createReactStyleSpec( @@ -7,8 +13,6 @@ export const Font = createReactStyleSpec( propSchema: "string", }, { - render: (props) => ( - - ), + render: RenderFont, } ); diff --git a/examples/05-custom-schema/react-custom-blocks/App.tsx b/examples/05-custom-schema/react-custom-blocks/App.tsx index f92ffb7be..96b5bd3a7 100644 --- a/examples/05-custom-schema/react-custom-blocks/App.tsx +++ b/examples/05-custom-schema/react-custom-blocks/App.tsx @@ -1,10 +1,18 @@ import { + BlockConfig, BlockNoteSchema, defaultBlockSpecs, + DefaultInlineContentSchema, defaultProps, + DefaultStyleSchema, } from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; -import { createReactBlockSpec, useCreateBlockNote } from "@blocknote/react"; +import { + createReactBlockSpec, + ReactCustomBlockRenderProps, + useContent, + useCreateBlockNote, +} from "@blocknote/react"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; @@ -34,6 +42,55 @@ const alertTypes = { }, }; +const alertBlockConfig = { + type: "alert", + propSchema: { + textAlignment: defaultProps.textAlignment, + textColor: defaultProps.textColor, + type: { + default: "warning", + values: ["warning", "error", "info", "success"], + }, + }, + content: "inline", +} satisfies BlockConfig; + +const AlertBlock = ( + props: ReactCustomBlockRenderProps< + typeof alertBlockConfig, + DefaultInlineContentSchema, + DefaultStyleSchema + > +) => { + const contentProps = useContent(); + + return ( +
+ +
+
+ ); +}; + export const alertBlock = createReactBlockSpec( { type: "alert", @@ -48,29 +105,7 @@ export const alertBlock = createReactBlockSpec( content: "inline", }, { - render: (props) => ( -
- -
-
- ), + render: AlertBlock, } ); @@ -96,6 +131,20 @@ const simpleImageBlock = createReactBlockSpec( } ); +const BracketsParagraph = () => { + const contentProps = useContent(); + + return ( +
+
{"["}
+ {"{"} +
+ {"}"} +
{"]"}
+
+ ); +}; + export const bracketsParagraphBlock = createReactBlockSpec( { type: "bracketsParagraph", @@ -105,15 +154,7 @@ export const bracketsParagraphBlock = createReactBlockSpec( }, }, { - render: (props) => ( -
-
{"["}
- {"{"} -
- {"}"} -
{"]"}
-
- ), + render: BracketsParagraph, } ); diff --git a/examples/05-custom-schema/react-custom-inline-content/App.tsx b/examples/05-custom-schema/react-custom-inline-content/App.tsx index 54f0ff725..02fa4fb8d 100644 --- a/examples/05-custom-schema/react-custom-inline-content/App.tsx +++ b/examples/05-custom-schema/react-custom-inline-content/App.tsx @@ -2,6 +2,7 @@ import { BlockNoteSchema, defaultInlineContentSpecs } from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { createReactInlineContentSpec, + useContent, useCreateBlockNote, } from "@blocknote/react"; import { BlockNoteView } from "@blocknote/mantine"; @@ -24,6 +25,16 @@ const mention = createReactInlineContentSpec( } ); +const Tag = () => { + const contentProps = useContent(); + + return ( + + # + + ); +}; + const tag = createReactInlineContentSpec( { type: "tag", @@ -31,13 +42,7 @@ const tag = createReactInlineContentSpec( content: "styled", }, { - render: (props) => { - return ( - - # - - ); - }, + render: Tag, } ); diff --git a/examples/05-custom-schema/react-custom-styles/App.tsx b/examples/05-custom-schema/react-custom-styles/App.tsx index f19533c79..85f61174f 100644 --- a/examples/05-custom-schema/react-custom-styles/App.tsx +++ b/examples/05-custom-schema/react-custom-styles/App.tsx @@ -8,34 +8,41 @@ import { useActiveStyles, useBlockNoteEditor, useComponentsContext, + useContent, useCreateBlockNote, } from "@blocknote/react"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; +const Small = () => { + const contentProps = useContent(); + + return ; +}; + const small = createReactStyleSpec( { type: "small", propSchema: "boolean", }, { - render: (props) => { - return ; - }, + render: Small, } ); +const FontSize = (props: { value: string }) => { + const { style, ...rest } = useContent(); + + return ; +}; + const fontSize = createReactStyleSpec( { type: "fontSize", propSchema: "string", }, { - render: (props) => { - return ( - - ); - }, + render: FontSize, } ); diff --git a/packages/react/src/hooks/useContent.tsx b/packages/react/src/hooks/useContent.tsx new file mode 100644 index 000000000..3e420e5c7 --- /dev/null +++ b/packages/react/src/hooks/useContent.tsx @@ -0,0 +1,9 @@ +import { useReactNodeView } from "@tiptap/react"; + +export function useContent() { + return { + ref: useReactNodeView().nodeViewContentRef, + style: { whiteSpace: "pre-wrap" }, + "data-node-view-content": "", + }; +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 2ee771bde..084a92398 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -69,6 +69,7 @@ export * from "./components/TableHandles/TableHandleMenu/TableHandleMenuProps"; export * from "./hooks/useActiveStyles"; export * from "./hooks/useBlockNoteEditor"; +export * from "./hooks/useContent"; export * from "./hooks/useCreateBlockNote"; export * from "./hooks/useEditorChange"; export * from "./hooks/useEditorContentOrSelectionChange"; diff --git a/packages/react/src/schema/@util/ReactRenderUtil.ts b/packages/react/src/schema/@util/ReactRenderUtil.ts index d88cd8637..c5b9b3a09 100644 --- a/packages/react/src/schema/@util/ReactRenderUtil.ts +++ b/packages/react/src/schema/@util/ReactRenderUtil.ts @@ -3,7 +3,7 @@ import { flushSync } from "react-dom"; import { Root, createRoot } from "react-dom/client"; export function renderToDOMSpec( - fc: (refCB: (ref: HTMLElement | null) => void) => React.ReactNode, + fc: () => React.ReactNode, editor: BlockNoteEditor | undefined ) { let contentDOM: HTMLElement | undefined; @@ -15,15 +15,12 @@ export function renderToDOMSpec( // This is currently only used for Styles. In this case, react context etc. won't be available inside `fc` root = createRoot(div); flushSync(() => { - root!.render(fc((el) => (contentDOM = el || undefined))); + root!.render(fc()); }); } else { // Render temporarily using `EditorContent` (which is stored somewhat hacky on `editor._tiptapEditor.contentComponent`) // This way React Context will still work, as `fc` will be rendered inside the existing React tree - editor._tiptapEditor.contentComponent.renderToElement( - fc((el) => (contentDOM = el || undefined)), - div - ); + editor._tiptapEditor.contentComponent.renderToElement(fc(), div); } if (!div.childElementCount) { diff --git a/packages/react/src/schema/ReactBlockSpec.tsx b/packages/react/src/schema/ReactBlockSpec.tsx index ba36009a6..7ee6dadb5 100644 --- a/packages/react/src/schema/ReactBlockSpec.tsx +++ b/packages/react/src/schema/ReactBlockSpec.tsx @@ -18,7 +18,6 @@ import { StyleSchema, } from "@blocknote/core"; import { - NodeViewContent, NodeViewProps, NodeViewWrapper, ReactNodeViewRenderer, @@ -35,7 +34,6 @@ export type ReactCustomBlockRenderProps< > = { block: BlockFromConfig; editor: BlockNoteEditor, I, S>; - contentRef: (node: HTMLElement | null) => void; }; // extend BlockConfig but use a React render function @@ -156,9 +154,6 @@ export function createReactBlockSpec< const blockContentDOMAttributes = this.options.domAttributes?.blockContent || {}; - // hacky, should export `useReactNodeView` from tiptap to get access to ref - const ref = (NodeViewContent({}) as any).ref; - const BlockContent = blockImplementation.render; return ( - + ); }, @@ -190,17 +181,13 @@ export function createReactBlockSpec< const BlockContent = blockImplementation.render; const output = renderToDOMSpec( - (refCB) => ( + () => ( - + ), editor @@ -215,18 +202,14 @@ export function createReactBlockSpec< const BlockContent = blockImplementation.toExternalHTML || blockImplementation.render; - const output = renderToDOMSpec((refCB) => { + const output = renderToDOMSpec(() => { return ( - + ); }, editor); diff --git a/packages/react/src/schema/ReactInlineContentSpec.tsx b/packages/react/src/schema/ReactInlineContentSpec.tsx index 27999dcfe..3542b4885 100644 --- a/packages/react/src/schema/ReactInlineContentSpec.tsx +++ b/packages/react/src/schema/ReactInlineContentSpec.tsx @@ -15,7 +15,6 @@ import { StyleSchema, } from "@blocknote/core"; import { - NodeViewContent, NodeViewProps, NodeViewWrapper, ReactNodeViewRenderer, @@ -34,7 +33,6 @@ export type ReactInlineContentImplementation< > = { render: FC<{ inlineContent: InlineContentFromConfig; - contentRef: (node: HTMLElement | null) => void; }>; // TODO? // toExternalHTML?: FC<{ @@ -119,7 +117,7 @@ export function createReactInlineContentSpec< ) as any as InlineContentFromConfig; // TODO: fix cast const Content = inlineContentImplementation.render; const output = renderToDOMSpec( - (refCB) => , + () => , editor ); @@ -137,9 +135,6 @@ export function createReactInlineContentSpec< return (props) => ReactNodeViewRenderer( (props: NodeViewProps) => { - // hacky, should export `useReactNodeView` from tiptap to get access to ref - const ref = (NodeViewContent({}) as any).ref; - const Content = inlineContentImplementation.render; return ( = { - render: T["propSchema"] extends "boolean" - ? FC<{ contentRef: (el: HTMLElement | null) => void }> - : FC<{ contentRef: (el: HTMLElement | null) => void; value: string }>; + render: T["propSchema"] extends "boolean" ? FC : FC<{ value: string }>; }; // A function to create custom block for API consumers @@ -44,7 +42,7 @@ export function createReactStyleSpec( const Content = styleImplementation.render; const renderResult = renderToDOMSpec( - (refCB) => , + () => , undefined ); diff --git a/packages/react/src/test/testCases/customReactBlocks.tsx b/packages/react/src/test/testCases/customReactBlocks.tsx index 5081e0aa7..e395bca3f 100644 --- a/packages/react/src/test/testCases/customReactBlocks.tsx +++ b/packages/react/src/test/testCases/customReactBlocks.tsx @@ -13,33 +13,42 @@ import { createContext, useContext } from "react"; import { createReactBlockSpec } from "../../schema/ReactBlockSpec"; import { ReactFileBlock } from "../../blocks/FileBlockContent/FileBlockContent"; import { ReactImageBlock } from "../../blocks/ImageBlockContent/ImageBlockContent"; +import { useContent } from "../../hooks/useContent"; -const ReactCustomParagraph = createReactBlockSpec( +const ReactCustomParagraph = () => { + const contentProps = useContent(); + + return

; +}; + +const reactCustomParagraph = createReactBlockSpec( { type: "reactCustomParagraph", propSchema: defaultProps, content: "inline", }, { - render: (props) => ( -

- ), + render: ReactCustomParagraph, toExternalHTML: () => (

Hello World

), } ); -const SimpleReactCustomParagraph = createReactBlockSpec( +const SimpleReactCustomParagraph = () => { + const contentProps = useContent(); + + return

; +}; + +const simpleReactCustomParagraph = createReactBlockSpec( { type: "simpleReactCustomParagraph", propSchema: defaultProps, content: "inline", }, { - render: (props) => ( -

- ), + render: SimpleReactCustomParagraph, } ); @@ -70,8 +79,8 @@ const schema = BlockNoteSchema.create({ ...defaultBlockSpecs, reactFile: ReactFileBlock, reactImage: ReactImageBlock, - reactCustomParagraph: ReactCustomParagraph, - simpleReactCustomParagraph: SimpleReactCustomParagraph, + reactCustomParagraph: reactCustomParagraph, + simpleReactCustomParagraph: simpleReactCustomParagraph, reactContextParagraph: ReactContextParagraph, }, }); diff --git a/packages/react/src/test/testCases/customReactInlineContent.tsx b/packages/react/src/test/testCases/customReactInlineContent.tsx index 72bc4947f..6cbccf9f8 100644 --- a/packages/react/src/test/testCases/customReactInlineContent.tsx +++ b/packages/react/src/test/testCases/customReactInlineContent.tsx @@ -8,6 +8,7 @@ import { uploadToTmpFilesDotOrg_DEV_ONLY, } from "@blocknote/core"; import { createReactInlineContentSpec } from "../../schema/ReactInlineContentSpec"; +import { useContent } from "../../hooks/useContent"; const mention = createReactInlineContentSpec( { @@ -26,6 +27,16 @@ const mention = createReactInlineContentSpec( } ); +const Tag = () => { + const contentProps = useContent(); + + return ( + + # + + ); +}; + const tag = createReactInlineContentSpec( { type: "tag", @@ -33,13 +44,7 @@ const tag = createReactInlineContentSpec( content: "styled", }, { - render: (props) => { - return ( - - # - - ); - }, + render: Tag, } ); diff --git a/packages/react/src/test/testCases/customReactStyles.tsx b/packages/react/src/test/testCases/customReactStyles.tsx index 4021ca2b6..c5122e3e9 100644 --- a/packages/react/src/test/testCases/customReactStyles.tsx +++ b/packages/react/src/test/testCases/customReactStyles.tsx @@ -8,6 +8,13 @@ import { uploadToTmpFilesDotOrg_DEV_ONLY, } from "@blocknote/core"; import { createReactStyleSpec } from "../../schema/ReactStyleSpec"; +import { useContent } from "../../hooks/useContent"; + +const Small = () => { + const contentProps = useContent(); + + return ; +}; const small = createReactStyleSpec( { @@ -15,23 +22,23 @@ const small = createReactStyleSpec( propSchema: "boolean", }, { - render: (props) => { - return ; - }, + render: Small, } ); +const FontSize = (props: { value: string }) => { + const { style, ...rest } = useContent(); + + return ; +}; + const fontSize = createReactStyleSpec( { type: "fontSize", propSchema: "string", }, { - render: (props) => { - return ( - - ); - }, + render: FontSize, } ); diff --git a/tests/src/utils/customblocks/ReactAlert.tsx b/tests/src/utils/customblocks/ReactAlert.tsx index 5a8752c2f..f88e2ab9c 100644 --- a/tests/src/utils/customblocks/ReactAlert.tsx +++ b/tests/src/utils/customblocks/ReactAlert.tsx @@ -1,5 +1,15 @@ -import { BlockNoteEditor, defaultProps } from "@blocknote/core"; -import { createReactBlockSpec } from "@blocknote/react"; +import { + BlockConfig, + BlockNoteEditor, + DefaultInlineContentSchema, + defaultProps, + DefaultStyleSchema, +} from "@blocknote/core"; +import { + createReactBlockSpec, + ReactCustomBlockRenderProps, + useContent, +} from "@blocknote/react"; import { useEffect, useState } from "react"; import { RiAlertFill } from "react-icons/ri"; @@ -22,97 +32,105 @@ const values = { }, } as const; -export const ReactAlert = createReactBlockSpec( - { - type: "reactAlert", - propSchema: { - textAlignment: defaultProps.textAlignment, - textColor: defaultProps.textColor, - type: { - default: "warning", - values: ["warning", "error", "info", "success"], - }, - } as const, - content: "inline", +const reactAlertConfig = { + type: "reactAlert" as const, + propSchema: { + textAlignment: defaultProps.textAlignment, + textColor: defaultProps.textColor, + type: { + default: "warning", + values: ["warning", "error", "info", "success"], + }, }, - { - render: function Render(props) { - const [type, setType] = useState(props.block.props.type); + content: "inline", +} satisfies BlockConfig; - useEffect(() => { - console.log("ReactAlert initialize"); - return () => { - console.log(" ReactAlert cleanup"); - }; - }, []); +const RenderReactAlert = ( + props: ReactCustomBlockRenderProps< + typeof reactAlertConfig, + DefaultInlineContentSchema, + DefaultStyleSchema + > +) => { + const contentProps = useContent(); - console.log("ReactAlert render"); + const [type, setType] = useState(props.block.props.type); - // Tests to see if types are correct: + useEffect(() => { + console.log("ReactAlert initialize"); + return () => { + console.log(" ReactAlert cleanup"); + }; + }, []); - const test: "reactAlert" = props.block.type; - console.log(test); + console.log("ReactAlert render"); - // @ts-expect-error - const test1: "othertype" = props.block.type; - console.log(test1); + // Tests to see if types are correct: - return ( -

-
{ - if (type === "warning") { - props.editor.updateBlock(props.block, { - props: { - type: "error", - }, - }); - setType("error"); - } else if (type === "error") { - props.editor.updateBlock(props.block, { - props: { - type: "info", - }, - }); - setType("info"); - } else if (type === "info") { - props.editor.updateBlock(props.block, { - props: { - type: "success", - }, - }); - setType("success"); - } else if (type === "success") { - props.editor.updateBlock(props.block, { - props: { - type: "warning", - }, - }); - setType("warning"); - } else { - throw new Error("Unknown alert type"); - } - }}> - {values[type as keyof typeof values].icon} -
- -
- ); - }, - } -); + const test: "reactAlert" = props.block.type; + console.log(test); + + // @ts-expect-error + const test1: "othertype" = props.block.type; + console.log(test1); + + return ( +
+
{ + if (type === "warning") { + props.editor.updateBlock(props.block, { + props: { + type: "error", + }, + }); + setType("error"); + } else if (type === "error") { + props.editor.updateBlock(props.block, { + props: { + type: "info", + }, + }); + setType("info"); + } else if (type === "info") { + props.editor.updateBlock(props.block, { + props: { + type: "success", + }, + }); + setType("success"); + } else if (type === "success") { + props.editor.updateBlock(props.block, { + props: { + type: "warning", + }, + }); + setType("warning"); + } else { + throw new Error("Unknown alert type"); + } + }}> + {values[type as keyof typeof values].icon} +
+ +
+ ); +}; + +export const ReactAlert = createReactBlockSpec(reactAlertConfig, { + render: RenderReactAlert, +}); export const insertReactAlert = { title: "Insert React Alert", onItemClick: (editor: BlockNoteEditor) => { diff --git a/tests/src/utils/customblocks/ReactImage.tsx b/tests/src/utils/customblocks/ReactImage.tsx index 410e52fd8..4c901a5d0 100644 --- a/tests/src/utils/customblocks/ReactImage.tsx +++ b/tests/src/utils/customblocks/ReactImage.tsx @@ -1,40 +1,53 @@ -import { BlockNoteEditor, defaultProps } from "@blocknote/core"; -import { createReactBlockSpec } from "@blocknote/react"; +import { + BlockConfig, + BlockNoteEditor, + DefaultInlineContentSchema, + defaultProps, + DefaultStyleSchema, +} from "@blocknote/core"; +import { + createReactBlockSpec, + ReactCustomBlockRenderProps, + useContent, +} from "@blocknote/react"; import { RiImage2Fill } from "react-icons/ri"; -export const ReactImage = createReactBlockSpec( - { - type: "reactImage", - propSchema: { - ...defaultProps, - src: { - default: "https://via.placeholder.com/1000", - }, +const reactImageConfig = { + type: "reactImage", + propSchema: { + ...defaultProps, + src: { + default: "https://via.placeholder.com/1000", }, - content: "inline", }, - { - render: ({ block, contentRef }) => { - return ( -
- {"test"} - -
- ); - }, - } -); + content: "inline", +} satisfies BlockConfig; + +const RenderReactImage = ( + props: ReactCustomBlockRenderProps< + typeof reactImageConfig, + DefaultInlineContentSchema, + DefaultStyleSchema + > +) => { + const { style, ...rest } = useContent(); + + return ( +
+ {"test"} + +
+ ); +}; + +export const ReactImage = createReactBlockSpec(reactImageConfig, { + render: RenderReactImage, +}); export const insertReactImage = { title: "Insert React Image",