diff --git a/examples/02-ui-components/custom-ui/CustomFormattingToolbar.tsx b/examples/02-ui-components/custom-ui/CustomFormattingToolbar.tsx
index 30b0423f88..6c860cbd37 100644
--- a/examples/02-ui-components/custom-ui/CustomFormattingToolbar.tsx
+++ b/examples/02-ui-components/custom-ui/CustomFormattingToolbar.tsx
@@ -1,6 +1,6 @@
import {
FormattingToolbarProps,
- useEditorContentChange,
+ useEditorChange,
useEditorSelectionChange,
} from "@blocknote/react";
import { useState } from "react";
@@ -77,8 +77,8 @@ export const CustomFormattingToolbar = (props: FormattingToolbarProps) => {
const [linkMenuOpen, setLinkMenuOpen] = useState(false);
// Updates toolbar state when the editor content or selection changes
- useEditorContentChange(props.editor, () => setState(getState()));
- useEditorSelectionChange(props.editor, () => setState(getState()));
+ useEditorChange(() => setState(getState()), props.editor);
+ useEditorSelectionChange(() => setState(getState()), props.editor);
return (
diff --git a/examples/02-ui-components/custom-ui/main.tsx b/examples/02-ui-components/custom-ui/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/02-ui-components/custom-ui/main.tsx
+++ b/examples/02-ui-components/custom-ui/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/02-ui-components/formatting-toolbar-buttons/App.tsx b/examples/02-ui-components/formatting-toolbar-buttons/App.tsx
index 380923af85..8e48bbf2d1 100644
--- a/examples/02-ui-components/formatting-toolbar-buttons/App.tsx
+++ b/examples/02-ui-components/formatting-toolbar-buttons/App.tsx
@@ -1,4 +1,3 @@
-import { BlockNoteEditor } from "@blocknote/core";
import {
BlockNoteView,
FormattingToolbarPositioner,
@@ -12,7 +11,7 @@ import { CustomFormattingToolbar } from "./CustomFormattingToolbar";
export default function App() {
// Creates a new editor instance.
- const editor: BlockNoteEditor = useBlockNote();
+ const editor = useBlockNote();
// Renders the editor instance.
return (
diff --git a/examples/02-ui-components/formatting-toolbar-buttons/CustomButton.tsx b/examples/02-ui-components/formatting-toolbar-buttons/CustomButton.tsx
index 01d18ea0f1..e9e6b16dcd 100644
--- a/examples/02-ui-components/formatting-toolbar-buttons/CustomButton.tsx
+++ b/examples/02-ui-components/formatting-toolbar-buttons/CustomButton.tsx
@@ -1,10 +1,10 @@
-import { useState } from "react";
import { BlockNoteEditor } from "@blocknote/core";
import {
ToolbarButton,
- useEditorContentChange,
+ useEditorChange,
useEditorSelectionChange,
} from "@blocknote/react";
+import { useState } from "react";
export const CustomButton = (props: { editor: BlockNoteEditor }) => {
// Tracks whether the text & background are both blue.
@@ -14,20 +14,20 @@ export const CustomButton = (props: { editor: BlockNoteEditor }) => {
);
// Updates state on content change.
- useEditorContentChange(props.editor, () => {
+ useEditorChange(() => {
setIsSelected(
props.editor.getActiveStyles().textColor === "blue" &&
props.editor.getActiveStyles().backgroundColor === "blue"
);
- });
+ }, props.editor);
// Updates state on selection change.
- useEditorSelectionChange(props.editor, () => {
+ useEditorSelectionChange(() => {
setIsSelected(
props.editor.getActiveStyles().textColor === "blue" &&
props.editor.getActiveStyles().backgroundColor === "blue"
);
- });
+ }, props.editor);
return (
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/02-ui-components/side-menu-buttons/App.tsx b/examples/02-ui-components/side-menu-buttons/App.tsx
index 76c1d6f459..e7789d6a55 100644
--- a/examples/02-ui-components/side-menu-buttons/App.tsx
+++ b/examples/02-ui-components/side-menu-buttons/App.tsx
@@ -16,7 +16,7 @@ export default function App() {
// Renders the editor instance.
return (
-
+
diff --git a/examples/02-ui-components/side-menu-buttons/main.tsx b/examples/02-ui-components/side-menu-buttons/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/02-ui-components/side-menu-buttons/main.tsx
+++ b/examples/02-ui-components/side-menu-buttons/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/02-ui-components/side-menu-drag-handle-items/App.tsx b/examples/02-ui-components/side-menu-drag-handle-items/App.tsx
index dd675ef056..ef6009df4d 100644
--- a/examples/02-ui-components/side-menu-drag-handle-items/App.tsx
+++ b/examples/02-ui-components/side-menu-drag-handle-items/App.tsx
@@ -17,7 +17,7 @@ export default function App() {
// Renders the editor instance.
return (
-
+
diff --git a/examples/02-ui-components/side-menu-drag-handle-items/main.tsx b/examples/02-ui-components/side-menu-drag-handle-items/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/02-ui-components/side-menu-drag-handle-items/main.tsx
+++ b/examples/02-ui-components/side-menu-drag-handle-items/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/02-ui-components/slash-menu-items/main.tsx b/examples/02-ui-components/slash-menu-items/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/02-ui-components/slash-menu-items/main.tsx
+++ b/examples/02-ui-components/slash-menu-items/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/02-ui-components/ui-elements-remove/App.tsx b/examples/02-ui-components/ui-elements-remove/App.tsx
index b8f8c4ee9b..6c8ff5de49 100644
--- a/examples/02-ui-components/ui-elements-remove/App.tsx
+++ b/examples/02-ui-components/ui-elements-remove/App.tsx
@@ -14,7 +14,7 @@ export default function App() {
// Renders the editor instance.
return (
-
+
diff --git a/examples/02-ui-components/ui-elements-remove/main.tsx b/examples/02-ui-components/ui-elements-remove/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/02-ui-components/ui-elements-remove/main.tsx
+++ b/examples/02-ui-components/ui-elements-remove/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/02-ui-components/ui-elements-replace/App.tsx b/examples/02-ui-components/ui-elements-replace/App.tsx
index 3c61df749e..82dc3d4845 100644
--- a/examples/02-ui-components/ui-elements-replace/App.tsx
+++ b/examples/02-ui-components/ui-elements-replace/App.tsx
@@ -15,7 +15,7 @@ export default function App() {
// Renders the editor instance.
return (
-
+
diff --git a/examples/02-ui-components/ui-elements-replace/main.tsx b/examples/02-ui-components/ui-elements-replace/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/02-ui-components/ui-elements-replace/main.tsx
+++ b/examples/02-ui-components/ui-elements-replace/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/04-theming/changing-font/App.tsx b/examples/04-theming/changing-font/App.tsx
index c8931f9a58..249f964521 100644
--- a/examples/04-theming/changing-font/App.tsx
+++ b/examples/04-theming/changing-font/App.tsx
@@ -1,10 +1,9 @@
-import { BlockNoteEditor } from "@blocknote/core";
import { BlockNoteView, useBlockNote } from "@blocknote/react";
import "@blocknote/react/style.css";
export default function App() {
// Creates a new editor instance.
- const editor: BlockNoteEditor = useBlockNote();
+ const editor = useBlockNote();
// Renders the editor instance using a React component.
return ;
diff --git a/examples/04-theming/changing-font/main.tsx b/examples/04-theming/changing-font/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/04-theming/changing-font/main.tsx
+++ b/examples/04-theming/changing-font/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/04-theming/theming-css-variables-code/main.tsx b/examples/04-theming/theming-css-variables-code/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/04-theming/theming-css-variables-code/main.tsx
+++ b/examples/04-theming/theming-css-variables-code/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/04-theming/theming-css-variables/main.tsx b/examples/04-theming/theming-css-variables/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/04-theming/theming-css-variables/main.tsx
+++ b/examples/04-theming/theming-css-variables/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/04-theming/theming-css/main.tsx b/examples/04-theming/theming-css/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/04-theming/theming-css/main.tsx
+++ b/examples/04-theming/theming-css/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/04-theming/theming-dom-attributes/main.tsx b/examples/04-theming/theming-dom-attributes/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/04-theming/theming-dom-attributes/main.tsx
+++ b/examples/04-theming/theming-dom-attributes/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/05-cursor-selections/selection-blocks/App.tsx b/examples/05-cursor-selections/selection-blocks/App.tsx
index 00bbac52fe..cebb15dc61 100644
--- a/examples/05-cursor-selections/selection-blocks/App.tsx
+++ b/examples/05-cursor-selections/selection-blocks/App.tsx
@@ -1,54 +1,56 @@
import { BlockNoteView, useBlockNote } from "@blocknote/react";
import "@blocknote/react/style.css";
+import { useCallback } from "react";
export default function App() {
// Creates a new editor instance.
- const editor = useBlockNote({
- // Listens for when the text cursor position changes.
- onTextCursorPositionChange: (editor) => {
- // Gets the blocks currently spanned by the selection.
- const selectedBlocks = editor.getSelection()?.blocks;
- // Converts array of blocks to set of block IDs for more efficient comparison.
- const selectedBlockIds = new Set(
- selectedBlocks?.map((block) => block.id) || []
- );
+ const editor = useBlockNote({});
- // Traverses all blocks.
- editor.forEachBlock((block) => {
- // If no selection is active, resets the background color of each block.
- if (selectedBlockIds.size === 0) {
- editor.updateBlock(block, {
- props: { backgroundColor: "default" },
- });
+ const onSelectionChange = useCallback(() => {
+ // Gets the blocks currently spanned by the selection.
+ const selectedBlocks = editor.getSelection()?.blocks;
+ // Converts array of blocks to set of block IDs for more efficient comparison.
+ const selectedBlockIds = new Set(
+ selectedBlocks?.map((block) => block.id) || []
+ );
- return true;
- }
-
- if (
- selectedBlockIds.has(block.id) &&
- block.props.backgroundColor !== "blue"
- ) {
- // If the block is currently spanned by the selection, makes its
- // background blue if it isn't already.
- editor.updateBlock(block, {
- props: { backgroundColor: "blue" },
- });
- } else if (
- !selectedBlockIds.has(block.id) &&
- block.props.backgroundColor === "blue"
- ) {
- // If the block is not currently spanned by the selection, resets
- // its background if it's blue.
- editor.updateBlock(block, {
- props: { backgroundColor: "default" },
- });
- }
+ // Traverses all blocks.
+ editor.forEachBlock((block) => {
+ // If no selection is active, resets the background color of each block.
+ if (selectedBlockIds.size === 0) {
+ editor.updateBlock(block, {
+ props: { backgroundColor: "default" },
+ });
return true;
- });
- },
- });
+ }
+
+ if (
+ selectedBlockIds.has(block.id) &&
+ block.props.backgroundColor !== "blue"
+ ) {
+ // If the block is currently spanned by the selection, makes its
+ // background blue if it isn't already.
+ editor.updateBlock(block, {
+ props: { backgroundColor: "blue" },
+ });
+ } else if (
+ !selectedBlockIds.has(block.id) &&
+ block.props.backgroundColor === "blue"
+ ) {
+ // If the block is not currently spanned by the selection, resets
+ // its background if it's blue.
+ editor.updateBlock(block, {
+ props: { backgroundColor: "default" },
+ });
+ }
+
+ return true;
+ });
+ }, [editor]);
// Renders the editor instance.
- return ;
+ return (
+
+ );
}
diff --git a/examples/05-cursor-selections/selection-blocks/README.md b/examples/05-cursor-selections/selection-blocks/README.md
index f0ce29294b..9f1c7c719c 100644
--- a/examples/05-cursor-selections/selection-blocks/README.md
+++ b/examples/05-cursor-selections/selection-blocks/README.md
@@ -1 +1,3 @@
-# Highlighting Blocks in Selection
\ No newline at end of file
+# Highlighting Blocks in Selection
+
+TODO: same as text-cursor block. Perhaps replace both by 1 single example
diff --git a/examples/05-cursor-selections/selection-blocks/main.tsx b/examples/05-cursor-selections/selection-blocks/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/05-cursor-selections/selection-blocks/main.tsx
+++ b/examples/05-cursor-selections/selection-blocks/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/05-cursor-selections/text-cursor-block/App.tsx b/examples/05-cursor-selections/text-cursor-block/App.tsx
index db526865fd..cf76d03ae7 100644
--- a/examples/05-cursor-selections/text-cursor-block/App.tsx
+++ b/examples/05-cursor-selections/text-cursor-block/App.tsx
@@ -1,41 +1,45 @@
import { BlockNoteView, useBlockNote } from "@blocknote/react";
import "@blocknote/react/style.css";
+import "@blocknote/react/style.css";
+import { useCallback } from "react";
+
export default function App() {
// Creates a new editor instance.
- const editor = useBlockNote({
- // Listens for when the text cursor position changes.
- onTextCursorPositionChange: (editor) => {
- // Gets the block currently hovered by the text cursor.
- const hoveredBlock = editor.getTextCursorPosition().block;
+ const editor = useBlockNote({});
+
+ const onSelectionChange = useCallback(() => {
+ // Gets the block currently hovered by the text cursor.
+ const hoveredBlock = editor.getTextCursorPosition().block;
- // Traverses all blocks.
- editor.forEachBlock((block) => {
- if (
- block.id === hoveredBlock.id &&
- block.props.backgroundColor !== "blue"
- ) {
- // If the block is currently hovered by the text cursor, makes its
- // background blue if it isn't already.
- editor.updateBlock(block, {
- props: { backgroundColor: "blue" },
- });
- } else if (
- block.id !== hoveredBlock.id &&
- block.props.backgroundColor === "blue"
- ) {
- // If the block is not currently hovered by the text cursor, resets
- // its background if it's blue.
- editor.updateBlock(block, {
- props: { backgroundColor: "default" },
- });
- }
+ // Traverses all blocks.
+ editor.forEachBlock((block) => {
+ if (
+ block.id === hoveredBlock.id &&
+ block.props.backgroundColor !== "blue"
+ ) {
+ // If the block is currently hovered by the text cursor, makes its
+ // background blue if it isn't already.
+ editor.updateBlock(block, {
+ props: { backgroundColor: "blue" },
+ });
+ } else if (
+ block.id !== hoveredBlock.id &&
+ block.props.backgroundColor === "blue"
+ ) {
+ // If the block is not currently hovered by the text cursor, resets
+ // its background if it's blue.
+ editor.updateBlock(block, {
+ props: { backgroundColor: "default" },
+ });
+ }
- return true;
- });
- },
- });
+ return true;
+ });
+ }, [editor]);
// Renders the editor instance.
- return ;
+ return (
+
+ );
}
diff --git a/examples/05-cursor-selections/text-cursor-block/README.md b/examples/05-cursor-selections/text-cursor-block/README.md
index dcef483c5f..51bf7844d7 100644
--- a/examples/05-cursor-selections/text-cursor-block/README.md
+++ b/examples/05-cursor-selections/text-cursor-block/README.md
@@ -1 +1,5 @@
-# Highlighting Block with the Text Cursor
\ No newline at end of file
+# Highlighting Block with the Text Cursor
+
+TODO: remove. I don't really see a scenario where this example makes sense in an application (selection related info should never be stored in the document; as this would also be saved in database, multiplayer, etc.)
+
+Let's replace with an example similar to "basic/block-objects" that just outputs the relevant info
diff --git a/examples/05-cursor-selections/text-cursor-block/main.tsx b/examples/05-cursor-selections/text-cursor-block/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/05-cursor-selections/text-cursor-block/main.tsx
+++ b/examples/05-cursor-selections/text-cursor-block/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/06-custom-schema/alert-block/main.tsx b/examples/06-custom-schema/alert-block/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/06-custom-schema/alert-block/main.tsx
+++ b/examples/06-custom-schema/alert-block/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/06-custom-schema/react-custom-blocks/App.tsx b/examples/06-custom-schema/react-custom-blocks/App.tsx
index 75f0a6e62a..da714765d7 100644
--- a/examples/06-custom-schema/react-custom-blocks/App.tsx
+++ b/examples/06-custom-schema/react-custom-blocks/App.tsx
@@ -117,12 +117,6 @@ export const bracketsParagraphBlock = createReactBlockSpec(
export default function App() {
const editor = useBlockNote({
- domAttributes: {
- editor: {
- class: "editor",
- "data-test": "editor",
- },
- },
blockSpecs: {
...defaultBlockSpecs,
alert: alertBlock,
@@ -150,5 +144,5 @@ export default function App() {
],
});
- return ;
+ return ;
}
diff --git a/examples/06-custom-schema/react-custom-blocks/main.tsx b/examples/06-custom-schema/react-custom-blocks/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/06-custom-schema/react-custom-blocks/main.tsx
+++ b/examples/06-custom-schema/react-custom-blocks/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/06-custom-schema/react-custom-inline-content/App.tsx b/examples/06-custom-schema/react-custom-inline-content/App.tsx
index a98d862c0d..ff053c77cf 100644
--- a/examples/06-custom-schema/react-custom-inline-content/App.tsx
+++ b/examples/06-custom-schema/react-custom-inline-content/App.tsx
@@ -47,12 +47,6 @@ export default function ReactInlineContent() {
tag,
...defaultInlineContentSpecs,
},
- domAttributes: {
- editor: {
- class: "editor",
- "data-test": "editor",
- },
- },
initialContent: [
{
type: "paragraph",
@@ -80,5 +74,5 @@ export default function ReactInlineContent() {
],
});
- return ;
+ return ;
}
diff --git a/examples/06-custom-schema/react-custom-inline-content/main.tsx b/examples/06-custom-schema/react-custom-inline-content/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/06-custom-schema/react-custom-inline-content/main.tsx
+++ b/examples/06-custom-schema/react-custom-inline-content/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/06-custom-schema/react-custom-styles/App.tsx b/examples/06-custom-schema/react-custom-styles/App.tsx
index 67366ef9c1..3c4a13c0d3 100644
--- a/examples/06-custom-schema/react-custom-styles/App.tsx
+++ b/examples/06-custom-schema/react-custom-styles/App.tsx
@@ -87,15 +87,6 @@ export default function App() {
const editor = useBlockNote(
{
styleSpecs: customReactStyles,
- onEditorContentChange: (editor) => {
- console.log(editor.topLevelBlocks);
- },
- domAttributes: {
- editor: {
- class: "editor",
- "data-test": "editor",
- },
- },
initialContent: [
{
type: "paragraph",
@@ -122,7 +113,7 @@ export default function App() {
);
return (
-
+
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/07-collaboration/partykit/App.tsx b/examples/07-collaboration/partykit/App.tsx
index 6a5217a5da..81a4f2a527 100644
--- a/examples/07-collaboration/partykit/App.tsx
+++ b/examples/07-collaboration/partykit/App.tsx
@@ -1,4 +1,3 @@
-import { uploadToTmpFilesDotOrg_DEV_ONLY } from "@blocknote/core";
import { BlockNoteView, useBlockNote } from "@blocknote/react";
import "@blocknote/react/style.css";
@@ -16,12 +15,6 @@ const provider = new YPartyKitProvider(
export default function App() {
const editor = useBlockNote({
- domAttributes: {
- editor: {
- class: "editor",
- "data-test": "editor",
- },
- },
collaboration: {
// The Yjs Provider responsible for transporting updates:
provider,
@@ -33,8 +26,7 @@ export default function App() {
color: "#ff0000",
},
},
- uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY,
});
- return ;
+ return ;
}
diff --git a/examples/07-collaboration/partykit/main.tsx b/examples/07-collaboration/partykit/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/07-collaboration/partykit/main.tsx
+++ b/examples/07-collaboration/partykit/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/08-interoperability/01-converting-blocks-to-html/.bnexample.json b/examples/08-interoperability/01-converting-blocks-to-html/.bnexample.json
new file mode 100644
index 0000000000..0993be7115
--- /dev/null
+++ b/examples/08-interoperability/01-converting-blocks-to-html/.bnexample.json
@@ -0,0 +1,4 @@
+{
+ "playground": true,
+ "docs": true
+}
diff --git a/examples/08-interoperability/01-converting-blocks-to-html/App.tsx b/examples/08-interoperability/01-converting-blocks-to-html/App.tsx
new file mode 100644
index 0000000000..6a61fc2a75
--- /dev/null
+++ b/examples/08-interoperability/01-converting-blocks-to-html/App.tsx
@@ -0,0 +1,42 @@
+import { BlockNoteView, useBlockNote } from "@blocknote/react";
+import "@blocknote/react/style.css";
+import { useState } from "react";
+
+// TODO: better design?
+export default function App() {
+ // Stores the editor's contents as HTML.
+ const [html, setHTML] = useState("");
+
+ // Creates a new editor instance with some initial content.
+ const editor = useBlockNote({
+ initialContent: [
+ {
+ type: "paragraph",
+ content: [
+ "Hello, ",
+ {
+ type: "text",
+ text: "world!",
+ styles: {
+ bold: true,
+ },
+ },
+ ],
+ },
+ ],
+ });
+
+ const onChange = async () => {
+ // Converts the editor's contents from Block objects to HTML and store to state.
+ const html = await editor.blocksToHTMLLossy(editor.topLevelBlocks);
+ setHTML(html);
+ };
+
+ // Renders the editor instance, and its contents as HTML below.
+ return (
+
+ );
+}
diff --git a/examples/08-interoperability/01-converting-blocks-to-html/README.md b/examples/08-interoperability/01-converting-blocks-to-html/README.md
new file mode 100644
index 0000000000..75da7363eb
--- /dev/null
+++ b/examples/08-interoperability/01-converting-blocks-to-html/README.md
@@ -0,0 +1,9 @@
+# Converting Blocks to HTML
+
+This example exports the current document (all blocks) as HTML and displays it below the editor.
+
+**Try it out:** Edit the document to see the HTML representation.
+
+**Relevant Docs:**
+
+TODO
diff --git a/examples/08-interoperability/converting-blocks-to-html/index.html b/examples/08-interoperability/01-converting-blocks-to-html/index.html
similarity index 100%
rename from examples/08-interoperability/converting-blocks-to-html/index.html
rename to examples/08-interoperability/01-converting-blocks-to-html/index.html
diff --git a/examples/08-interoperability/01-converting-blocks-to-html/main.tsx b/examples/08-interoperability/01-converting-blocks-to-html/main.tsx
new file mode 100644
index 0000000000..f88b490fbd
--- /dev/null
+++ b/examples/08-interoperability/01-converting-blocks-to-html/main.tsx
@@ -0,0 +1,11 @@
+// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
+import React from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App";
+
+const root = createRoot(document.getElementById("root")!);
+root.render(
+
+
+
+);
diff --git a/examples/08-interoperability/converting-blocks-to-html/package.json b/examples/08-interoperability/01-converting-blocks-to-html/package.json
similarity index 100%
rename from examples/08-interoperability/converting-blocks-to-html/package.json
rename to examples/08-interoperability/01-converting-blocks-to-html/package.json
diff --git a/examples/08-interoperability/converting-blocks-from-md/tsconfig.json b/examples/08-interoperability/01-converting-blocks-to-html/tsconfig.json
similarity index 100%
rename from examples/08-interoperability/converting-blocks-from-md/tsconfig.json
rename to examples/08-interoperability/01-converting-blocks-to-html/tsconfig.json
diff --git a/examples/08-interoperability/converting-blocks-from-md/vite.config.ts b/examples/08-interoperability/01-converting-blocks-to-html/vite.config.ts
similarity index 100%
rename from examples/08-interoperability/converting-blocks-from-md/vite.config.ts
rename to examples/08-interoperability/01-converting-blocks-to-html/vite.config.ts
diff --git a/examples/08-interoperability/02-converting-blocks-from-html/.bnexample.json b/examples/08-interoperability/02-converting-blocks-from-html/.bnexample.json
new file mode 100644
index 0000000000..0993be7115
--- /dev/null
+++ b/examples/08-interoperability/02-converting-blocks-from-html/.bnexample.json
@@ -0,0 +1,4 @@
+{
+ "playground": true,
+ "docs": true
+}
diff --git a/examples/08-interoperability/02-converting-blocks-from-html/App.tsx b/examples/08-interoperability/02-converting-blocks-from-html/App.tsx
new file mode 100644
index 0000000000..712eeea796
--- /dev/null
+++ b/examples/08-interoperability/02-converting-blocks-from-html/App.tsx
@@ -0,0 +1,39 @@
+import { BlockNoteView, useBlockNote } from "@blocknote/react";
+import "@blocknote/react/style.css";
+import { ChangeEvent, useCallback, useEffect } from "react";
+
+const initialHTML = "Hello, world!
";
+
+// TODO: better design?
+export default function App() {
+ // Creates a new editor instance.
+ const editor = useBlockNote();
+
+ const htmlInputChanged = useCallback(
+ async (e: ChangeEvent) => {
+ // Whenever the current HTML content changes, converts it to an array of
+ // Block objects and replaces the editor's content with them.
+ const blocks = await editor.tryParseHTMLToBlocks(e.target.value);
+ editor.replaceBlocks(editor.topLevelBlocks, blocks);
+ },
+ [editor]
+ );
+
+ // For initialization; on mount, convert the initial HTML to blocks and replace the default editor's content
+ useEffect(() => {
+ async function loadInitialHTML() {
+ const blocks = await editor.tryParseHTMLToBlocks(initialHTML);
+ editor.replaceBlocks(editor.topLevelBlocks, blocks);
+ }
+ loadInitialHTML();
+ }, [editor]);
+
+ // Renders a text area for you to write/paste HTML in, and the editor instance
+ // below, which displays the current HTML as blocks.
+ return (
+
+
+
+
+ );
+}
diff --git a/examples/08-interoperability/02-converting-blocks-from-html/README.md b/examples/08-interoperability/02-converting-blocks-from-html/README.md
new file mode 100644
index 0000000000..7265bc4890
--- /dev/null
+++ b/examples/08-interoperability/02-converting-blocks-from-html/README.md
@@ -0,0 +1,11 @@
+# Parsing HTML to Blocks
+
+This example shows how you can convert HTML content to a BlockNote document.
+
+Note that the editor itself is locked for editing by setting `editable` to `false`.
+
+**Try it out:** Edit the HTML in the textarea to see the BlockNote document update.
+
+**Relevant Docs:**
+
+TODO
diff --git a/examples/08-interoperability/converting-blocks-from-md/index.html b/examples/08-interoperability/02-converting-blocks-from-html/index.html
similarity index 87%
rename from examples/08-interoperability/converting-blocks-from-md/index.html
rename to examples/08-interoperability/02-converting-blocks-from-html/index.html
index 7ea3b342a9..1dfff3bae9 100644
--- a/examples/08-interoperability/converting-blocks-from-md/index.html
+++ b/examples/08-interoperability/02-converting-blocks-from-html/index.html
@@ -5,7 +5,7 @@
- Converting Markdown to Blocks
+ Parsing HTML to Blocks
diff --git a/examples/08-interoperability/02-converting-blocks-from-html/main.tsx b/examples/08-interoperability/02-converting-blocks-from-html/main.tsx
new file mode 100644
index 0000000000..f88b490fbd
--- /dev/null
+++ b/examples/08-interoperability/02-converting-blocks-from-html/main.tsx
@@ -0,0 +1,11 @@
+// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
+import React from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App";
+
+const root = createRoot(document.getElementById("root")!);
+root.render(
+
+
+
+);
diff --git a/examples/08-interoperability/converting-blocks-from-html/package.json b/examples/08-interoperability/02-converting-blocks-from-html/package.json
similarity index 100%
rename from examples/08-interoperability/converting-blocks-from-html/package.json
rename to examples/08-interoperability/02-converting-blocks-from-html/package.json
diff --git a/examples/08-interoperability/converting-blocks-to-html/tsconfig.json b/examples/08-interoperability/02-converting-blocks-from-html/tsconfig.json
similarity index 100%
rename from examples/08-interoperability/converting-blocks-to-html/tsconfig.json
rename to examples/08-interoperability/02-converting-blocks-from-html/tsconfig.json
diff --git a/examples/08-interoperability/converting-blocks-to-html/vite.config.ts b/examples/08-interoperability/02-converting-blocks-from-html/vite.config.ts
similarity index 100%
rename from examples/08-interoperability/converting-blocks-to-html/vite.config.ts
rename to examples/08-interoperability/02-converting-blocks-from-html/vite.config.ts
diff --git a/examples/08-interoperability/03-converting-blocks-to-md/.bnexample.json b/examples/08-interoperability/03-converting-blocks-to-md/.bnexample.json
new file mode 100644
index 0000000000..0993be7115
--- /dev/null
+++ b/examples/08-interoperability/03-converting-blocks-to-md/.bnexample.json
@@ -0,0 +1,4 @@
+{
+ "playground": true,
+ "docs": true
+}
diff --git a/examples/08-interoperability/03-converting-blocks-to-md/App.tsx b/examples/08-interoperability/03-converting-blocks-to-md/App.tsx
new file mode 100644
index 0000000000..21e28001d6
--- /dev/null
+++ b/examples/08-interoperability/03-converting-blocks-to-md/App.tsx
@@ -0,0 +1,42 @@
+import { BlockNoteView, useBlockNote } from "@blocknote/react";
+import "@blocknote/react/style.css";
+import { useState } from "react";
+
+// TODO: better design?
+export default function App() {
+ // Stores the editor's contents as Markdown.
+ const [markdown, setMarkdown] = useState("");
+
+ // Creates a new editor instance with some initial content.
+ const editor = useBlockNote({
+ initialContent: [
+ {
+ type: "paragraph",
+ content: [
+ "Hello, ",
+ {
+ type: "text",
+ text: "world!",
+ styles: {
+ bold: true,
+ },
+ },
+ ],
+ },
+ ],
+ });
+
+ const onChange = async () => {
+ // Converts the editor's contents from Block objects to Markdown and store to state.
+ const markdown = await editor.blocksToMarkdownLossy(editor.topLevelBlocks);
+ setMarkdown(markdown);
+ };
+
+ // Renders the editor instance, and its contents as Markdown below.
+ return (
+
+ );
+}
diff --git a/examples/08-interoperability/03-converting-blocks-to-md/README.md b/examples/08-interoperability/03-converting-blocks-to-md/README.md
new file mode 100644
index 0000000000..00d86e3096
--- /dev/null
+++ b/examples/08-interoperability/03-converting-blocks-to-md/README.md
@@ -0,0 +1,9 @@
+# Converting Blocks to Markdown
+
+This example exports the current document (all blocks) as Markdown and displays it below the editor.
+
+**Try it out:** Edit the document to see the Markdown representation.
+
+**Relevant Docs:**
+
+TODO
diff --git a/examples/08-interoperability/converting-blocks-to-md/index.html b/examples/08-interoperability/03-converting-blocks-to-md/index.html
similarity index 100%
rename from examples/08-interoperability/converting-blocks-to-md/index.html
rename to examples/08-interoperability/03-converting-blocks-to-md/index.html
diff --git a/examples/08-interoperability/03-converting-blocks-to-md/main.tsx b/examples/08-interoperability/03-converting-blocks-to-md/main.tsx
new file mode 100644
index 0000000000..f88b490fbd
--- /dev/null
+++ b/examples/08-interoperability/03-converting-blocks-to-md/main.tsx
@@ -0,0 +1,11 @@
+// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
+import React from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App";
+
+const root = createRoot(document.getElementById("root")!);
+root.render(
+
+
+
+);
diff --git a/examples/08-interoperability/converting-blocks-to-md/package.json b/examples/08-interoperability/03-converting-blocks-to-md/package.json
similarity index 100%
rename from examples/08-interoperability/converting-blocks-to-md/package.json
rename to examples/08-interoperability/03-converting-blocks-to-md/package.json
diff --git a/examples/08-interoperability/converting-blocks-to-md/tsconfig.json b/examples/08-interoperability/03-converting-blocks-to-md/tsconfig.json
similarity index 100%
rename from examples/08-interoperability/converting-blocks-to-md/tsconfig.json
rename to examples/08-interoperability/03-converting-blocks-to-md/tsconfig.json
diff --git a/examples/08-interoperability/converting-blocks-to-md/vite.config.ts b/examples/08-interoperability/03-converting-blocks-to-md/vite.config.ts
similarity index 100%
rename from examples/08-interoperability/converting-blocks-to-md/vite.config.ts
rename to examples/08-interoperability/03-converting-blocks-to-md/vite.config.ts
diff --git a/examples/08-interoperability/04-converting-blocks-from-md/.bnexample.json b/examples/08-interoperability/04-converting-blocks-from-md/.bnexample.json
new file mode 100644
index 0000000000..0993be7115
--- /dev/null
+++ b/examples/08-interoperability/04-converting-blocks-from-md/.bnexample.json
@@ -0,0 +1,4 @@
+{
+ "playground": true,
+ "docs": true
+}
diff --git a/examples/08-interoperability/04-converting-blocks-from-md/App.tsx b/examples/08-interoperability/04-converting-blocks-from-md/App.tsx
new file mode 100644
index 0000000000..fb0c3a6df9
--- /dev/null
+++ b/examples/08-interoperability/04-converting-blocks-from-md/App.tsx
@@ -0,0 +1,42 @@
+import { BlockNoteView, useBlockNote } from "@blocknote/react";
+import "@blocknote/react/style.css";
+import { ChangeEvent, useCallback, useEffect } from "react";
+
+// TODO: better design?
+const initialMarkdown = "Hello, **world!**";
+
+export default function App() {
+ // Creates a new editor instance.
+ const editor = useBlockNote();
+
+ const markdownInputChanged = useCallback(
+ async (e: ChangeEvent) => {
+ // Whenever the current Markdown content changes, converts it to an array of
+ // Block objects and replaces the editor's content with them.
+ const blocks = await editor.tryParseMarkdownToBlocks(e.target.value);
+ editor.replaceBlocks(editor.topLevelBlocks, blocks);
+ },
+ [editor]
+ );
+
+ // For initialization; on mount, convert the initial Markdown to blocks and replace the default editor's content
+ useEffect(() => {
+ async function loadInitialHTML() {
+ const blocks = await editor.tryParseMarkdownToBlocks(initialMarkdown);
+ editor.replaceBlocks(editor.topLevelBlocks, blocks);
+ }
+ loadInitialHTML();
+ }, [editor]);
+
+ // Renders a text area for you to write/paste Markdown in, and the editor instance
+ // below, which displays the current Markdown as blocks.
+ return (
+
+
+
+
+ );
+}
diff --git a/examples/08-interoperability/04-converting-blocks-from-md/README.md b/examples/08-interoperability/04-converting-blocks-from-md/README.md
new file mode 100644
index 0000000000..2ac78242eb
--- /dev/null
+++ b/examples/08-interoperability/04-converting-blocks-from-md/README.md
@@ -0,0 +1,11 @@
+# Parsing Markdown to Blocks
+
+This example shows how you can convert HTML content to a BlockNote document.
+
+Note that the editor itself is locked for editing by setting `editable` to `false`.
+
+**Try it out:** Edit the Markdown in the textarea to see the BlockNote document update.
+
+**Relevant Docs:**
+
+TODO
diff --git a/examples/08-interoperability/04-converting-blocks-from-md/index.html b/examples/08-interoperability/04-converting-blocks-from-md/index.html
new file mode 100644
index 0000000000..ba17f723a6
--- /dev/null
+++ b/examples/08-interoperability/04-converting-blocks-from-md/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ Parsing Markdown to Blocks
+
+
+
+
+
+
diff --git a/examples/08-interoperability/04-converting-blocks-from-md/main.tsx b/examples/08-interoperability/04-converting-blocks-from-md/main.tsx
new file mode 100644
index 0000000000..f88b490fbd
--- /dev/null
+++ b/examples/08-interoperability/04-converting-blocks-from-md/main.tsx
@@ -0,0 +1,11 @@
+// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
+import React from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App";
+
+const root = createRoot(document.getElementById("root")!);
+root.render(
+
+
+
+);
diff --git a/examples/08-interoperability/converting-blocks-from-md/package.json b/examples/08-interoperability/04-converting-blocks-from-md/package.json
similarity index 100%
rename from examples/08-interoperability/converting-blocks-from-md/package.json
rename to examples/08-interoperability/04-converting-blocks-from-md/package.json
diff --git a/examples/08-interoperability/04-converting-blocks-from-md/tsconfig.json b/examples/08-interoperability/04-converting-blocks-from-md/tsconfig.json
new file mode 100644
index 0000000000..bb6637c459
--- /dev/null
+++ b/examples/08-interoperability/04-converting-blocks-from-md/tsconfig.json
@@ -0,0 +1,36 @@
+{
+ "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": [
+ "DOM",
+ "DOM.Iterable",
+ "ESNext"
+ ],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "composite": true
+ },
+ "include": [
+ "."
+ ],
+ "references": [
+ {
+ "path": "../../../packages/core/"
+ },
+ {
+ "path": "../../../packages/react/"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/examples/08-interoperability/04-converting-blocks-from-md/vite.config.ts b/examples/08-interoperability/04-converting-blocks-from-md/vite.config.ts
new file mode 100644
index 0000000000..f62ab20bc2
--- /dev/null
+++ b/examples/08-interoperability/04-converting-blocks-from-md/vite.config.ts
@@ -0,0 +1,32 @@
+// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
+import react from "@vitejs/plugin-react";
+import * as fs from "fs";
+import * as path from "path";
+import { defineConfig } from "vite";
+// import eslintPlugin from "vite-plugin-eslint";
+// https://vitejs.dev/config/
+export default defineConfig((conf) => ({
+ plugins: [react()],
+ optimizeDeps: {},
+ build: {
+ sourcemap: true,
+ },
+ resolve: {
+ alias:
+ conf.command === "build" ||
+ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src"))
+ ? {}
+ : ({
+ // Comment out the lines below to load a built version of blocknote
+ // or, keep as is to load live from sources with live reload working
+ "@blocknote/core": path.resolve(
+ __dirname,
+ "../../packages/core/src/"
+ ),
+ "@blocknote/react": path.resolve(
+ __dirname,
+ "../../packages/react/src/"
+ ),
+ } as any),
+ },
+}));
diff --git a/examples/08-interoperability/converting-blocks-from-html/.bnexample.json b/examples/08-interoperability/converting-blocks-from-html/.bnexample.json
deleted file mode 100644
index 178fd44ce0..0000000000
--- a/examples/08-interoperability/converting-blocks-from-html/.bnexample.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "playground": true,
- "docs": false,
- "group": "Basic Examples",
- "order": 1
-}
diff --git a/examples/08-interoperability/converting-blocks-from-html/App.tsx b/examples/08-interoperability/converting-blocks-from-html/App.tsx
deleted file mode 100644
index d00dce62b1..0000000000
--- a/examples/08-interoperability/converting-blocks-from-html/App.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { useEffect, useState } from "react";
-import { BlockNoteView, useBlockNote } from "@blocknote/react";
-import "@blocknote/react/style.css";
-
-export default function App() {
- // Stores the current HTML content.
- const [html, setHTML] = useState("");
-
- // Creates a new editor instance.
- const editor = useBlockNote({
- // Makes the editor non-editable.
- editable: false,
- });
-
- useEffect(() => {
- if (editor) {
- // Whenever the current HTML content changes, converts it to an array of
- // Block objects and replaces the editor's content with them.
- const getBlocks = async () => {
- const blocks = await editor.tryParseHTMLToBlocks(html);
- editor.replaceBlocks(editor.topLevelBlocks, blocks);
- };
- getBlocks();
- }
- }, [editor, html]);
-
- // Renders a text area for you to write/paste HTML in, and the editor instance
- // below, which displays the current HTML as blocks.
- return (
-
-
- );
-}
diff --git a/examples/08-interoperability/converting-blocks-from-html/README.md b/examples/08-interoperability/converting-blocks-from-html/README.md
deleted file mode 100644
index d40e874ab8..0000000000
--- a/examples/08-interoperability/converting-blocks-from-html/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Converting HTML to Blocks
\ No newline at end of file
diff --git a/examples/08-interoperability/converting-blocks-from-html/main.tsx b/examples/08-interoperability/converting-blocks-from-html/main.tsx
deleted file mode 100644
index 8327213ef0..0000000000
--- a/examples/08-interoperability/converting-blocks-from-html/main.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
-import React from "react";
-import { createRoot } from "react-dom/client";
-import App from "./App";
-
-const root = createRoot(document.getElementById("root")!);
-root.render(
- //
-
- //
-);
\ No newline at end of file
diff --git a/examples/08-interoperability/converting-blocks-from-md/.bnexample.json b/examples/08-interoperability/converting-blocks-from-md/.bnexample.json
deleted file mode 100644
index 178fd44ce0..0000000000
--- a/examples/08-interoperability/converting-blocks-from-md/.bnexample.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "playground": true,
- "docs": false,
- "group": "Basic Examples",
- "order": 1
-}
diff --git a/examples/08-interoperability/converting-blocks-from-md/App.tsx b/examples/08-interoperability/converting-blocks-from-md/App.tsx
deleted file mode 100644
index 59790c90af..0000000000
--- a/examples/08-interoperability/converting-blocks-from-md/App.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { useEffect, useState } from "react";
-import { BlockNoteView, useBlockNote } from "@blocknote/react";
-import "@blocknote/react/style.css";
-
-export default function App() {
- // Stores the current Markdown content.
- const [markdown, setMarkdown] = useState("");
-
- // Creates a new editor instance.
- const editor = useBlockNote({
- // Makes the editor non-editable.
- editable: false,
- });
-
- useEffect(() => {
- if (editor) {
- // Whenever the current Markdown content changes, converts it to an array
- // of Block objects and replaces the editor's content with them.
- const getBlocks = async () => {
- const blocks = await editor.tryParseMarkdownToBlocks(markdown);
- editor.replaceBlocks(editor.topLevelBlocks, blocks);
- };
- getBlocks();
- }
- }, [editor, markdown]);
-
- // Renders a text area for you to write/paste Markdown in, and the editor
- // instance below, which displays the current Markdown as blocks.
- return (
-
-
- );
-}
diff --git a/examples/08-interoperability/converting-blocks-from-md/README.md b/examples/08-interoperability/converting-blocks-from-md/README.md
deleted file mode 100644
index 3a69731458..0000000000
--- a/examples/08-interoperability/converting-blocks-from-md/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Converting Markdown to Blocks
\ No newline at end of file
diff --git a/examples/08-interoperability/converting-blocks-from-md/main.tsx b/examples/08-interoperability/converting-blocks-from-md/main.tsx
deleted file mode 100644
index 8327213ef0..0000000000
--- a/examples/08-interoperability/converting-blocks-from-md/main.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
-import React from "react";
-import { createRoot } from "react-dom/client";
-import App from "./App";
-
-const root = createRoot(document.getElementById("root")!);
-root.render(
- //
-
- //
-);
\ No newline at end of file
diff --git a/examples/08-interoperability/converting-blocks-to-html/.bnexample.json b/examples/08-interoperability/converting-blocks-to-html/.bnexample.json
deleted file mode 100644
index 178fd44ce0..0000000000
--- a/examples/08-interoperability/converting-blocks-to-html/.bnexample.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "playground": true,
- "docs": false,
- "group": "Basic Examples",
- "order": 1
-}
diff --git a/examples/08-interoperability/converting-blocks-to-html/App.tsx b/examples/08-interoperability/converting-blocks-to-html/App.tsx
deleted file mode 100644
index f5ef167b9c..0000000000
--- a/examples/08-interoperability/converting-blocks-to-html/App.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { useState } from "react";
-import { BlockNoteView, useBlockNote } from "@blocknote/react";
-import "@blocknote/react/style.css";
-
-export default function App() {
- // Stores the editor's contents as HTML.
- const [html, setHTML] = useState("");
-
- // Creates a new editor instance.
- const editor = useBlockNote({
- // Listens for when the editor's contents change.
- onEditorContentChange: (editor) => {
- // Converts the editor's contents from Block objects to HTML and saves
- // them.
- const saveBlocksAsHTML = async () => {
- const html = await editor.blocksToHTMLLossy(editor.topLevelBlocks);
- setHTML(html);
- };
- saveBlocksAsHTML();
- },
- });
-
- // Renders the editor instance, and its contents as HTML below.
- return (
-
- );
-}
diff --git a/examples/08-interoperability/converting-blocks-to-html/README.md b/examples/08-interoperability/converting-blocks-to-html/README.md
deleted file mode 100644
index e3d69ff3fd..0000000000
--- a/examples/08-interoperability/converting-blocks-to-html/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Converting Blocks to HTML
\ No newline at end of file
diff --git a/examples/08-interoperability/converting-blocks-to-html/main.tsx b/examples/08-interoperability/converting-blocks-to-html/main.tsx
deleted file mode 100644
index 8327213ef0..0000000000
--- a/examples/08-interoperability/converting-blocks-to-html/main.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
-import React from "react";
-import { createRoot } from "react-dom/client";
-import App from "./App";
-
-const root = createRoot(document.getElementById("root")!);
-root.render(
- //
-
- //
-);
\ No newline at end of file
diff --git a/examples/08-interoperability/converting-blocks-to-md/.bnexample.json b/examples/08-interoperability/converting-blocks-to-md/.bnexample.json
deleted file mode 100644
index 178fd44ce0..0000000000
--- a/examples/08-interoperability/converting-blocks-to-md/.bnexample.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "playground": true,
- "docs": false,
- "group": "Basic Examples",
- "order": 1
-}
diff --git a/examples/08-interoperability/converting-blocks-to-md/App.tsx b/examples/08-interoperability/converting-blocks-to-md/App.tsx
deleted file mode 100644
index e6ebc000a0..0000000000
--- a/examples/08-interoperability/converting-blocks-to-md/App.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { useState } from "react";
-import { BlockNoteView, useBlockNote } from "@blocknote/react";
-import "@blocknote/react/style.css";
-
-export default function App() {
- // Stores the editor's contents as Markdown.
- const [markdown, setMarkdown] = useState("");
-
- // Creates a new editor instance.
- const editor = useBlockNote({
- // Listens for when the editor's contents change.
- onEditorContentChange: (editor) => {
- // Converts the editor's contents from Block objects to Markdown and
- // saves them.
- const saveBlocksAsMarkdown = async () => {
- const markdown = await editor.blocksToMarkdownLossy(
- editor.topLevelBlocks
- );
- setMarkdown(markdown);
- };
- saveBlocksAsMarkdown();
- },
- });
-
- // Renders the editor instance, and its contents as Markdown below.
- return (
-
- );
-}
diff --git a/examples/08-interoperability/converting-blocks-to-md/README.md b/examples/08-interoperability/converting-blocks-to-md/README.md
deleted file mode 100644
index 7275d6f73d..0000000000
--- a/examples/08-interoperability/converting-blocks-to-md/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Converting Blocks to Markdown
\ No newline at end of file
diff --git a/examples/08-interoperability/converting-blocks-to-md/main.tsx b/examples/08-interoperability/converting-blocks-to-md/main.tsx
deleted file mode 100644
index 8327213ef0..0000000000
--- a/examples/08-interoperability/converting-blocks-to-md/main.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
-import React from "react";
-import { createRoot } from "react-dom/client";
-import App from "./App";
-
-const root = createRoot(document.getElementById("root")!);
-root.render(
- //
-
- //
-);
\ No newline at end of file
diff --git a/examples/09-vanilla-js/react-vanilla-custom-blocks/App.tsx b/examples/09-vanilla-js/react-vanilla-custom-blocks/App.tsx
index 0e968ae3ba..df9b462034 100644
--- a/examples/09-vanilla-js/react-vanilla-custom-blocks/App.tsx
+++ b/examples/09-vanilla-js/react-vanilla-custom-blocks/App.tsx
@@ -179,12 +179,6 @@ const bracketsParagraphBlock = createBlockSpec(
export default function App() {
const editor = useBlockNote({
- domAttributes: {
- editor: {
- class: "editor",
- "data-test": "editor",
- },
- },
blockSpecs: {
...defaultBlockSpecs,
alert: alertBlock,
@@ -212,5 +206,5 @@ export default function App() {
],
});
- return ;
+ return ;
}
diff --git a/examples/09-vanilla-js/react-vanilla-custom-blocks/main.tsx b/examples/09-vanilla-js/react-vanilla-custom-blocks/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/09-vanilla-js/react-vanilla-custom-blocks/main.tsx
+++ b/examples/09-vanilla-js/react-vanilla-custom-blocks/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/09-vanilla-js/react-vanilla-custom-inline-content/App.tsx b/examples/09-vanilla-js/react-vanilla-custom-inline-content/App.tsx
index d2e819f0bb..aa39042c34 100644
--- a/examples/09-vanilla-js/react-vanilla-custom-inline-content/App.tsx
+++ b/examples/09-vanilla-js/react-vanilla-custom-inline-content/App.tsx
@@ -56,12 +56,6 @@ export default function App() {
tag,
...defaultInlineContentSpecs,
},
- domAttributes: {
- editor: {
- class: "editor",
- "data-test": "editor",
- },
- },
initialContent: [
{
type: "paragraph",
@@ -89,5 +83,5 @@ export default function App() {
],
});
- return ;
+ return ;
}
diff --git a/examples/09-vanilla-js/react-vanilla-custom-inline-content/main.tsx b/examples/09-vanilla-js/react-vanilla-custom-inline-content/main.tsx
index 8327213ef0..f88b490fbd 100644
--- a/examples/09-vanilla-js/react-vanilla-custom-inline-content/main.tsx
+++ b/examples/09-vanilla-js/react-vanilla-custom-inline-content/main.tsx
@@ -5,7 +5,7 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx b/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx
index 6b9c6628ec..32f0ad4d60 100644
--- a/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx
+++ b/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx
@@ -96,15 +96,6 @@ export default function App() {
small,
fontSize,
},
- onEditorContentChange: (editor) => {
- console.log(editor.topLevelBlocks);
- },
- domAttributes: {
- editor: {
- class: "editor",
- "data-test": "editor",
- },
- },
initialContent: [
{
type: "paragraph",
@@ -131,7 +122,7 @@ export default function App() {
);
return (
-
+
-
- //
-);
\ No newline at end of file
+
+
+
+);
diff --git a/package-lock.json b/package-lock.json
index 40bd9aa987..da1b391c7d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,7 +20,7 @@
"glob": "^10.3.10",
"lerna": "^5.4.0",
"patch-package": "^6.4.7",
- "typescript": "^5.2.2"
+ "typescript": "^5.3.3"
}
},
"docs": {
@@ -21348,7 +21348,7 @@
"prettier": "^2.7.1",
"rimraf": "^5.0.5",
"rollup-plugin-webpack-stats": "^0.2.2",
- "typescript": "^5.0.4",
+ "typescript": "^5.3.3",
"vite": "^4.4.8",
"vite-plugin-eslint": "^1.8.1",
"vitest": "^0.34.1"
@@ -21404,7 +21404,7 @@
"react": "^18",
"react-dom": "^18",
"tsx": "^4.6.2",
- "typescript": "^5.0.4"
+ "typescript": "^5.3.3"
}
},
"packages/react": {
@@ -21438,7 +21438,7 @@
"prettier": "^2.7.1",
"rimraf": "^5.0.5",
"rollup-plugin-webpack-stats": "^0.2.2",
- "typescript": "^5.0.4",
+ "typescript": "^5.3.3",
"vite": "^4.4.8",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-externalize-deps": "^0.7.0",
diff --git a/package.json b/package.json
index 6577ca3fdc..c4d628b496 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"eslint-config-react-app": "^7.0.0",
"lerna": "^5.4.0",
"patch-package": "^6.4.7",
- "typescript": "^5.2.2",
+ "typescript": "^5.3.3",
"@typescript-eslint/parser": "^5.5.0",
"@typescript-eslint/eslint-plugin": "^5.5.0",
"glob": "^10.3.10"
diff --git a/packages/core/package.json b/packages/core/package.json
index 97256acd98..69c64a40dc 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -98,7 +98,7 @@
"prettier": "^2.7.1",
"rimraf": "^5.0.5",
"rollup-plugin-webpack-stats": "^0.2.2",
- "typescript": "^5.0.4",
+ "typescript": "^5.3.3",
"vite": "^4.4.8",
"vite-plugin-eslint": "^1.8.1",
"vitest": "^0.34.1"
diff --git a/packages/core/src/api/blockManipulation/blockManipulation.test.ts b/packages/core/src/api/blockManipulation/blockManipulation.test.ts
index f7d0521e2a..df471d8092 100644
--- a/packages/core/src/api/blockManipulation/blockManipulation.test.ts
+++ b/packages/core/src/api/blockManipulation/blockManipulation.test.ts
@@ -1,11 +1,12 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
+ Block,
DefaultBlockSchema,
DefaultInlineContentSchema,
DefaultStyleSchema,
+ PartialBlock,
} from "../../blocks/defaultBlocks";
import { BlockNoteEditor } from "../../editor/BlockNoteEditor";
-import { Block, PartialBlock } from "../../schema/blocks/types";
let editor: BlockNoteEditor;
diff --git a/packages/core/src/api/blockManipulation/blockManipulation.ts b/packages/core/src/api/blockManipulation/blockManipulation.ts
index 892a8be8bb..e1e44e834b 100644
--- a/packages/core/src/api/blockManipulation/blockManipulation.ts
+++ b/packages/core/src/api/blockManipulation/blockManipulation.ts
@@ -1,17 +1,16 @@
import { Node } from "prosemirror-model";
+import { Transaction } from "prosemirror-state";
+import { Block, PartialBlock } from "../../blocks/defaultBlocks";
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
import {
- Block,
BlockIdentifier,
BlockSchema,
InlineContentSchema,
- PartialBlock,
StyleSchema,
} from "../../schema";
import { blockToNode, nodeToBlock } from "../nodeConversions/nodeConversions";
import { getNodeById } from "../nodeUtil";
-import { Transaction } from "prosemirror-state";
export function insertBlocks<
BSchema extends BlockSchema,
diff --git a/packages/core/src/api/exporters/html/externalHTMLExporter.ts b/packages/core/src/api/exporters/html/externalHTMLExporter.ts
index 43b591b610..932a6c086e 100644
--- a/packages/core/src/api/exporters/html/externalHTMLExporter.ts
+++ b/packages/core/src/api/exporters/html/externalHTMLExporter.ts
@@ -3,13 +3,9 @@ import rehypeParse from "rehype-parse";
import rehypeStringify from "rehype-stringify";
import { unified } from "unified";
+import { PartialBlock } from "../../../blocks/defaultBlocks";
import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
-import {
- BlockSchema,
- InlineContentSchema,
- PartialBlock,
- StyleSchema,
-} from "../../../schema";
+import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema";
import { blockToNode } from "../../nodeConversions/nodeConversions";
import {
serializeNodeInner,
diff --git a/packages/core/src/api/exporters/html/htmlConversion.test.ts b/packages/core/src/api/exporters/html/htmlConversion.test.ts
index 6671037f19..bc02e76d02 100644
--- a/packages/core/src/api/exporters/html/htmlConversion.test.ts
+++ b/packages/core/src/api/exporters/html/htmlConversion.test.ts
@@ -2,7 +2,8 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
import { addIdsToBlocks, partialBlocksToBlocksForTesting } from "../../..";
-import { BlockSchema, PartialBlock } from "../../../schema/blocks/types";
+import { PartialBlock } from "../../../blocks/defaultBlocks";
+import { BlockSchema } from "../../../schema/blocks/types";
import { InlineContentSchema } from "../../../schema/inlineContent/types";
import { StyleSchema } from "../../../schema/styles/types";
import { customBlocksTestCases } from "../../testUtil/cases/customBlocks";
diff --git a/packages/core/src/api/exporters/html/internalHTMLSerializer.ts b/packages/core/src/api/exporters/html/internalHTMLSerializer.ts
index a635819caa..9f2a55612d 100644
--- a/packages/core/src/api/exporters/html/internalHTMLSerializer.ts
+++ b/packages/core/src/api/exporters/html/internalHTMLSerializer.ts
@@ -1,11 +1,7 @@
import { DOMSerializer, Fragment, Node, Schema } from "prosemirror-model";
+import { PartialBlock } from "../../../blocks/defaultBlocks";
import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
-import {
- BlockSchema,
- InlineContentSchema,
- PartialBlock,
- StyleSchema,
-} from "../../../schema";
+import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema";
import { blockToNode } from "../../nodeConversions/nodeConversions";
import {
serializeNodeInner,
diff --git a/packages/core/src/api/exporters/markdown/markdownExporter.test.ts b/packages/core/src/api/exporters/markdown/markdownExporter.test.ts
index a4f391bc11..894f4663cd 100644
--- a/packages/core/src/api/exporters/markdown/markdownExporter.test.ts
+++ b/packages/core/src/api/exporters/markdown/markdownExporter.test.ts
@@ -1,15 +1,16 @@
import fs from "node:fs";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import { PartialBlock } from "../../../blocks/defaultBlocks";
import { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
-import { BlockSchema, PartialBlock } from "../../../schema/blocks/types";
+import { BlockSchema } from "../../../schema/blocks/types";
import { InlineContentSchema } from "../../../schema/inlineContent/types";
import { StyleSchema } from "../../../schema/styles/types";
-import { partialBlocksToBlocksForTesting } from "../../testUtil/partialBlockTestUtil";
import { customBlocksTestCases } from "../../testUtil/cases/customBlocks";
import { customInlineContentTestCases } from "../../testUtil/cases/customInlineContent";
import { customStylesTestCases } from "../../testUtil/cases/customStyles";
import { defaultSchemaTestCases } from "../../testUtil/cases/defaultSchema";
+import { partialBlocksToBlocksForTesting } from "../../testUtil/partialBlockTestUtil";
async function convertToMarkdownAndCompareSnapshots<
B extends BlockSchema,
diff --git a/packages/core/src/api/exporters/markdown/markdownExporter.ts b/packages/core/src/api/exporters/markdown/markdownExporter.ts
index 841ff6381e..24990edee3 100644
--- a/packages/core/src/api/exporters/markdown/markdownExporter.ts
+++ b/packages/core/src/api/exporters/markdown/markdownExporter.ts
@@ -4,13 +4,9 @@ import rehypeRemark from "rehype-remark";
import remarkGfm from "remark-gfm";
import remarkStringify from "remark-stringify";
import { unified } from "unified";
+import { Block } from "../../../blocks/defaultBlocks";
import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
-import {
- Block,
- BlockSchema,
- InlineContentSchema,
- StyleSchema,
-} from "../../../schema";
+import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema";
import { createExternalHTMLExporter } from "../html/externalHTMLExporter";
import { removeUnderlines } from "./removeUnderlinesRehypePlugin";
diff --git a/packages/core/src/api/nodeConversions/nodeConversions.test.ts b/packages/core/src/api/nodeConversions/nodeConversions.test.ts
index 68d583dd55..4bdfded9af 100644
--- a/packages/core/src/api/nodeConversions/nodeConversions.test.ts
+++ b/packages/core/src/api/nodeConversions/nodeConversions.test.ts
@@ -1,12 +1,16 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { BlockNoteEditor } from "../../editor/BlockNoteEditor";
-import { PartialBlock } from "../../schema/blocks/types";
+
+import { PartialBlock } from "../../blocks/defaultBlocks";
import { customInlineContentTestCases } from "../testUtil/cases/customInlineContent";
import { customStylesTestCases } from "../testUtil/cases/customStyles";
import { defaultSchemaTestCases } from "../testUtil/cases/defaultSchema";
+import {
+ addIdsToBlock,
+ partialBlockToBlockForTesting,
+} from "../testUtil/partialBlockTestUtil";
import { blockToNode, nodeToBlock } from "./nodeConversions";
-import { addIdsToBlock, partialBlockToBlockForTesting } from "../testUtil/partialBlockTestUtil";
function validateConversion(
block: PartialBlock,
diff --git a/packages/core/src/api/nodeConversions/nodeConversions.ts b/packages/core/src/api/nodeConversions/nodeConversions.ts
index 3f82377c5a..fba56c71ca 100644
--- a/packages/core/src/api/nodeConversions/nodeConversions.ts
+++ b/packages/core/src/api/nodeConversions/nodeConversions.ts
@@ -2,14 +2,12 @@ import { Mark, Node, Schema } from "@tiptap/pm/model";
import UniqueID from "../../extensions/UniqueID/UniqueID";
import type {
- Block,
BlockSchema,
CustomInlineContentConfig,
CustomInlineContentFromConfig,
InlineContent,
InlineContentFromConfig,
InlineContentSchema,
- PartialBlock,
PartialCustomInlineContentFromConfig,
PartialInlineContent,
PartialLink,
@@ -21,6 +19,7 @@ import type {
} from "../../schema";
import { getBlockInfo } from "../getBlockInfoFromPos";
+import type { Block, PartialBlock } from "../../blocks/defaultBlocks";
import {
isLinkInlineContent,
isPartialLinkInlineContent,
diff --git a/packages/core/src/api/parsers/html/parseHTML.ts b/packages/core/src/api/parsers/html/parseHTML.ts
index dad025f9dc..97f743c2cd 100644
--- a/packages/core/src/api/parsers/html/parseHTML.ts
+++ b/packages/core/src/api/parsers/html/parseHTML.ts
@@ -1,11 +1,7 @@
import { DOMParser, Schema } from "prosemirror-model";
-import {
- Block,
- BlockSchema,
- InlineContentSchema,
- StyleSchema,
-} from "../../../schema";
+import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema";
+import { Block } from "../../../blocks/defaultBlocks";
import { nodeToBlock } from "../../nodeConversions/nodeConversions";
import { nestedListsToBlockNoteStructure } from "./util/nestedLists";
export async function HTMLToBlocks<
diff --git a/packages/core/src/api/parsers/markdown/parseMarkdown.ts b/packages/core/src/api/parsers/markdown/parseMarkdown.ts
index 6e4fab74e2..c3418fc5e3 100644
--- a/packages/core/src/api/parsers/markdown/parseMarkdown.ts
+++ b/packages/core/src/api/parsers/markdown/parseMarkdown.ts
@@ -4,12 +4,8 @@ import remarkGfm from "remark-gfm";
import remarkParse from "remark-parse";
import remarkRehype, { defaultHandlers } from "remark-rehype";
import { unified } from "unified";
-import {
- Block,
- BlockSchema,
- InlineContentSchema,
- StyleSchema,
-} from "../../../schema";
+import { Block } from "../../../blocks/defaultBlocks";
+import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema";
import { HTMLToBlocks } from "../html/parseHTML";
// modified version of https://github.com/syntax-tree/mdast-util-to-hast/blob/main/lib/handlers/code.js
diff --git a/packages/core/src/api/testUtil/index.ts b/packages/core/src/api/testUtil/index.ts
index d3269f3e86..d52a871781 100644
--- a/packages/core/src/api/testUtil/index.ts
+++ b/packages/core/src/api/testUtil/index.ts
@@ -1,5 +1,6 @@
+import { PartialBlock } from "../../blocks/defaultBlocks";
import { BlockNoteEditor } from "../../editor/BlockNoteEditor";
-import { BlockSchema, PartialBlock } from "../../schema/blocks/types";
+import { BlockSchema } from "../../schema/blocks/types";
import { InlineContentSchema } from "../../schema/inlineContent/types";
import { StyleSchema } from "../../schema/styles/types";
diff --git a/packages/core/src/api/testUtil/partialBlockTestUtil.ts b/packages/core/src/api/testUtil/partialBlockTestUtil.ts
index 6d91a204f3..3be300c7bd 100644
--- a/packages/core/src/api/testUtil/partialBlockTestUtil.ts
+++ b/packages/core/src/api/testUtil/partialBlockTestUtil.ts
@@ -1,10 +1,6 @@
+import { Block, PartialBlock } from "../../blocks/defaultBlocks";
import UniqueID from "../../extensions/UniqueID/UniqueID";
-import {
- Block,
- BlockSchema,
- PartialBlock,
- TableContent,
-} from "../../schema/blocks/types";
+import { BlockSchema, TableContent } from "../../schema/blocks/types";
import {
InlineContent,
InlineContentSchema,
diff --git a/packages/core/src/blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts b/packages/core/src/blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts
index 21c09bb645..406dc7e8d9 100644
--- a/packages/core/src/blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts
+++ b/packages/core/src/blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts
@@ -1,3 +1,8 @@
+/**
+ * Uploads a file to tmpfiles.org and returns the URL to the uploaded file.
+ *
+ * @warning This function should only be used for development purposes, replace with your own backend!
+ */
export const uploadToTmpFilesDotOrg_DEV_ONLY = async (file: File) => {
const body = new FormData();
body.append("file", file);
diff --git a/packages/core/src/blocks/defaultBlockHelpers.ts b/packages/core/src/blocks/defaultBlockHelpers.ts
index 710ca57aa0..7769e6a492 100644
--- a/packages/core/src/blocks/defaultBlockHelpers.ts
+++ b/packages/core/src/blocks/defaultBlockHelpers.ts
@@ -1,12 +1,8 @@
import { blockToNode } from "../api/nodeConversions/nodeConversions";
import type { BlockNoteEditor } from "../editor/BlockNoteEditor";
-import type {
- Block,
- BlockSchema,
- InlineContentSchema,
- StyleSchema,
-} from "../schema";
+import type { BlockSchema, InlineContentSchema, StyleSchema } from "../schema";
import { mergeCSSClasses } from "../util/browser";
+import { Block } from "./defaultBlocks";
// Function that creates a ProseMirror `DOMOutputSpec` for a default block.
// Since all default blocks have the same structure (`blockContent` div with a
diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts
index 36ebc50f7d..df16a70fcf 100644
--- a/packages/core/src/blocks/defaultBlocks.ts
+++ b/packages/core/src/blocks/defaultBlocks.ts
@@ -6,8 +6,13 @@ import Underline from "@tiptap/extension-underline";
import { BackgroundColor } from "../extensions/BackgroundColor/BackgroundColorMark";
import { TextColor } from "../extensions/TextColor/TextColorMark";
import {
+ BlockNoDefaults,
+ BlockSchema,
BlockSpecs,
+ InlineContentSchema,
InlineContentSpecs,
+ PartialBlockNoDefaults,
+ StyleSchema,
StyleSpecs,
createStyleSpecFromTipTapMark,
getBlockSchemaFromSpecs,
@@ -58,3 +63,15 @@ export const defaultInlineContentSchema = getInlineContentSchemaFromSpecs(
);
export type DefaultInlineContentSchema = typeof defaultInlineContentSchema;
+
+export type PartialBlock<
+ BSchema extends BlockSchema = DefaultBlockSchema,
+ I extends InlineContentSchema = DefaultInlineContentSchema,
+ S extends StyleSchema = DefaultStyleSchema
+> = PartialBlockNoDefaults;
+
+export type Block<
+ BSchema extends BlockSchema = DefaultBlockSchema,
+ I extends InlineContentSchema = DefaultInlineContentSchema,
+ S extends StyleSchema = DefaultStyleSchema
+> = BlockNoDefaults;
diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts
index 00214d8454..8308a6abd5 100644
--- a/packages/core/src/editor/BlockNoteEditor.ts
+++ b/packages/core/src/editor/BlockNoteEditor.ts
@@ -1,7 +1,6 @@
-import { Editor, EditorOptions, Extension } from "@tiptap/core";
+import { EditorOptions, Extension } from "@tiptap/core";
import { Node } from "prosemirror-model";
// import "./blocknote.css";
-import { Editor as TiptapEditor } from "@tiptap/core/dist/packages/core/src/Editor";
import * as Y from "yjs";
import {
insertBlocks,
@@ -12,21 +11,20 @@ import {
import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter";
import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter";
import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos";
-import {
- blockToNode,
- nodeToBlock,
-} from "../api/nodeConversions/nodeConversions";
+import { nodeToBlock } from "../api/nodeConversions/nodeConversions";
import { getNodeById } from "../api/nodeUtil";
import { HTMLToBlocks } from "../api/parsers/html/parseHTML";
import { markdownToBlocks } from "../api/parsers/markdown/parseMarkdown";
import {
+ Block,
DefaultBlockSchema,
- DefaultInlineContentSchema,
- DefaultStyleSchema,
defaultBlockSchema,
defaultBlockSpecs,
+ DefaultInlineContentSchema,
defaultInlineContentSpecs,
+ DefaultStyleSchema,
defaultStyleSpecs,
+ PartialBlock,
} from "../blocks/defaultBlocks";
import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin";
import { HyperlinkToolbarProsemirrorPlugin } from "../extensions/HyperlinkToolbar/HyperlinkToolbarPlugin";
@@ -38,24 +36,22 @@ import { getDefaultSlashMenuItems } from "../extensions/SlashMenu/defaultSlashMe
import { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/TableHandlesPlugin";
import { UniqueID } from "../extensions/UniqueID/UniqueID";
import {
- Block,
BlockIdentifier,
BlockNoteDOMAttributes,
BlockSchema,
BlockSchemaFromSpecs,
BlockSchemaWithBlock,
BlockSpecs,
+ getBlockSchemaFromSpecs,
+ getInlineContentSchemaFromSpecs,
+ getStyleSchemaFromSpecs,
InlineContentSchema,
InlineContentSchemaFromSpecs,
InlineContentSpecs,
- PartialBlock,
+ Styles,
StyleSchema,
StyleSchemaFromSpecs,
StyleSpecs,
- Styles,
- getBlockSchemaFromSpecs,
- getInlineContentSchemaFromSpecs,
- getStyleSchemaFromSpecs,
} from "../schema";
import { mergeCSSClasses } from "../util/browser";
import { UnreachableCaseError } from "../util/typescript";
@@ -68,8 +64,15 @@ import { transformPasted } from "./transformPasted";
// CSS
import "./Block.css";
+import {
+ BlockNoteTipTapEditor,
+ BlockNoteTipTapEditorOptions,
+} from "./BlockNoteTipTapEditor";
import "./editor.css";
+// TODO: change for built-in version of typescript 5.4 after upgrade
+export type NoInfer = [T][T extends any ? 0 : never];
+
export type BlockNoteEditorOptions<
BSpecs extends BlockSpecs,
ISpecs extends InlineContentSpecs,
@@ -85,59 +88,20 @@ export type BlockNoteEditorOptions<
*/
slashMenuItems: BaseSlashMenuItem[];
- /**
- * The HTML element that should be used as the parent element for the editor.
- *
- * @default: undefined, the editor is not attached to the DOM
- */
- parentElement: HTMLElement;
/**
* An object containing attributes that should be added to HTML elements of the editor.
*
* @example { editor: { class: "my-editor-class" } }
*/
domAttributes: Partial;
- /**
- * A callback function that runs when the editor is ready to be used.
- */
- onEditorReady: (
- editor: BlockNoteEditor<
- BlockSchemaFromSpecs,
- InlineContentSchemaFromSpecs,
- StyleSchemaFromSpecs
- >
- ) => void;
- /**
- * A callback function that runs whenever the editor's contents change.
- */
- onEditorContentChange: (
- editor: BlockNoteEditor<
- BlockSchemaFromSpecs,
- InlineContentSchemaFromSpecs,
- StyleSchemaFromSpecs
- >
- ) => void;
- /**
- * A callback function that runs whenever the text cursor position changes.
- */
- onTextCursorPositionChange: (
- editor: BlockNoteEditor<
- BlockSchemaFromSpecs,
- InlineContentSchemaFromSpecs,
- StyleSchemaFromSpecs
- >
- ) => void;
- /**
- * Locks the editor from being editable by the user if set to `false`.
- */
- editable: boolean;
+
/**
* The content that should be in the editor when it's created, represented as an array of partial block objects.
*/
initialContent: PartialBlock<
- BlockSchemaFromSpecs,
- InlineContentSchemaFromSpecs,
- StyleSchemaFromSpecs
+ BlockSchemaFromSpecs>,
+ InlineContentSchemaFromSpecs>,
+ StyleSchemaFromSpecs>
>[];
/**
* Use default BlockNote font and reset the styles of
elements etc., that are used in BlockNote.
@@ -147,14 +111,20 @@ export type BlockNoteEditorOptions<
defaultStyles: boolean;
/**
- * A list of block types that should be available in the editor.
+ * A list of custom block types that should be available in the editor.
*/
blockSpecs: BSpecs;
- styleSpecs: SSpecs;
-
+ /**
+ * A list of custom InlineContent types that should be available in the editor.
+ */
inlineContentSpecs: ISpecs;
+ /**
+ * A list of custom Styles that should be available in the editor.
+ */
+ styleSpecs: SSpecs;
+
/**
* A custom function to handle file uploads.
* @param file The file that should be uploaded.
@@ -202,7 +172,9 @@ export class BlockNoteEditor<
ISchema extends InlineContentSchema = DefaultInlineContentSchema,
SSchema extends StyleSchema = DefaultStyleSchema
> {
- public readonly _tiptapEditor: TiptapEditor & { contentComponent: any };
+ public readonly _tiptapEditor: BlockNoteTipTapEditor & {
+ contentComponent: any;
+ };
public blockCache = new WeakMap>();
public readonly blockSchema: BSchema;
public readonly inlineContentSchema: ISchema;
@@ -212,8 +184,6 @@ export class BlockNoteEditor<
public readonly inlineContentImplementations: InlineContentSpecs;
public readonly styleImplementations: StyleSpecs;
- public ready = false;
-
public readonly sideMenu: SideMenuProsemirrorPlugin<
BSchema,
ISchema,
@@ -266,6 +236,31 @@ export class BlockNoteEditor<
private constructor(
private readonly options: Partial>
) {
+ const anyOpts = options as any;
+ if (anyOpts.onEditorContentChange) {
+ throw new Error(
+ "onEditorContentChange initialization option is deprecated, use , the useEditorChange(...) hook, or editor.onChange(...)"
+ );
+ }
+
+ if (anyOpts.onTextCursorPositionChange) {
+ throw new Error(
+ "onTextCursorPositionChange initialization option is deprecated, use , the useEditorSelectionChange(...) hook, or editor.onSelectionChange(...)"
+ );
+ }
+
+ if (anyOpts.onEditorReady) {
+ throw new Error(
+ "onEditorReady is deprecated. Editor is immediately ready for use after creation."
+ );
+ }
+
+ if (anyOpts.editable) {
+ throw new Error(
+ "editable initialization option is deprecated, use , or alternatively editor.isEditable = true/false"
+ );
+ }
+
// apply defaults
const newOptions = {
defaultStyles: true,
@@ -336,95 +331,30 @@ export class BlockNoteEditor<
const initialContent =
newOptions.initialContent ||
(options.collaboration
- ? undefined
+ ? [
+ {
+ type: "paragraph",
+ id: "initialBlockId",
+ },
+ ]
: [
{
type: "paragraph",
id: UniqueID.options.generateID(),
},
]);
- const styleSchema = this.styleSchema;
- const tiptapOptions: Partial = {
+ if (!Array.isArray(initialContent) || initialContent.length === 0) {
+ throw new Error(
+ "initialContent must be a non-empty array of blocks, received: " +
+ initialContent
+ );
+ }
+
+ const tiptapOptions: BlockNoteTipTapEditorOptions = {
...blockNoteTipTapOptions,
...newOptions._tiptapOptions,
- onBeforeCreate(editor) {
- newOptions._tiptapOptions?.onBeforeCreate?.(editor);
- // We always set the initial content to a single paragraph block. This
- // allows us to easily replace it with the actual initial content once
- // the TipTap editor is initialized.
- const schema = editor.editor.schema;
-
- // This is a hack to make "initial content detection" by y-prosemirror (and also tiptap isEmpty)
- // properly detect whether or not the document has changed.
- // We change the doc.createAndFill function to make sure the initial block id is set, instead of null
- let cache: any;
- const oldCreateAndFill = schema.nodes.doc.createAndFill;
- (schema.nodes.doc as any).createAndFill = (...args: any) => {
- if (cache) {
- return cache;
- }
- const ret = oldCreateAndFill.apply(schema.nodes.doc, args);
-
- // create a copy that we can mutate (otherwise, assigning attrs is not safe and corrupts the pm state)
- const jsonNode = JSON.parse(JSON.stringify(ret!.toJSON()));
- jsonNode.content[0].content[0].attrs.id = "initialBlockId";
-
- cache = Node.fromJSON(schema, jsonNode);
- return cache;
- };
-
- const root = schema.node(
- "doc",
- undefined,
- schema.node("blockGroup", undefined, [
- blockToNode(
- { id: "initialBlockId", type: "paragraph" },
- schema,
- styleSchema
- ),
- ])
- );
- editor.editor.options.content = root.toJSON();
- },
- onCreate: (editor) => {
- newOptions._tiptapOptions?.onCreate?.(editor);
- // We need to wait for the TipTap editor to init before we can set the
- // initial content, as the schema may contain custom blocks which need
- // it to render.
- if (initialContent !== undefined) {
- this.replaceBlocks(this.topLevelBlocks, initialContent as any);
- }
-
- newOptions.onEditorReady?.(this);
- this.ready = true;
- },
- onUpdate: (editor) => {
- newOptions._tiptapOptions?.onUpdate?.(editor);
- // This seems to be necessary due to a bug in TipTap:
- // https://github.com/ueberdosis/tiptap/issues/2583
- if (!this.ready) {
- return;
- }
-
- newOptions.onEditorContentChange?.(this);
- },
- onSelectionUpdate: (editor) => {
- newOptions._tiptapOptions?.onSelectionUpdate?.(editor);
- // This seems to be necessary due to a bug in TipTap:
- // https://github.com/ueberdosis/tiptap/issues/2583
- if (!this.ready) {
- return;
- }
-
- newOptions.onTextCursorPositionChange?.(this);
- },
- editable:
- options.editable !== undefined
- ? options.editable
- : newOptions._tiptapOptions?.editable !== undefined
- ? newOptions._tiptapOptions?.editable
- : true,
+ content: initialContent,
extensions:
newOptions.enableBlockNoteExtensions === false
? newOptions._tiptapOptions?.extensions || []
@@ -435,7 +365,6 @@ export class BlockNoteEditor<
...newOptions._tiptapOptions?.editorProps?.attributes,
...newOptions.domAttributes?.editor,
class: mergeCSSClasses(
- "bn-root",
"bn-editor",
newOptions.defaultStyles ? "bn-default-styles" : "",
newOptions.domAttributes?.editor?.class || ""
@@ -445,15 +374,23 @@ export class BlockNoteEditor<
},
};
- if (newOptions.parentElement) {
- tiptapOptions.element = newOptions.parentElement;
- }
-
- this._tiptapEditor = new Editor(tiptapOptions) as Editor & {
+ this._tiptapEditor = new BlockNoteTipTapEditor(
+ tiptapOptions,
+ this.styleSchema
+ ) as BlockNoteTipTapEditor & {
contentComponent: any;
};
}
+ /**
+ * Mount the editor to a parent DOM element. Call mount(undefined) to clean up
+ *
+ * @warning Not needed for React, use BlockNoteView to take care of this
+ */
+ public mount(parentElement?: HTMLElement | null) {
+ this._tiptapEditor.mount(parentElement);
+ }
+
public get prosemirrorView() {
return this._tiptapEditor.view;
}
@@ -1052,4 +989,44 @@ export class BlockNoteEditor<
}
this._tiptapEditor.commands.updateUser(user);
}
+
+ /**
+ * A callback function that runs whenever the editor's contents change.
+ *
+ * @param callback The callback to execute.
+ * @returns A function to remove the callback.
+ */
+ public onChange(
+ callback: (editor: BlockNoteEditor) => void
+ ) {
+ const cb = () => {
+ callback(this);
+ };
+
+ this._tiptapEditor.on("update", cb);
+
+ return () => {
+ this._tiptapEditor.off("update", cb);
+ };
+ }
+
+ /**
+ * A callback function that runs whenever the text cursor position or selection changes.
+ *
+ * @param callback The callback to execute.
+ * @returns A function to remove the callback.
+ */
+ public onSelectionChange(
+ callback: (editor: BlockNoteEditor) => void
+ ) {
+ const cb = () => {
+ callback(this);
+ };
+
+ this._tiptapEditor.on("selectionUpdate", cb);
+
+ return () => {
+ this._tiptapEditor.off("selectionUpdate", cb);
+ };
+ }
}
diff --git a/packages/core/src/editor/BlockNoteTipTapEditor.ts b/packages/core/src/editor/BlockNoteTipTapEditor.ts
new file mode 100644
index 0000000000..6bd1b35998
--- /dev/null
+++ b/packages/core/src/editor/BlockNoteTipTapEditor.ts
@@ -0,0 +1,160 @@
+import { EditorOptions, createDocument } from "@tiptap/core";
+// import "./blocknote.css";
+import { Editor as TiptapEditor } from "@tiptap/core";
+import { Node } from "@tiptap/pm/model";
+import { EditorView } from "@tiptap/pm/view";
+import { EditorState } from "prosemirror-state";
+
+import { blockToNode } from "../api/nodeConversions/nodeConversions";
+import { PartialBlock } from "../blocks/defaultBlocks";
+import { StyleSchema } from "../schema";
+
+export type BlockNoteTipTapEditorOptions = Partial<
+ Omit
+> & {
+ content: PartialBlock[];
+};
+
+/**
+ * Custom Editor class that extends TiptapEditor and separates
+ * the creation of the view from the constructor.
+ */
+// @ts-ignore
+export class BlockNoteTipTapEditor extends TiptapEditor {
+ private _state: EditorState;
+
+ constructor(options: BlockNoteTipTapEditorOptions, styleSchema: StyleSchema) {
+ // possible fix for next.js server side rendering
+ // const d = globalThis.document;
+ // const w = globalThis.window;
+ // if (!globalThis.document) {
+ // globalThis.document = {
+ // createElement: () => {},
+ // };
+ // }
+ // if (!globalThis.window) {
+ // globalThis.window = {
+ // setTimeout: () => {},
+ // };
+ // }
+ // options.injectCSS = false
+ super({ ...options, content: undefined });
+
+ // try {
+ // globalThis.window = w;
+ // } catch(e) {}
+ // try {
+ // globalThis.document = d;
+ // } catch(e) {}
+
+ // This is a hack to make "initial content detection" by y-prosemirror (and also tiptap isEmpty)
+ // properly detect whether or not the document has changed.
+ // We change the doc.createAndFill function to make sure the initial block id is set, instead of null
+ const schema = this.schema;
+ let cache: any;
+ const oldCreateAndFill = schema.nodes.doc.createAndFill;
+ (schema.nodes.doc as any).createAndFill = (...args: any) => {
+ if (cache) {
+ return cache;
+ }
+ const ret = oldCreateAndFill.apply(schema.nodes.doc, args);
+
+ // create a copy that we can mutate (otherwise, assigning attrs is not safe and corrupts the pm state)
+ const jsonNode = JSON.parse(JSON.stringify(ret!.toJSON()));
+ jsonNode.content[0].content[0].attrs.id = "initialBlockId";
+
+ cache = Node.fromJSON(schema, jsonNode);
+ return cache;
+ };
+
+ let doc: Node;
+
+ try {
+ const pmNodes = options?.content.map((b) =>
+ blockToNode(b, this.schema, styleSchema).toJSON()
+ );
+ doc = createDocument(
+ {
+ type: "doc",
+ content: [
+ {
+ type: "blockGroup",
+ content: pmNodes,
+ },
+ ],
+ },
+ this.schema,
+ this.options.parseOptions
+ );
+ } catch (e) {
+ console.error(
+ "Error creating document from blocks passed as `initialContent`. Caused by exception: ",
+ e
+ );
+ throw new Error(
+ "Error creating document from blocks passed as `initialContent`:\n" +
+ +JSON.stringify(options.content)
+ );
+ }
+
+ // Create state immediately, so that it's available independently from the View,
+ // the way Prosemirror "intends it to be". This also makes sure that we can access
+ // the state before the view is created / mounted.
+ this._state = EditorState.create({
+ doc,
+ schema: this.schema,
+ // selection: selection || undefined,
+ });
+ }
+
+ get state() {
+ if (this.view) {
+ this._state = this.view.state;
+ }
+ return this._state;
+ }
+
+ createView() {
+ // no-op
+ // Disable default call to `createView` in the Editor constructor.
+ // We should call `createView` manually only when a DOM element is available
+ }
+
+ /**
+ * Replace the default `createView` method with a custom one - which we call on mount
+ */
+ private createViewAlternative() {
+ this.view = new EditorView(this.options.element, {
+ ...this.options.editorProps,
+ // @ts-ignore
+ dispatchTransaction: this.dispatchTransaction.bind(this),
+ state: this.state,
+ });
+
+ // `editor.view` is not yet available at this time.
+ // Therefore we will add all plugins and node views directly afterwards.
+ const newState = this.state.reconfigure({
+ plugins: this.extensionManager.plugins,
+ });
+
+ this.view.updateState(newState);
+
+ this.createNodeViews();
+ }
+
+ /**
+ * Mounts / unmounts the editor to a dom element
+ *
+ * @param element DOM element to mount to, ur null / undefined to destroy
+ */
+ public mount = (element?: HTMLElement | null) => {
+ console.log("mount", element);
+ if (!element) {
+ this.destroy();
+ } else {
+ this.options.element = element;
+ // @ts-ignore
+ this.createViewAlternative();
+ }
+ };
+}
diff --git a/packages/core/src/editor/cursorPositionTypes.ts b/packages/core/src/editor/cursorPositionTypes.ts
index b7fa932475..7f82cce855 100644
--- a/packages/core/src/editor/cursorPositionTypes.ts
+++ b/packages/core/src/editor/cursorPositionTypes.ts
@@ -1,9 +1,5 @@
-import {
- Block,
- BlockSchema,
- InlineContentSchema,
- StyleSchema,
-} from "../schema";
+import { Block } from "../blocks/defaultBlocks";
+import { BlockSchema, InlineContentSchema, StyleSchema } from "../schema";
export type TextCursorPosition<
BSchema extends BlockSchema,
diff --git a/packages/core/src/editor/selectionTypes.ts b/packages/core/src/editor/selectionTypes.ts
index aef65b5f08..a96b26945f 100644
--- a/packages/core/src/editor/selectionTypes.ts
+++ b/packages/core/src/editor/selectionTypes.ts
@@ -1,9 +1,5 @@
-import {
- Block,
- BlockSchema,
- InlineContentSchema,
- StyleSchema,
-} from "../schema";
+import { Block } from "../blocks/defaultBlocks";
+import { BlockSchema, InlineContentSchema, StyleSchema } from "../schema";
export type Selection<
BSchema extends BlockSchema,
diff --git a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts
index 2eb687e7cb..76815c5ab0 100644
--- a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts
+++ b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts
@@ -6,14 +6,10 @@ import { createExternalHTMLExporter } from "../../api/exporters/html/externalHTM
import { createInternalHTMLSerializer } from "../../api/exporters/html/internalHTMLSerializer";
import { cleanHTMLToMarkdown } from "../../api/exporters/markdown/markdownExporter";
import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos";
+import { Block } from "../../blocks/defaultBlocks";
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
import { BaseUiElementState } from "../../extensions-shared/BaseUiElementTypes";
-import {
- Block,
- BlockSchema,
- InlineContentSchema,
- StyleSchema,
-} from "../../schema";
+import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema";
import { EventEmitter } from "../../util/EventEmitter";
import { slashMenuPluginKey } from "../SlashMenu/SlashMenuPlugin";
import { MultipleNodeSelection } from "./MultipleNodeSelection";
diff --git a/packages/core/src/extensions/SlashMenu/defaultSlashMenuItems.ts b/packages/core/src/extensions/SlashMenu/defaultSlashMenuItems.ts
index b6b3aa0115..bdc88415c1 100644
--- a/packages/core/src/extensions/SlashMenu/defaultSlashMenuItems.ts
+++ b/packages/core/src/extensions/SlashMenu/defaultSlashMenuItems.ts
@@ -1,10 +1,12 @@
-import { defaultBlockSchema } from "../../blocks/defaultBlocks";
-import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
import {
Block,
+ PartialBlock,
+ defaultBlockSchema,
+} from "../../blocks/defaultBlocks";
+import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
+import {
BlockSchema,
InlineContentSchema,
- PartialBlock,
StyleSchema,
isStyledTextInlineContent,
} from "../../schema";
diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts
index 520ae5718e..d4532e98d3 100644
--- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts
+++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts
@@ -1,18 +1,20 @@
import { Plugin, PluginKey, PluginView } from "prosemirror-state";
import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
-import { EventEmitter } from "../../util/EventEmitter";
import { nodeToBlock } from "../../api/nodeConversions/nodeConversions";
-import { DefaultBlockSchema } from "../../blocks/defaultBlocks";
-import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
import {
Block,
+ DefaultBlockSchema,
+ PartialBlock,
+} from "../../blocks/defaultBlocks";
+import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
+import {
BlockFromConfigNoChildren,
BlockSchemaWithBlock,
InlineContentSchema,
- PartialBlock,
SpecificBlock,
StyleSchema,
} from "../../schema";
+import { EventEmitter } from "../../util/EventEmitter";
import { getDraggableBlockFromCoords } from "../SideMenu/SideMenuPlugin";
let dragImageElement: HTMLElement | undefined;
diff --git a/packages/core/src/pm-nodes/BlockContainer.ts b/packages/core/src/pm-nodes/BlockContainer.ts
index 77e3e3dab8..b4467ffd01 100644
--- a/packages/core/src/pm-nodes/BlockContainer.ts
+++ b/packages/core/src/pm-nodes/BlockContainer.ts
@@ -8,6 +8,7 @@ import {
inlineContentToNodes,
tableContentToNodes,
} from "../api/nodeConversions/nodeConversions";
+import { PartialBlock } from "../blocks/defaultBlocks";
import type { BlockNoteEditor } from "../editor/BlockNoteEditor";
import { NonEditableBlockPlugin } from "../extensions/NonEditableBlocks/NonEditableBlockPlugin";
import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin";
@@ -15,7 +16,6 @@ import {
BlockNoteDOMAttributes,
BlockSchema,
InlineContentSchema,
- PartialBlock,
StyleSchema,
} from "../schema";
import { mergeCSSClasses } from "../util/browser";
diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts
index 65b231c3a9..6872e48e39 100644
--- a/packages/core/src/schema/blocks/types.ts
+++ b/packages/core/src/schema/blocks/types.ts
@@ -41,7 +41,7 @@ export type TiptapBlockImplementation<
node: Node;
toInternalHTML: (
block: BlockFromConfigNoChildren & {
- children: Block[];
+ children: BlockNoDefaults[];
},
editor: BlockNoteEditor
) => {
@@ -50,7 +50,7 @@ export type TiptapBlockImplementation<
};
toExternalHTML: (
block: BlockFromConfigNoChildren & {
- children: Block[];
+ children: BlockNoDefaults[];
},
editor: BlockNoteEditor
) => {
@@ -142,7 +142,7 @@ export type BlockFromConfig<
I extends InlineContentSchema,
S extends StyleSchema
> = BlockFromConfigNoChildren & {
- children: Block[];
+ children: BlockNoDefaults[];
};
// Converts each block spec into a Block object without children. We later merge
@@ -158,12 +158,12 @@ type BlocksWithoutChildren<
// Converts each block spec into a Block object without children, merges them
// into a union type, and adds a children property
-export type Block<
+export type BlockNoDefaults<
BSchema extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema
> = BlocksWithoutChildren[keyof BSchema] & {
- children: Block[];
+ children: BlockNoDefaults[];
};
export type SpecificBlock<
@@ -172,7 +172,7 @@ export type SpecificBlock<
I extends InlineContentSchema,
S extends StyleSchema
> = BlocksWithoutChildren[BType] & {
- children: Block[];
+ children: BlockNoDefaults[];
};
/** CODE FOR PARTIAL BLOCKS, analogous to above
@@ -219,7 +219,7 @@ type PartialBlocksWithoutChildren<
>;
};
-export type PartialBlock<
+export type PartialBlockNoDefaults<
BSchema extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema
@@ -229,7 +229,7 @@ export type PartialBlock<
S
>[keyof PartialBlocksWithoutChildren] &
Partial<{
- children: PartialBlock[];
+ children: PartialBlockNoDefaults[];
}>;
export type SpecificPartialBlock<
@@ -238,7 +238,7 @@ export type SpecificPartialBlock<
BType extends keyof BSchema,
S extends StyleSchema
> = PartialBlocksWithoutChildren[BType] & {
- children?: Block[];
+ children?: BlockNoDefaults[];
};
export type PartialBlockFromConfig<
@@ -246,7 +246,7 @@ export type PartialBlockFromConfig<
I extends InlineContentSchema,
S extends StyleSchema
> = PartialBlockFromConfigNoChildren & {
- children?: Block[];
+ children?: BlockNoDefaults[];
};
export type BlockIdentifier = { id: string } | string;
diff --git a/packages/dev-scripts/examples/genDocs.ts b/packages/dev-scripts/examples/genDocs.ts
index a3cd17b17c..dbdf41db9c 100644
--- a/packages/dev-scripts/examples/genDocs.ts
+++ b/packages/dev-scripts/examples/genDocs.ts
@@ -82,16 +82,6 @@ ${readme}
* Consists of the contents of the readme + the interactive example
*/
async function generatePageForExample(project: Project) {
- if (
- !fs.existsSync(
- path.resolve(dir, "../../../docs/pages/examples/" + project.group.slug)
- )
- ) {
- fs.mkdirSync(
- path.resolve(dir, "../../../docs/pages/examples/" + project.group.slug)
- );
- }
-
const target = path.resolve(
dir,
"../../../docs/pages/examples/" + project.fullSlug + ".mdx"
@@ -112,6 +102,16 @@ async function generateMetaForExampleGroup(group: {
slug: string;
projects: Project[];
}) {
+ if (
+ !fs.existsSync(
+ path.resolve(dir, "../../../docs/pages/examples/" + group.slug)
+ )
+ ) {
+ fs.mkdirSync(
+ path.resolve(dir, "../../../docs/pages/examples/" + group.slug)
+ );
+ }
+
const target = path.resolve(
dir,
"../../../docs/pages/examples/" + group.slug + "/_meta.json"
diff --git a/packages/dev-scripts/examples/template-react/main.tsx.template.tsx b/packages/dev-scripts/examples/template-react/main.tsx.template.tsx
index e0bb00ee34..ea71db9e53 100644
--- a/packages/dev-scripts/examples/template-react/main.tsx.template.tsx
+++ b/packages/dev-scripts/examples/template-react/main.tsx.template.tsx
@@ -9,9 +9,10 @@ import App from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(
- //
-
- //
-);`;
+
+
+
+);
+`;
export default template;
diff --git a/packages/dev-scripts/package.json b/packages/dev-scripts/package.json
index f4205226a6..83bd14724d 100644
--- a/packages/dev-scripts/package.json
+++ b/packages/dev-scripts/package.json
@@ -19,7 +19,7 @@
"@types/react-dom": "^18.0.9",
"eslint": "^8.10.0",
"prettier": "^2.7.1",
- "typescript": "^5.0.4",
+ "typescript": "^5.3.3",
"tsx": "^4.6.2",
"glob": "^10.3.10",
"fast-glob": "^3.3.2",
diff --git a/packages/react/package.json b/packages/react/package.json
index cfc57b07e5..52ee0be09c 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -76,7 +76,7 @@
"prettier": "^2.7.1",
"rimraf": "^5.0.5",
"rollup-plugin-webpack-stats": "^0.2.2",
- "typescript": "^5.0.4",
+ "typescript": "^5.3.3",
"vite": "^4.4.8",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-externalize-deps": "^0.7.0",
diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/ColorStyleButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/ColorStyleButton.tsx
index 3b2b3fba80..bf2f52efb2 100644
--- a/packages/react/src/components/FormattingToolbar/DefaultButtons/ColorStyleButton.tsx
+++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/ColorStyleButton.tsx
@@ -5,7 +5,7 @@ import { useCallback, useMemo, useState } from "react";
import { ColorIcon } from "../../../components-shared/ColorPicker/ColorIcon";
import { ColorPicker } from "../../../components-shared/ColorPicker/ColorPicker";
import { ToolbarButton } from "../../../components-shared/Toolbar/ToolbarButton";
-import { useEditorChange } from "../../../hooks/useEditorChange";
+import { useEditorContentOrSelectionChange } from "../../../hooks/useEditorContentOrSelectionChange";
import { usePreventMenuOverflow } from "../../../hooks/usePreventMenuOverflow";
import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks";
@@ -21,12 +21,12 @@ export const ColorStyleButton = (props: {
props.editor.getActiveStyles().backgroundColor || "default"
);
- useEditorChange(props.editor, () => {
+ useEditorContentOrSelectionChange(() => {
setCurrentTextColor(props.editor.getActiveStyles().textColor || "default");
setCurrentBackgroundColor(
props.editor.getActiveStyles().backgroundColor || "default"
);
- });
+ }, props.editor);
const { ref, updateMaxHeight } = usePreventMenuOverflow();
diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx
index 1276c8ff06..b3e29c5ba8 100644
--- a/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx
+++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx
@@ -1,13 +1,13 @@
-import { useCallback, useMemo, useState } from "react";
import { BlockNoteEditor, BlockSchema } from "@blocknote/core";
+import { useCallback, useMemo, useState } from "react";
import { RiLink } from "react-icons/ri";
-import { ToolbarInputDropdownButton } from "../../../components-shared/Toolbar/ToolbarInputDropdownButton";
+import { formatKeyboardShortcut } from "@blocknote/core";
import { ToolbarButton } from "../../../components-shared/Toolbar/ToolbarButton";
-import { EditHyperlinkMenu } from "../../HyperlinkToolbar/EditHyperlinkMenu/components/EditHyperlinkMenu";
+import { ToolbarInputDropdownButton } from "../../../components-shared/Toolbar/ToolbarInputDropdownButton";
+import { useEditorContentOrSelectionChange } from "../../../hooks/useEditorContentOrSelectionChange";
import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks";
-import { useEditorChange } from "../../../hooks/useEditorChange";
-import { formatKeyboardShortcut } from "@blocknote/core";
+import { EditHyperlinkMenu } from "../../HyperlinkToolbar/EditHyperlinkMenu/components/EditHyperlinkMenu";
export const CreateLinkButton = (props: {
editor: BlockNoteEditor;
@@ -19,10 +19,10 @@ export const CreateLinkButton = (props: {
);
const [text, setText] = useState(props.editor.getSelectedText());
- useEditorChange(props.editor, () => {
+ useEditorContentOrSelectionChange(() => {
setText(props.editor.getSelectedText() || "");
setUrl(props.editor.getSelectedLinkUrl() || "");
- });
+ }, props.editor);
const update = useCallback(
(url: string, text: string) => {
diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/NestBlockButtons.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/NestBlockButtons.tsx
index efdda0b7b7..28abeafaa3 100644
--- a/packages/react/src/components/FormattingToolbar/DefaultButtons/NestBlockButtons.tsx
+++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/NestBlockButtons.tsx
@@ -1,10 +1,10 @@
-import { useCallback, useState } from "react";
import { BlockNoteEditor, BlockSchema } from "@blocknote/core";
+import { useCallback, useState } from "react";
import { RiIndentDecrease, RiIndentIncrease } from "react-icons/ri";
-import { ToolbarButton } from "../../../components-shared/Toolbar/ToolbarButton";
-import { useEditorChange } from "../../../hooks/useEditorChange";
import { formatKeyboardShortcut } from "@blocknote/core";
+import { ToolbarButton } from "../../../components-shared/Toolbar/ToolbarButton";
+import { useEditorContentOrSelectionChange } from "../../../hooks/useEditorContentOrSelectionChange";
export const NestBlockButton = (props: {
editor: BlockNoteEditor;
@@ -13,10 +13,10 @@ export const NestBlockButton = (props: {
props.editor.canNestBlock()
);
- useEditorChange(props.editor, () => {
+ useEditorContentOrSelectionChange(() => {
props.editor.canNestBlock();
setCanNestBlock(props.editor.canNestBlock());
- });
+ }, props.editor);
const nestBlock = useCallback(() => {
props.editor.focus();
@@ -41,9 +41,9 @@ export const UnnestBlockButton = (props: {
props.editor.canUnnestBlock()
);
- useEditorChange(props.editor, () => {
+ useEditorContentOrSelectionChange(() => {
setCanUnnestBlock(props.editor.canUnnestBlock());
- });
+ }, props.editor);
const unnestBlock = useCallback(() => {
props.editor.focus();
diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/ToggledStyleButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/ToggledStyleButton.tsx
index 98e2212842..8c537844e0 100644
--- a/packages/react/src/components/FormattingToolbar/DefaultButtons/ToggledStyleButton.tsx
+++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/ToggledStyleButton.tsx
@@ -15,7 +15,7 @@ import {
} from "react-icons/ri";
import { ToolbarButton } from "../../../components-shared/Toolbar/ToolbarButton";
-import { useEditorChange } from "../../../hooks/useEditorChange";
+import { useEditorContentOrSelectionChange } from "../../../hooks/useEditorContentOrSelectionChange";
import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks";
const shortcuts = {
@@ -48,9 +48,9 @@ export const ToggledStyleButton = <
props.toggledStyle in props.editor.getActiveStyles()
);
- useEditorChange(props.editor, () => {
+ useEditorContentOrSelectionChange(() => {
setActive(props.toggledStyle in props.editor.getActiveStyles());
- });
+ }, props.editor);
const toggleStyle = (style: typeof props.toggledStyle) => {
props.editor.focus();
diff --git a/packages/react/src/components/FormattingToolbar/DefaultDropdowns/BlockTypeDropdown.tsx b/packages/react/src/components/FormattingToolbar/DefaultDropdowns/BlockTypeDropdown.tsx
index f14c61490d..f1e8b92fae 100644
--- a/packages/react/src/components/FormattingToolbar/DefaultDropdowns/BlockTypeDropdown.tsx
+++ b/packages/react/src/components/FormattingToolbar/DefaultDropdowns/BlockTypeDropdown.tsx
@@ -12,7 +12,7 @@ import {
import { ToolbarDropdown } from "../../../components-shared/Toolbar/ToolbarDropdown";
import type { ToolbarDropdownItemProps } from "../../../components-shared/Toolbar/ToolbarDropdownItem";
-import { useEditorChange } from "../../../hooks/useEditorChange";
+import { useEditorContentOrSelectionChange } from "../../../hooks/useEditorContentOrSelectionChange";
import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks";
export type BlockTypeDropdownItem = {
@@ -138,9 +138,9 @@ export const BlockTypeDropdown = (props: {
}));
}, [block, filteredItems, props.editor, selectedBlocks]);
- useEditorChange(props.editor, () => {
+ useEditorContentOrSelectionChange(() => {
setBlock(props.editor.getTextCursorPosition().block);
- });
+ }, props.editor);
if (!shouldShow) {
return null;
diff --git a/packages/react/src/components/FormattingToolbar/FormattingToolbarPositioner.tsx b/packages/react/src/components/FormattingToolbar/FormattingToolbarPositioner.tsx
index 24f52f1937..008b65e14b 100644
--- a/packages/react/src/components/FormattingToolbar/FormattingToolbarPositioner.tsx
+++ b/packages/react/src/components/FormattingToolbar/FormattingToolbarPositioner.tsx
@@ -12,7 +12,7 @@ import {
} from "@floating-ui/react";
import { FC, useEffect, useRef, useState } from "react";
-import { useEditorChange } from "../../hooks/useEditorChange";
+import { useEditorContentOrSelectionChange } from "../../hooks/useEditorContentOrSelectionChange";
import { DefaultFormattingToolbar } from "./DefaultFormattingToolbar";
const textAlignmentToPlacement = (
@@ -77,7 +77,7 @@ export const FormattingToolbarPositioner = <
});
}, [props.editor, update]);
- useEditorChange(props.editor, () => {
+ useEditorContentOrSelectionChange(() => {
const block = props.editor.getTextCursorPosition().block;
if (!("textAlignment" in block.props)) {
@@ -89,7 +89,7 @@ export const FormattingToolbarPositioner = <
)
);
}
- });
+ }, props.editor);
useEffect(() => {
refs.setReference({
diff --git a/packages/react/src/editor/BlockNoteContext.ts b/packages/react/src/editor/BlockNoteContext.ts
new file mode 100644
index 0000000000..5de44dc28b
--- /dev/null
+++ b/packages/react/src/editor/BlockNoteContext.ts
@@ -0,0 +1,33 @@
+import {
+ BlockNoteEditor,
+ BlockSchema,
+ DefaultBlockSchema,
+ DefaultInlineContentSchema,
+ DefaultStyleSchema,
+ InlineContentSchema,
+ StyleSchema,
+} from "@blocknote/core";
+import { createContext, useContext } from "react";
+
+type BlockNoteContextValue<
+ BSchema extends BlockSchema = DefaultBlockSchema,
+ ISchema extends InlineContentSchema = DefaultInlineContentSchema,
+ SSchema extends StyleSchema = DefaultStyleSchema
+> = {
+ editor?: BlockNoteEditor;
+ colorSchemePreference?: "light" | "dark";
+};
+
+export const BlockNoteContext = createContext<
+ BlockNoteContextValue | undefined
+>(undefined);
+
+export function useBlockNoteContext<
+ BSchema extends BlockSchema = DefaultBlockSchema,
+ ISchema extends InlineContentSchema = DefaultInlineContentSchema,
+ SSchema extends StyleSchema = DefaultStyleSchema
+>(): BlockNoteContextValue | undefined {
+ const context = useContext(BlockNoteContext) as any;
+
+ return context;
+}
diff --git a/packages/react/src/editor/BlockNoteView.tsx b/packages/react/src/editor/BlockNoteView.tsx
index a7bb77fcfe..451a7749e7 100644
--- a/packages/react/src/editor/BlockNoteView.tsx
+++ b/packages/react/src/editor/BlockNoteView.tsx
@@ -2,24 +2,37 @@ import {
BlockNoteEditor,
BlockSchema,
InlineContentSchema,
- mergeCSSClasses,
StyleSchema,
+ mergeCSSClasses,
} from "@blocknote/core";
import { MantineProvider } from "@mantine/core";
-import { EditorContent } from "@tiptap/react";
-import { HTMLAttributes, ReactNode, useEffect, useState } from "react";
+
+import React, {
+ HTMLAttributes,
+ ReactNode,
+ Ref,
+ useCallback,
+ useEffect,
+ useMemo,
+ useState,
+} from "react";
import usePrefersColorScheme from "use-prefers-color-scheme";
-import {
- Theme,
- applyBlockNoteCSSVariablesFromTheme,
- removeBlockNoteCSSVariables,
-} from "./BlockNoteTheme";
import { FormattingToolbarPositioner } from "../components/FormattingToolbar/FormattingToolbarPositioner";
import { HyperlinkToolbarPositioner } from "../components/HyperlinkToolbar/HyperlinkToolbarPositioner";
import { ImageToolbarPositioner } from "../components/ImageToolbar/ImageToolbarPositioner";
import { SideMenuPositioner } from "../components/SideMenu/SideMenuPositioner";
import { SlashMenuPositioner } from "../components/SlashMenu/SlashMenuPositioner";
import { TableHandlesPositioner } from "../components/TableHandles/TableHandlePositioner";
+import { useEditorChange } from "../hooks/useEditorChange";
+import { useEditorSelectionChange } from "../hooks/useEditorSelectionChange";
+import { mergeRefs } from "../util/mergeRefs";
+import { BlockNoteContext, useBlockNoteContext } from "./BlockNoteContext";
+import {
+ Theme,
+ applyBlockNoteCSSVariablesFromTheme,
+ removeBlockNoteCSSVariables,
+} from "./BlockNoteTheme";
+import { EditorContent } from "./EditorContent";
import "./styles.css";
const mantineTheme = {
@@ -27,13 +40,18 @@ const mantineTheme = {
activeClassName: "",
};
-export function BlockNoteView<
+const emptyFn = (_editor: any) => {
+ // noop
+};
+
+function BlockNoteViewComponent<
BSchema extends BlockSchema,
ISchema extends InlineContentSchema,
SSchema extends StyleSchema
>(
props: {
editor: BlockNoteEditor;
+
theme?:
| "light"
| "dark"
@@ -42,73 +60,151 @@ export function BlockNoteView<
light: Theme;
dark: Theme;
};
+ /**
+ * Locks the editor from being editable by the user if set to `false`.
+ */
+ editable?: boolean;
+ /**
+ * A callback function that runs whenever the text cursor position or selection changes.
+ */
+ onSelectionChange?: (
+ editor: BlockNoteEditor
+ ) => void;
+
+ /**
+ * A callback function that runs whenever the editor's contents change.
+ */
+ onChange?: (editor: BlockNoteEditor) => void;
+
children?: ReactNode;
- } & HTMLAttributes
+
+ ref?: Ref | undefined; // only here to get types working with the generics. Regular form doesn't work
+ } & Omit<
+ HTMLAttributes,
+ "onChange" | "onSelectionChange" | "children"
+ >,
+ ref: React.Ref
) {
- const { editor, className, theme, children, ...rest } = props;
+ const {
+ editor,
+ className,
+ theme,
+ children,
+ editable,
+ onSelectionChange,
+ onChange,
+ ...rest
+ } = props;
+
+ const existingContext = useBlockNoteContext();
const systemColorScheme = usePrefersColorScheme();
+ const defaultColorScheme =
+ existingContext?.colorSchemePreference || systemColorScheme;
const [editorColorScheme, setEditorColorScheme] = useState<
"light" | "dark" | undefined
>(undefined);
- useEffect(() => {
- removeBlockNoteCSSVariables(editor.domElement.parentElement!);
+ const containerRef = useCallback(
+ (node: HTMLDivElement | null) => {
+ if (!node) {
+ // todo: clean variables?
+ return;
+ }
- if (theme === "light") {
- setEditorColorScheme("light");
- return;
- }
+ removeBlockNoteCSSVariables(node);
- if (theme === "dark") {
- setEditorColorScheme("dark");
- return;
- }
+ if (theme === "light") {
+ setEditorColorScheme("light");
+ return;
+ }
- if (typeof theme === "object") {
- if ("light" in theme && "dark" in theme) {
- applyBlockNoteCSSVariablesFromTheme(
- theme[systemColorScheme === "dark" ? "dark" : "light"],
- editor.domElement.parentElement!
- );
- setEditorColorScheme(systemColorScheme === "dark" ? "dark" : "light");
+ if (theme === "dark") {
+ setEditorColorScheme("dark");
return;
}
- applyBlockNoteCSSVariablesFromTheme(
- theme,
- editor.domElement.parentElement!
- );
- setEditorColorScheme(undefined);
- return;
+ if (typeof theme === "object") {
+ if ("light" in theme && "dark" in theme) {
+ applyBlockNoteCSSVariablesFromTheme(
+ theme[defaultColorScheme === "dark" ? "dark" : "light"],
+ node
+ );
+ setEditorColorScheme(
+ defaultColorScheme === "dark" ? "dark" : "light"
+ );
+ return;
+ }
+
+ applyBlockNoteCSSVariablesFromTheme(theme, node);
+ setEditorColorScheme(undefined);
+ return;
+ }
+
+ setEditorColorScheme(defaultColorScheme === "dark" ? "dark" : "light");
+ },
+ [defaultColorScheme, theme]
+ );
+
+ useEditorChange(onChange || emptyFn, editor);
+ useEditorSelectionChange(onSelectionChange || emptyFn, editor);
+
+ useEffect(() => {
+ if (editable === false) {
+ editor.isEditable = false;
+ } else {
+ editor.isEditable = true;
}
+ }, [editable, editor]);
+
+ const renderChildren = useMemo(() => {
+ return (
+ children || (
+ <>
+
+
+
+
+
+ {editor.blockSchema.table && (
+
+ )}
+ >
+ )
+ );
+ }, [editor, children]);
- setEditorColorScheme(systemColorScheme === "dark" ? "dark" : "light");
- }, [systemColorScheme, editor.domElement, theme]);
+ const context = useMemo(() => {
+ return {
+ ...existingContext,
+ editor,
+ };
+ }, [existingContext, editor]);
+
+ const refs = useMemo(() => {
+ return mergeRefs([containerRef, editor._tiptapEditor.mount, ref]);
+ }, [containerRef, editor._tiptapEditor.mount, ref]);
return (
// `cssVariablesSelector` scopes Mantine CSS variables to only the editor,
// as proposed here: https://github.com/orgs/mantinedev/discussions/5685
-
- {children || (
- <>
-
-
-
-
-
- {editor.blockSchema.table && (
-
- )}
- >
- )}
-
+
+
+
+ {renderChildren}
+
+
+
);
}
+
+export const BlockNoteView = React.forwardRef(
+ BlockNoteViewComponent
+) as typeof BlockNoteViewComponent; // need hack to get types working with generics
diff --git a/packages/react/src/editor/EditorContent.tsx b/packages/react/src/editor/EditorContent.tsx
index 7bcccbc6cd..1610a0af94 100644
--- a/packages/react/src/editor/EditorContent.tsx
+++ b/packages/react/src/editor/EditorContent.tsx
@@ -1,2 +1,58 @@
-// BlockNote uses a similar pattern as Tiptap, so for now we can just export that
-export { EditorContent } from "@tiptap/react";
+import { BlockNoteEditor } from "@blocknote/core";
+import { ReactRenderer } from "@tiptap/react";
+import { useEffect, useState } from "react";
+import { createPortal } from "react-dom";
+
+const Portals: React.FC<{ renderers: Record }> = ({
+ renderers,
+}) => {
+ return (
+ <>
+ {Object.entries(renderers).map(([key, renderer]) => {
+ return createPortal(renderer.reactElement, renderer.element, key);
+ })}
+ >
+ );
+};
+
+/**
+ * Replacement of https://github.com/ueberdosis/tiptap/blob/6676c7e034a46117afdde560a1b25fe75411a21d/packages/react/src/EditorContent.tsx
+ * that only takes care of the Portals.
+ *
+ * Original implementation is messy, and we use a "mount" system in BlockNoteTiptapEditor.tsx that makes this cleaner
+ */
+export function EditorContent(props: {
+ editor: BlockNoteEditor;
+ children: any;
+}) {
+ const [renderers, setRenderers] = useState>({});
+
+ useEffect(() => {
+ props.editor._tiptapEditor.contentComponent = {
+ setRenderer(id: string, renderer: ReactRenderer) {
+ setRenderers((renderers) => ({ ...renderers, [id]: renderer }));
+ },
+
+ removeRenderer(id: string) {
+ setRenderers((renderers) => {
+ const nextRenderers = { ...renderers };
+
+ delete nextRenderers[id];
+
+ return nextRenderers;
+ });
+ },
+ };
+ props.editor._tiptapEditor.createNodeViews();
+ return () => {
+ props.editor._tiptapEditor.contentComponent = null;
+ };
+ }, [props.editor._tiptapEditor]);
+
+ return (
+ <>
+
+ {props.children}
+ >
+ );
+}
diff --git a/packages/react/src/hooks/useActiveStyles.ts b/packages/react/src/hooks/useActiveStyles.ts
index 93e4ad41ac..b53a3a26e8 100644
--- a/packages/react/src/hooks/useActiveStyles.ts
+++ b/packages/react/src/hooks/useActiveStyles.ts
@@ -1,6 +1,6 @@
import { BlockNoteEditor, StyleSchema } from "@blocknote/core";
import { useState } from "react";
-import { useEditorContentChange } from "./useEditorContentChange";
+import { useEditorChange } from "./useEditorChange";
import { useEditorSelectionChange } from "./useEditorSelectionChange";
export function useActiveStyles(
@@ -9,14 +9,14 @@ export function useActiveStyles(
const [styles, setStyles] = useState(() => editor.getActiveStyles());
// Updates state on editor content change.
- useEditorContentChange(editor, () => {
+ useEditorChange((editor) => {
setStyles(editor.getActiveStyles());
- });
+ }, editor);
// Updates state on selection change.
- useEditorSelectionChange(editor, () => {
+ useEditorSelectionChange(() => {
setStyles(editor.getActiveStyles());
- });
+ }, editor);
return styles;
}
diff --git a/packages/react/src/hooks/useBlockNote.ts b/packages/react/src/hooks/useBlockNote.ts
index 996d42672c..a1f32437ee 100644
--- a/packages/react/src/hooks/useBlockNote.ts
+++ b/packages/react/src/hooks/useBlockNote.ts
@@ -1,21 +1,19 @@
import {
BlockNoteEditor,
BlockNoteEditorOptions,
- BlockSchemaFromSpecs,
BlockSpecs,
- InlineContentSchemaFromSpecs,
InlineContentSpecs,
- StyleSchemaFromSpecs,
StyleSpecs,
defaultBlockSpecs,
defaultInlineContentSpecs,
defaultStyleSpecs,
getBlockSchemaFromSpecs,
} from "@blocknote/core";
-import { DependencyList, useMemo, useRef } from "react";
+import { DependencyList, useMemo } from "react";
import { getDefaultReactSlashMenuItems } from "../slashMenuItems/defaultReactSlashMenuItems";
-const initEditor = <
+// TODO: document in docs
+export const createBlockNoteEditor = <
BSpecs extends BlockSpecs,
ISpecs extends InlineContentSpecs,
SSpecs extends StyleSpecs
@@ -31,6 +29,8 @@ const initEditor = <
/**
* Main hook for importing a BlockNote editor into a React project
+ *
+ * TODO: document in docs
*/
export const useBlockNote = <
BSpecs extends BlockSpecs = typeof defaultBlockSpecs,
@@ -40,25 +40,12 @@ export const useBlockNote = <
options: Partial> = {},
deps: DependencyList = []
) => {
- const editorRef =
- useRef<
- BlockNoteEditor<
- BlockSchemaFromSpecs,
- InlineContentSchemaFromSpecs,
- StyleSchemaFromSpecs
- >
- >();
-
return useMemo(() => {
- if (editorRef.current) {
- editorRef.current._tiptapEditor.destroy();
- }
-
- editorRef.current = initEditor(options);
+ const editor = createBlockNoteEditor(options);
if (window) {
// for testing / dev purposes
- (window as any).ProseMirror = editorRef.current._tiptapEditor;
+ (window as any).ProseMirror = editor._tiptapEditor;
}
- return editorRef.current!;
+ return editor;
}, deps); //eslint-disable-line react-hooks/exhaustive-deps
};
diff --git a/packages/react/src/hooks/useEditorChange.ts b/packages/react/src/hooks/useEditorChange.ts
index 517f980205..d4e81f8114 100644
--- a/packages/react/src/hooks/useEditorChange.ts
+++ b/packages/react/src/hooks/useEditorChange.ts
@@ -1,11 +1,23 @@
import type { BlockNoteEditor } from "@blocknote/core";
-import { useEditorContentChange } from "./useEditorContentChange";
-import { useEditorSelectionChange } from "./useEditorSelectionChange";
+import { useEffect } from "react";
+import { useBlockNoteContext } from "../editor/BlockNoteContext";
export function useEditorChange(
- editor: BlockNoteEditor,
- callback: () => void
+ callback: (editor: BlockNoteEditor) => void,
+ editor?: BlockNoteEditor
) {
- useEditorContentChange(editor, callback);
- useEditorSelectionChange(editor, callback);
+ const editorContext = useBlockNoteContext();
+ if (!editor) {
+ editor = editorContext?.editor;
+ }
+
+ useEffect(() => {
+ if (!editor) {
+ throw new Error(
+ "'editor' is required, either from BlockNoteContext or as a function argument"
+ );
+ }
+
+ return editor.onChange(callback);
+ }, [callback, editor]);
}
diff --git a/packages/react/src/hooks/useEditorContentChange.ts b/packages/react/src/hooks/useEditorContentChange.ts
deleted file mode 100644
index 2922258a60..0000000000
--- a/packages/react/src/hooks/useEditorContentChange.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { BlockNoteEditor } from "@blocknote/core";
-import { useEffect } from "react";
-
-export function useEditorContentChange(
- editor: BlockNoteEditor,
- callback: () => void
-) {
- useEffect(() => {
- editor._tiptapEditor.on("update", callback);
-
- return () => {
- editor._tiptapEditor.off("update", callback);
- };
- }, [callback, editor._tiptapEditor]);
-}
diff --git a/packages/react/src/hooks/useEditorContentOrSelectionChange.ts b/packages/react/src/hooks/useEditorContentOrSelectionChange.ts
new file mode 100644
index 0000000000..ca48c68fb9
--- /dev/null
+++ b/packages/react/src/hooks/useEditorContentOrSelectionChange.ts
@@ -0,0 +1,11 @@
+import type { BlockNoteEditor } from "@blocknote/core";
+import { useEditorChange } from "./useEditorChange";
+import { useEditorSelectionChange } from "./useEditorSelectionChange";
+
+export function useEditorContentOrSelectionChange(
+ callback: (editor: BlockNoteEditor) => void,
+ editor?: BlockNoteEditor
+) {
+ useEditorChange(callback, editor);
+ useEditorSelectionChange(callback, editor);
+}
diff --git a/packages/react/src/hooks/useEditorSelectionChange.ts b/packages/react/src/hooks/useEditorSelectionChange.ts
index dab5c227cf..ff9d298bd9 100644
--- a/packages/react/src/hooks/useEditorSelectionChange.ts
+++ b/packages/react/src/hooks/useEditorSelectionChange.ts
@@ -1,15 +1,22 @@
import type { BlockNoteEditor } from "@blocknote/core";
import { useEffect } from "react";
+import { useBlockNoteContext } from "../editor/BlockNoteContext";
export function useEditorSelectionChange(
- editor: BlockNoteEditor,
- callback: () => void
+ callback: (editor: BlockNoteEditor) => void,
+ editor?: BlockNoteEditor
) {
- useEffect(() => {
- editor._tiptapEditor.on("selectionUpdate", callback);
+ const editorContext = useBlockNoteContext();
+ if (!editor) {
+ editor = editorContext?.editor;
+ }
- return () => {
- editor._tiptapEditor.off("selectionUpdate", callback);
- };
- }, [callback, editor._tiptapEditor]);
+ useEffect(() => {
+ if (!editor) {
+ throw new Error(
+ "'editor' is required, either from BlockNoteContext or as a function argument"
+ );
+ }
+ return editor.onSelectionChange(callback);
+ }, [callback, editor]);
}
diff --git a/packages/react/src/hooks/useSelectedBlocks.ts b/packages/react/src/hooks/useSelectedBlocks.ts
index 63ab136ed7..49d90dece0 100644
--- a/packages/react/src/hooks/useSelectedBlocks.ts
+++ b/packages/react/src/hooks/useSelectedBlocks.ts
@@ -6,24 +6,37 @@ import {
StyleSchema,
} from "@blocknote/core";
import { useState } from "react";
-import { useEditorChange } from "./useEditorChange";
+import { useBlockNoteContext } from "../editor/BlockNoteContext";
+import { useEditorContentOrSelectionChange } from "./useEditorContentOrSelectionChange";
export function useSelectedBlocks<
BSchema extends BlockSchema,
ISchema extends InlineContentSchema,
SSchema extends StyleSchema
->(editor: BlockNoteEditor) {
+>(editor?: BlockNoteEditor) {
+ const editorContext = useBlockNoteContext();
+ if (!editor) {
+ editor = editorContext?.editor;
+ }
+
+ if (!editor) {
+ throw new Error(
+ "'editor' is required, either from BlockNoteContext or as a function argument"
+ );
+ }
+
+ const e = editor;
+
const [selectedBlocks, setSelectedBlocks] = useState<
Block[]
- >(
- () =>
- editor.getSelection()?.blocks || [editor.getTextCursorPosition().block]
- );
+ >(() => e.getSelection()?.blocks || [e.getTextCursorPosition().block]);
- useEditorChange(editor, () =>
- setSelectedBlocks(
- editor.getSelection()?.blocks || [editor.getTextCursorPosition().block]
- )
+ useEditorContentOrSelectionChange(
+ () =>
+ setSelectedBlocks(
+ e.getSelection()?.blocks || [e.getTextCursorPosition().block]
+ ),
+ e
);
return selectedBlocks;
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 258765b26b..1cd5d0e958 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -1,4 +1,5 @@
// TODO: review directories
+export * from "./editor/BlockNoteContext";
export * from "./editor/BlockNoteTheme";
export * from "./editor/BlockNoteView";
export * from "./editor/defaultThemes";
@@ -29,10 +30,10 @@ export * from "./components/SideMenu/DragHandleMenu/DefaultDragHandleMenu";
export * from "./components/SideMenu/DragHandleMenu/DragHandleMenu";
export * from "./components/SideMenu/DragHandleMenu/DragHandleMenuItem";
-export * from "./slashMenuItems/ReactSlashMenuItem";
export * from "./components/SlashMenu/DefaultSlashMenu";
export * from "./components/SlashMenu/SlashMenuItem";
export * from "./components/SlashMenu/SlashMenuPositioner";
+export * from "./slashMenuItems/ReactSlashMenuItem";
export * from "./slashMenuItems/defaultReactSlashMenuItems";
export * from "./components/ImageToolbar/DefaultImageToolbar";
@@ -48,7 +49,7 @@ export * from "./components-shared/Toolbar/ToolbarDropdown";
export * from "./hooks/useActiveStyles";
export * from "./hooks/useBlockNote";
export * from "./hooks/useEditorChange";
-export * from "./hooks/useEditorContentChange";
+export * from "./hooks/useEditorContentOrSelectionChange";
export * from "./hooks/useEditorForceUpdate";
export * from "./hooks/useEditorSelectionChange";
export * from "./hooks/useSelectedBlocks";
diff --git a/packages/react/src/util/mergeRefs.ts b/packages/react/src/util/mergeRefs.ts
new file mode 100644
index 0000000000..969732f585
--- /dev/null
+++ b/packages/react/src/util/mergeRefs.ts
@@ -0,0 +1,14 @@
+// https://github.com/gregberge/react-merge-refs/blob/main/src/index.tsx
+export function mergeRefs(
+ refs: Array | React.LegacyRef | undefined | null>
+): React.RefCallback {
+ return (value) => {
+ refs.forEach((ref) => {
+ if (typeof ref === "function") {
+ ref(value);
+ } else if (ref != null) {
+ (ref as React.MutableRefObject).current = value;
+ }
+ });
+ };
+}
diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx
index aff1e2ec96..1656f1d339 100644
--- a/playground/src/examples.gen.tsx
+++ b/playground/src/examples.gen.tsx
@@ -11,9 +11,7 @@
"pathFromRoot": "examples/01-basic/01-minimal",
"config": {
"playground": true,
- "docs": false,
- "group": "Basic Examples",
- "order": 1
+ "docs": true
},
"title": "Basic Editor Setup",
"group": {
@@ -21,15 +19,27 @@
"slug": "basic"
}
},
+ {
+ "projectSlug": "block-objects",
+ "fullSlug": "basic/block-objects",
+ "pathFromRoot": "examples/01-basic/02-block-objects",
+ "config": {
+ "playground": true,
+ "docs": true
+ },
+ "title": "Displaying Block Objects",
+ "group": {
+ "pathFromRoot": "examples/01-basic",
+ "slug": "basic"
+ }
+ },
{
"projectSlug": "block-manipulation",
"fullSlug": "basic/block-manipulation",
- "pathFromRoot": "examples/01-basic/block-manipulation",
+ "pathFromRoot": "examples/01-basic/03-block-manipulation",
"config": {
"playground": true,
- "docs": true,
- "group": "Basic Examples",
- "order": 1
+ "docs": true
},
"title": "Block Manipulation",
"group": {
@@ -38,48 +48,42 @@
}
},
{
- "projectSlug": "block-objects",
- "fullSlug": "basic/block-objects",
- "pathFromRoot": "examples/01-basic/block-objects",
+ "projectSlug": "saving-loading",
+ "fullSlug": "basic/saving-loading",
+ "pathFromRoot": "examples/01-basic/04-saving-loading",
"config": {
"playground": true,
- "docs": false,
- "group": "Basic Examples",
- "order": 1
+ "docs": true
},
- "title": "Displaying Block Objects",
+ "title": "Saving & Loading",
"group": {
"pathFromRoot": "examples/01-basic",
"slug": "basic"
}
},
{
- "projectSlug": "keyboard-shortcuts",
- "fullSlug": "basic/keyboard-shortcuts",
- "pathFromRoot": "examples/01-basic/keyboard-shortcuts",
+ "projectSlug": "file-uploading",
+ "fullSlug": "basic/file-uploading",
+ "pathFromRoot": "examples/01-basic/05-file-uploading",
"config": {
"playground": true,
- "docs": true,
- "group": "Basic Examples",
- "order": 5
+ "docs": true
},
- "title": "Keyboard Shortcuts",
+ "title": "File / image uploading",
"group": {
"pathFromRoot": "examples/01-basic",
"slug": "basic"
}
},
{
- "projectSlug": "saving-loading",
- "fullSlug": "basic/saving-loading",
- "pathFromRoot": "examples/01-basic/saving-loading",
+ "projectSlug": "keyboard-shortcuts",
+ "fullSlug": "basic/keyboard-shortcuts",
+ "pathFromRoot": "examples/01-basic/06-keyboard-shortcuts",
"config": {
"playground": true,
- "docs": true,
- "group": "Basic Examples",
- "order": 2
+ "docs": true
},
- "title": "Saving & Loading",
+ "title": "Keyboard Shortcuts",
"group": {
"pathFromRoot": "examples/01-basic",
"slug": "basic"
@@ -356,64 +360,56 @@
"title": "Interoperability",
"projects": [
{
- "projectSlug": "converting-blocks-from-html",
- "fullSlug": "interoperability/converting-blocks-from-html",
- "pathFromRoot": "examples/08-interoperability/converting-blocks-from-html",
+ "projectSlug": "converting-blocks-to-html",
+ "fullSlug": "interoperability/converting-blocks-to-html",
+ "pathFromRoot": "examples/08-interoperability/01-converting-blocks-to-html",
"config": {
"playground": true,
- "docs": false,
- "group": "Basic Examples",
- "order": 1
+ "docs": true
},
- "title": "Converting HTML to Blocks",
+ "title": "Converting Blocks to HTML",
"group": {
"pathFromRoot": "examples/08-interoperability",
"slug": "interoperability"
}
},
{
- "projectSlug": "converting-blocks-from-md",
- "fullSlug": "interoperability/converting-blocks-from-md",
- "pathFromRoot": "examples/08-interoperability/converting-blocks-from-md",
+ "projectSlug": "converting-blocks-from-html",
+ "fullSlug": "interoperability/converting-blocks-from-html",
+ "pathFromRoot": "examples/08-interoperability/02-converting-blocks-from-html",
"config": {
"playground": true,
- "docs": false,
- "group": "Basic Examples",
- "order": 1
+ "docs": true
},
- "title": "Converting Markdown to Blocks",
+ "title": "Parsing HTML to Blocks",
"group": {
"pathFromRoot": "examples/08-interoperability",
"slug": "interoperability"
}
},
{
- "projectSlug": "converting-blocks-to-html",
- "fullSlug": "interoperability/converting-blocks-to-html",
- "pathFromRoot": "examples/08-interoperability/converting-blocks-to-html",
+ "projectSlug": "converting-blocks-to-md",
+ "fullSlug": "interoperability/converting-blocks-to-md",
+ "pathFromRoot": "examples/08-interoperability/03-converting-blocks-to-md",
"config": {
"playground": true,
- "docs": false,
- "group": "Basic Examples",
- "order": 1
+ "docs": true
},
- "title": "Converting Blocks to HTML",
+ "title": "Converting Blocks to Markdown",
"group": {
"pathFromRoot": "examples/08-interoperability",
"slug": "interoperability"
}
},
{
- "projectSlug": "converting-blocks-to-md",
- "fullSlug": "interoperability/converting-blocks-to-md",
- "pathFromRoot": "examples/08-interoperability/converting-blocks-to-md",
+ "projectSlug": "converting-blocks-from-md",
+ "fullSlug": "interoperability/converting-blocks-from-md",
+ "pathFromRoot": "examples/08-interoperability/04-converting-blocks-from-md",
"config": {
"playground": true,
- "docs": false,
- "group": "Basic Examples",
- "order": 1
+ "docs": true
},
- "title": "Converting Blocks to Markdown",
+ "title": "Parsing Markdown to Blocks",
"group": {
"pathFromRoot": "examples/08-interoperability",
"slug": "interoperability"
diff --git a/playground/src/main.tsx b/playground/src/main.tsx
index ee0ca4f580..a5332cf86b 100644
--- a/playground/src/main.tsx
+++ b/playground/src/main.tsx
@@ -118,9 +118,7 @@ const router = createBrowserRouter([
const root = createRoot(document.getElementById("root")!);
root.render(
- // TODO: StrictMode is causing duplicate mounts and conflicts with collaboration
- //
- //
-
- //
+
+
+
);
diff --git a/playground/src/style.css b/playground/src/style.css
index 2d6f7390c7..67f8b81395 100644
--- a/playground/src/style.css
+++ b/playground/src/style.css
@@ -5,13 +5,9 @@ body {
}
.bn-container {
- margin-top: 8px;
+ margin: 8px calc((100% - 731px) / 2) 0;
}
.mantine-AppShell-navbar {
background-color: #f7f7f5;
}
-
-.editor {
- margin: 8px calc((100% - 731px) / 2) 0;
-}
diff --git a/tests/src/end-to-end/basics/basics.test.ts b/tests/src/end-to-end/basics/basics.test.ts
index e835649b06..bbe16e2ae9 100644
--- a/tests/src/end-to-end/basics/basics.test.ts
+++ b/tests/src/end-to-end/basics/basics.test.ts
@@ -1,6 +1,6 @@
import { expect } from "@playwright/test";
import { test } from "../../setup/setupScript";
-import { BASE_URL } from "../../utils/const";
+import { BASE_URL, EDITOR_SELECTOR } from "../../utils/const";
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL);
@@ -9,7 +9,10 @@ test.beforeEach(async ({ page }) => {
test.describe("Basic typing functionality", () => {
test("should allow me to type content", async ({ page }) => {
const editor = await page.waitForSelector("[data-test='editor']");
- await page.locator('[data-test="editor"] div').nth(3).click();
+ await page
+ .locator(EDITOR_SELECTOR + " div")
+ .nth(3)
+ .click();
await page.keyboard.insertText("hello world");
// await page.pause();
expect(await editor.textContent()).toBe("hello world");
diff --git a/tests/src/end-to-end/placeholder/placeholder.test.ts b/tests/src/end-to-end/placeholder/placeholder.test.ts
index 2c93a19608..dc1be47610 100644
--- a/tests/src/end-to-end/placeholder/placeholder.test.ts
+++ b/tests/src/end-to-end/placeholder/placeholder.test.ts
@@ -1,6 +1,6 @@
import { expect } from "@playwright/test";
import { test } from "../../setup/setupScript";
-import { BASE_URL } from "../../utils/const";
+import { BASE_URL, EDITOR_SELECTOR } from "../../utils/const";
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL);
@@ -9,7 +9,10 @@ test.beforeEach(async ({ page }) => {
test.describe("Basic placeholder functionality", () => {
test("should show placeholder on load", async ({ page }) => {
// const editor = await page.waitForSelector("[data-test='editor']");
- await page.locator('[data-test="editor"] div').nth(3).hover();
+ await page
+ .locator(EDITOR_SELECTOR + " div")
+ .nth(3)
+ .hover();
// TODO: doesn't work. No way to access text of ::before element?
// expect(await editor.textContent()).toBe(
diff --git a/tests/src/utils/components/Editor.tsx b/tests/src/utils/components/Editor.tsx
index b70567a89f..39c072d177 100644
--- a/tests/src/utils/components/Editor.tsx
+++ b/tests/src/utils/components/Editor.tsx
@@ -10,7 +10,6 @@ import { Button, insertButton } from "../customblocks/Button";
import { Embed, insertEmbed } from "../customblocks/Embed";
import { Image, insertImage } from "../customblocks/Image";
import { Separator, insertSeparator } from "../customblocks/Separator";
-import styles from "./Editor.module.css";
export default function Editor() {
const blockSpecs = {
@@ -33,9 +32,6 @@ export default function Editor() {
];
const editor = useBlockNote({
- domAttributes: {
- editor: { class: styles.editor, "data-test": "editor" },
- },
blockSpecs,
slashMenuItems: [...getDefaultReactSlashMenuItems(), ...slashMenuItems],
});
diff --git a/tests/src/utils/const.ts b/tests/src/utils/const.ts
index bb536f8083..95cdee36da 100644
--- a/tests/src/utils/const.ts
+++ b/tests/src/utils/const.ts
@@ -5,7 +5,7 @@ export const BASE_URL = !process.env.RUN_IN_DOCKER
export const PASTE_ZONE_SELECTOR = "#pasteZone";
-export const EDITOR_SELECTOR = `[data-test="editor"]`;
+export const EDITOR_SELECTOR = `.bn-editor`;
export const BLOCK_CONTAINER_SELECTOR = `[data-node-type="blockContainer"]`;
export const BLOCK_GROUP_SELECTOR = `[data-node-type="blockGroup"]`;