diff --git a/docs/package.json b/docs/package.json
index 0ffbc251cc..bcd31a57ea 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -131,4 +131,4 @@
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3"
}
-}
+}
\ No newline at end of file
diff --git a/examples/01-basic/03-multi-column/package.json b/examples/01-basic/03-multi-column/package.json
index 482c7409d8..590b6adc07 100644
--- a/examples/01-basic/03-multi-column/package.json
+++ b/examples/01-basic/03-multi-column/package.json
@@ -16,12 +16,12 @@
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
- "@blocknote/xl-multi-column": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@blocknote/xl-multi-column": "latest"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/02-backend/03-s3/package.json b/examples/02-backend/03-s3/package.json
index 32f939f061..2c2c249b08 100644
--- a/examples/02-backend/03-s3/package.json
+++ b/examples/02-backend/03-s3/package.json
@@ -11,8 +11,6 @@
"preview": "vite preview"
},
"dependencies": {
- "@aws-sdk/client-s3": "^3.609.0",
- "@aws-sdk/s3-request-presigner": "^3.609.0",
"@blocknote/ariakit": "latest",
"@blocknote/core": "latest",
"@blocknote/mantine": "latest",
@@ -22,7 +20,9 @@
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@aws-sdk/client-s3": "^3.609.0",
+ "@aws-sdk/s3-request-presigner": "^3.609.0"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/02-backend/04-rendering-static-documents/package.json b/examples/02-backend/04-rendering-static-documents/package.json
index 14c1dd6aa7..41aeab8c5c 100644
--- a/examples/02-backend/04-rendering-static-documents/package.json
+++ b/examples/02-backend/04-rendering-static-documents/package.json
@@ -15,13 +15,13 @@
"@blocknote/core": "latest",
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
- "@blocknote/server-util": "latest",
"@blocknote/shadcn": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@blocknote/server-util": "latest"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/03-ui-components/11-uppy-file-panel/package.json b/examples/03-ui-components/11-uppy-file-panel/package.json
index d65faa9792..5523df0951 100644
--- a/examples/03-ui-components/11-uppy-file-panel/package.json
+++ b/examples/03-ui-components/11-uppy-file-panel/package.json
@@ -19,6 +19,8 @@
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
+ "react": "^19.2.3",
+ "react-dom": "^19.2.3",
"@uppy/core": "^3.13.1",
"@uppy/dashboard": "^3.9.1",
"@uppy/drag-drop": "^3.1.1",
@@ -30,8 +32,6 @@
"@uppy/status-bar": "^3.1.1",
"@uppy/webcam": "^3.4.2",
"@uppy/xhr-upload": "^3.4.0",
- "react": "^19.2.3",
- "react-dom": "^19.2.3",
"react-icons": "^5.5.0"
},
"devDependencies": {
diff --git a/examples/03-ui-components/13-custom-ui/package.json b/examples/03-ui-components/13-custom-ui/package.json
index 1c45dea04a..97585870e0 100644
--- a/examples/03-ui-components/13-custom-ui/package.json
+++ b/examples/03-ui-components/13-custom-ui/package.json
@@ -19,10 +19,10 @@
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
- "@mui/icons-material": "^5.16.1",
- "@mui/material": "^5.16.1",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@mui/icons-material": "^5.16.1",
+ "@mui/material": "^5.16.1"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/04-theming/06-code-block/package.json b/examples/04-theming/06-code-block/package.json
index dd4d7472f5..037641e740 100644
--- a/examples/04-theming/06-code-block/package.json
+++ b/examples/04-theming/06-code-block/package.json
@@ -12,7 +12,6 @@
},
"dependencies": {
"@blocknote/ariakit": "latest",
- "@blocknote/code-block": "latest",
"@blocknote/core": "latest",
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
@@ -21,7 +20,8 @@
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@blocknote/code-block": "latest"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/04-theming/07-custom-code-block/.bnexample.json b/examples/04-theming/07-custom-code-block/.bnexample.json
index 5776d2de67..84166710e3 100644
--- a/examples/04-theming/07-custom-code-block/.bnexample.json
+++ b/examples/04-theming/07-custom-code-block/.bnexample.json
@@ -5,10 +5,10 @@
"tags": ["Basic"],
"dependencies": {
"@blocknote/code-block": "latest",
- "@shikijs/core": "^3.19.0",
- "@shikijs/engine-javascript": "^3.19.0",
- "@shikijs/langs-precompiled": "^3.19.0",
- "@shikijs/themes": "^3.19.0",
- "@shikijs/types": "^3.19.0"
+ "@shikijs/core": "^4",
+ "@shikijs/engine-javascript": "^4",
+ "@shikijs/langs-precompiled": "^4",
+ "@shikijs/themes": "^4",
+ "@shikijs/types": "^4"
}
}
diff --git a/examples/04-theming/07-custom-code-block/package.json b/examples/04-theming/07-custom-code-block/package.json
index df3d173512..d381ab18f1 100644
--- a/examples/04-theming/07-custom-code-block/package.json
+++ b/examples/04-theming/07-custom-code-block/package.json
@@ -12,7 +12,6 @@
},
"dependencies": {
"@blocknote/ariakit": "latest",
- "@blocknote/code-block": "latest",
"@blocknote/core": "latest",
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
@@ -20,13 +19,14 @@
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
+ "react": "^19.2.3",
+ "react-dom": "^19.2.3",
+ "@blocknote/code-block": "latest",
"@shikijs/core": "^4",
"@shikijs/engine-javascript": "^4",
"@shikijs/langs-precompiled": "^4",
"@shikijs/themes": "^4",
- "@shikijs/types": "^4",
- "react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "@shikijs/types": "^4"
},
"devDependencies": {
"@types/react": "^19.2.3",
@@ -34,4 +34,4 @@
"@vitejs/plugin-react": "^6.0.1",
"vite": "^8.0.8"
}
-}
+}
\ No newline at end of file
diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/package.json b/examples/05-interoperability/05-converting-blocks-to-pdf/package.json
index a470b3892e..f402e292d7 100644
--- a/examples/05-interoperability/05-converting-blocks-to-pdf/package.json
+++ b/examples/05-interoperability/05-converting-blocks-to-pdf/package.json
@@ -16,14 +16,14 @@
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
- "@blocknote/xl-multi-column": "latest",
- "@blocknote/xl-pdf-exporter": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
- "@react-pdf/renderer": "^4.3.0",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@blocknote/xl-pdf-exporter": "latest",
+ "@blocknote/xl-multi-column": "latest",
+ "@react-pdf/renderer": "^4.3.0"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/05-interoperability/06-converting-blocks-to-docx/package.json b/examples/05-interoperability/06-converting-blocks-to-docx/package.json
index dead9892bb..9f29639db1 100644
--- a/examples/05-interoperability/06-converting-blocks-to-docx/package.json
+++ b/examples/05-interoperability/06-converting-blocks-to-docx/package.json
@@ -16,13 +16,13 @@
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
- "@blocknote/xl-docx-exporter": "latest",
- "@blocknote/xl-multi-column": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@blocknote/xl-docx-exporter": "latest",
+ "@blocknote/xl-multi-column": "latest"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/05-interoperability/07-converting-blocks-to-odt/package.json b/examples/05-interoperability/07-converting-blocks-to-odt/package.json
index afc26ae220..4c30dd0d65 100644
--- a/examples/05-interoperability/07-converting-blocks-to-odt/package.json
+++ b/examples/05-interoperability/07-converting-blocks-to-odt/package.json
@@ -16,13 +16,13 @@
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
- "@blocknote/xl-multi-column": "latest",
- "@blocknote/xl-odt-exporter": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@blocknote/xl-odt-exporter": "latest",
+ "@blocknote/xl-multi-column": "latest"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/05-interoperability/08-converting-blocks-to-react-email/package.json b/examples/05-interoperability/08-converting-blocks-to-react-email/package.json
index d5a60f1f1e..15b4292fd7 100644
--- a/examples/05-interoperability/08-converting-blocks-to-react-email/package.json
+++ b/examples/05-interoperability/08-converting-blocks-to-react-email/package.json
@@ -16,13 +16,13 @@
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
- "@blocknote/xl-email-exporter": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
- "@react-email/render": "^2.0.4",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@blocknote/xl-email-exporter": "latest",
+ "@react-email/render": "^2.0.4"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/07-collaboration/02-liveblocks/package.json b/examples/07-collaboration/02-liveblocks/package.json
index 8da1946fac..52e5f94320 100644
--- a/examples/07-collaboration/02-liveblocks/package.json
+++ b/examples/07-collaboration/02-liveblocks/package.json
@@ -16,16 +16,16 @@
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
- "@liveblocks/client": "^3.17.0",
- "@liveblocks/react": "^3.17.0",
- "@liveblocks/react-blocknote": "^3.17.0",
- "@liveblocks/react-tiptap": "^3.17.0",
- "@liveblocks/react-ui": "^3.17.0",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
"react": "^19.2.3",
"react-dom": "^19.2.3",
+ "@liveblocks/client": "^3.17.0",
+ "@liveblocks/react": "^3.17.0",
+ "@liveblocks/react-blocknote": "^3.17.0",
+ "@liveblocks/react-tiptap": "^3.17.0",
+ "@liveblocks/react-ui": "^3.17.0",
"yjs": "^13.6.27"
},
"devDependencies": {
diff --git a/examples/07-collaboration/03-y-sweet/package.json b/examples/07-collaboration/03-y-sweet/package.json
index 8270d8195d..e1dcc579ca 100644
--- a/examples/07-collaboration/03-y-sweet/package.json
+++ b/examples/07-collaboration/03-y-sweet/package.json
@@ -19,9 +19,9 @@
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
- "@y-sweet/react": "^0.6.3",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@y-sweet/react": "^0.6.3"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/07-collaboration/05-comments/package.json b/examples/07-collaboration/05-comments/package.json
index 7f675d3e2d..e1902158f5 100644
--- a/examples/07-collaboration/05-comments/package.json
+++ b/examples/07-collaboration/05-comments/package.json
@@ -19,9 +19,9 @@
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
- "@y-sweet/react": "^0.6.3",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@y-sweet/react": "^0.6.3"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/07-collaboration/09-comments-testing/.bnexample.json b/examples/07-collaboration/09-comments-testing/.bnexample.json
new file mode 100644
index 0000000000..5d7d986420
--- /dev/null
+++ b/examples/07-collaboration/09-comments-testing/.bnexample.json
@@ -0,0 +1,9 @@
+{
+ "playground": true,
+ "docs": false,
+ "author": "matthewlipski",
+ "tags": ["Advanced", "Comments", "Testing"],
+ "dependencies": {
+ "yjs": "^13.6.27"
+ }
+}
diff --git a/examples/07-collaboration/09-comments-testing/README.md b/examples/07-collaboration/09-comments-testing/README.md
new file mode 100644
index 0000000000..b59f2ecd1b
--- /dev/null
+++ b/examples/07-collaboration/09-comments-testing/README.md
@@ -0,0 +1,3 @@
+# Comments Testing
+
+A minimal comments example used for end-to-end testing. Uses a local Y.Doc (no collaboration provider) with a single hardcoded editor user.
diff --git a/examples/07-collaboration/09-comments-testing/index.html b/examples/07-collaboration/09-comments-testing/index.html
new file mode 100644
index 0000000000..f50976be79
--- /dev/null
+++ b/examples/07-collaboration/09-comments-testing/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+ Comments Testing
+
+
+
+
+
+
+
diff --git a/examples/07-collaboration/09-comments-testing/main.tsx b/examples/07-collaboration/09-comments-testing/main.tsx
new file mode 100644
index 0000000000..677c7f7eed
--- /dev/null
+++ b/examples/07-collaboration/09-comments-testing/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 "./src/App.jsx";
+
+const root = createRoot(document.getElementById("root")!);
+root.render(
+
+
+
+);
diff --git a/examples/07-collaboration/09-comments-testing/package.json b/examples/07-collaboration/09-comments-testing/package.json
new file mode 100644
index 0000000000..070af0d48e
--- /dev/null
+++ b/examples/07-collaboration/09-comments-testing/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@blocknote/example-collaboration-comments-testing",
+ "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
+ "type": "module",
+ "private": true,
+ "version": "0.12.4",
+ "scripts": {
+ "start": "vite",
+ "dev": "vite",
+ "build:prod": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@blocknote/ariakit": "latest",
+ "@blocknote/core": "latest",
+ "@blocknote/mantine": "latest",
+ "@blocknote/react": "latest",
+ "@blocknote/shadcn": "latest",
+ "@mantine/core": "^8.3.11",
+ "@mantine/hooks": "^8.3.11",
+ "@mantine/utils": "^6.0.22",
+ "react": "^19.2.3",
+ "react-dom": "^19.2.3",
+ "yjs": "^13.6.27"
+ },
+ "devDependencies": {
+ "@types/react": "^19.2.3",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^6.0.1",
+ "vite": "^8.0.8"
+ }
+}
\ No newline at end of file
diff --git a/examples/07-collaboration/09-comments-testing/src/App.tsx b/examples/07-collaboration/09-comments-testing/src/App.tsx
new file mode 100644
index 0000000000..3bada358c1
--- /dev/null
+++ b/examples/07-collaboration/09-comments-testing/src/App.tsx
@@ -0,0 +1,44 @@
+"use client";
+
+import {
+ CommentsExtension,
+ DefaultThreadStoreAuth,
+ YjsThreadStore,
+} from "@blocknote/core/comments";
+import { BlockNoteView } from "@blocknote/mantine";
+import "@blocknote/mantine/style.css";
+import { useCreateBlockNote } from "@blocknote/react";
+import { useMemo } from "react";
+import * as Y from "yjs";
+
+const USER = {
+ id: "1",
+ username: "John Doe",
+ avatarUrl: "https://placehold.co/100x100?text=John",
+ role: "editor" as const,
+};
+
+async function resolveUsers(userIds: string[]) {
+ return [USER].filter((user) => userIds.includes(user.id));
+}
+
+export default function App() {
+ const doc = useMemo(() => new Y.Doc(), []);
+
+ const threadStore = useMemo(() => {
+ return new YjsThreadStore(
+ USER.id,
+ doc.getMap("threads"),
+ new DefaultThreadStoreAuth(USER.id, USER.role),
+ );
+ }, [doc]);
+
+ const editor = useCreateBlockNote(
+ {
+ extensions: [CommentsExtension({ threadStore, resolveUsers })],
+ },
+ [threadStore],
+ );
+
+ return ;
+}
diff --git a/examples/07-collaboration/09-comments-testing/tsconfig.json b/examples/07-collaboration/09-comments-testing/tsconfig.json
new file mode 100644
index 0000000000..dbe3e6f62d
--- /dev/null
+++ b/examples/07-collaboration/09-comments-testing/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": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "composite": true
+ },
+ "include": [
+ "."
+ ],
+ "__ADD_FOR_LOCAL_DEV_references": [
+ {
+ "path": "../../../packages/core/"
+ },
+ {
+ "path": "../../../packages/react/"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/examples/07-collaboration/09-comments-testing/vite.config.ts b/examples/07-collaboration/09-comments-testing/vite.config.ts
new file mode 100644
index 0000000000..f62ab20bc2
--- /dev/null
+++ b/examples/07-collaboration/09-comments-testing/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-extensions/01-tiptap-arrow-conversion/package.json b/examples/08-extensions/01-tiptap-arrow-conversion/package.json
index 5bca11ea17..b1055a283f 100644
--- a/examples/08-extensions/01-tiptap-arrow-conversion/package.json
+++ b/examples/08-extensions/01-tiptap-arrow-conversion/package.json
@@ -19,9 +19,9 @@
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
- "@tiptap/core": "^3.13.0",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@tiptap/core": "^3.13.0"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/09-ai/01-minimal/package.json b/examples/09-ai/01-minimal/package.json
index 92c27364e7..d78b179eb4 100644
--- a/examples/09-ai/01-minimal/package.json
+++ b/examples/09-ai/01-minimal/package.json
@@ -16,13 +16,13 @@
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
- "@blocknote/xl-ai": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
- "ai": "^6.0.5",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@blocknote/xl-ai": "latest",
+ "ai": "^6.0.5"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/09-ai/02-playground/package.json b/examples/09-ai/02-playground/package.json
index ed3c58dab8..7824de1c5b 100644
--- a/examples/09-ai/02-playground/package.json
+++ b/examples/09-ai/02-playground/package.json
@@ -16,13 +16,13 @@
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
- "@blocknote/xl-ai": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
- "ai": "^6.0.5",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@blocknote/xl-ai": "latest",
+ "ai": "^6.0.5"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/09-ai/03-custom-ai-menu-items/package.json b/examples/09-ai/03-custom-ai-menu-items/package.json
index fb628a4aa9..d6a2573f8e 100644
--- a/examples/09-ai/03-custom-ai-menu-items/package.json
+++ b/examples/09-ai/03-custom-ai-menu-items/package.json
@@ -16,13 +16,13 @@
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
- "@blocknote/xl-ai": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
- "ai": "^6.0.5",
"react": "^19.2.3",
"react-dom": "^19.2.3",
+ "@blocknote/xl-ai": "latest",
+ "ai": "^6.0.5",
"react-icons": "^5.5.0"
},
"devDependencies": {
diff --git a/examples/09-ai/04-with-collaboration/package.json b/examples/09-ai/04-with-collaboration/package.json
index 75daec4c9f..ef36c46e88 100644
--- a/examples/09-ai/04-with-collaboration/package.json
+++ b/examples/09-ai/04-with-collaboration/package.json
@@ -16,13 +16,13 @@
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
- "@blocknote/xl-ai": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
- "ai": "^6.0.5",
"react": "^19.2.3",
"react-dom": "^19.2.3",
+ "@blocknote/xl-ai": "latest",
+ "ai": "^6.0.5",
"y-partykit": "^0.0.25",
"yjs": "^13.6.27"
},
diff --git a/examples/09-ai/05-manual-execution/package.json b/examples/09-ai/05-manual-execution/package.json
index a1b130faa8..dd5e66ef92 100644
--- a/examples/09-ai/05-manual-execution/package.json
+++ b/examples/09-ai/05-manual-execution/package.json
@@ -16,13 +16,13 @@
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
- "@blocknote/xl-ai": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
- "ai": "^6.0.5",
"react": "^19.2.3",
"react-dom": "^19.2.3",
+ "@blocknote/xl-ai": "latest",
+ "ai": "^6.0.5",
"y-partykit": "^0.0.25",
"yjs": "^13.6.27"
},
diff --git a/examples/09-ai/06-client-side-transport/package.json b/examples/09-ai/06-client-side-transport/package.json
index 8cc1777300..64b09bc543 100644
--- a/examples/09-ai/06-client-side-transport/package.json
+++ b/examples/09-ai/06-client-side-transport/package.json
@@ -11,19 +11,19 @@
"preview": "vite preview"
},
"dependencies": {
- "@ai-sdk/groq": "^3.0.2",
"@blocknote/ariakit": "latest",
"@blocknote/core": "latest",
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
- "@blocknote/xl-ai": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
- "ai": "^6.0.5",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@ai-sdk/groq": "^3.0.2",
+ "@blocknote/xl-ai": "latest",
+ "ai": "^6.0.5"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/09-ai/07-server-persistence/package.json b/examples/09-ai/07-server-persistence/package.json
index 0ee494038b..0ac67163a2 100644
--- a/examples/09-ai/07-server-persistence/package.json
+++ b/examples/09-ai/07-server-persistence/package.json
@@ -16,13 +16,13 @@
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
- "@blocknote/xl-ai": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
- "ai": "^6.0.5",
"react": "^19.2.3",
- "react-dom": "^19.2.3"
+ "react-dom": "^19.2.3",
+ "@blocknote/xl-ai": "latest",
+ "ai": "^6.0.5"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/packages/core/src/comments/extension.ts b/packages/core/src/comments/extension.ts
index 4e8e566cef..515a740c34 100644
--- a/packages/core/src/comments/extension.ts
+++ b/packages/core/src/comments/extension.ts
@@ -306,6 +306,11 @@ export const CommentsExtension = createExtension(
selectedThreadId: undefined,
pendingComment: true,
}));
+ // Use `editor.domElement` as `editor.focus()` doesn't do anything if
+ // the editor is non-editable. Editor needs to be focused as
+ // `showSelection` will otherwise trigger a selection update which
+ // triggers `stopPendingComment`.
+ editor.domElement?.focus();
editor
.getExtension(ShowSelectionExtension)
?.showSelection(true, "comments");
diff --git a/packages/core/src/editor/managers/StyleManager.ts b/packages/core/src/editor/managers/StyleManager.ts
index e03c46a6d1..e8ffa99881 100644
--- a/packages/core/src/editor/managers/StyleManager.ts
+++ b/packages/core/src/editor/managers/StyleManager.ts
@@ -169,7 +169,12 @@ export class StyleManager<
const { from, to } = tr.selection;
if (text) {
- tr.insertText(text, from, to).addMark(from, from + text.length, mark);
+ const existingText = tr.doc.textBetween(from, to);
+ if (text !== existingText) {
+ tr.insertText(text, from, to);
+ }
+
+ tr.addMark(from, from + text.length, mark);
} else {
tr.setSelection(TextSelection.create(tr.doc, to)).addMark(
from,
diff --git a/packages/core/src/extensions/LinkToolbar/LinkToolbar.ts b/packages/core/src/extensions/LinkToolbar/LinkToolbar.ts
index 1a61d67d44..f190bc97e6 100644
--- a/packages/core/src/extensions/LinkToolbar/LinkToolbar.ts
+++ b/packages/core/src/extensions/LinkToolbar/LinkToolbar.ts
@@ -88,7 +88,12 @@ export const LinkToolbarExtension = createExtension(({ editor }) => {
if (!range) {
return;
}
- tr.insertText(text, range.from, range.to);
+
+ const existingText = tr.doc.textBetween(range.from, range.to);
+ if (text !== existingText) {
+ tr.insertText(text, range.from, range.to);
+ }
+
tr.addMark(
range.from,
range.from + text.length,
diff --git a/packages/dev-scripts/examples/template-react/package.json.template.tsx b/packages/dev-scripts/examples/template-react/package.json.template.tsx
index a08cdc93ad..98c67f3e50 100644
--- a/packages/dev-scripts/examples/template-react/package.json.template.tsx
+++ b/packages/dev-scripts/examples/template-react/package.json.template.tsx
@@ -39,8 +39,8 @@ const template = (project: Project) => ({
: {}),
"@types/react": "^19.2.3",
"@types/react-dom": "^19.2.3",
- "@vitejs/plugin-react": "^4.7.0",
- vite: "^5.4.20",
+ "@vitejs/plugin-react": "^6.0.1",
+ vite: "^8.0.8",
...(project.config?.devDependencies || {}),
},
});
diff --git a/packages/react/src/components/Comments/FloatingComposer.tsx b/packages/react/src/components/Comments/FloatingComposer.tsx
index 1cc72d634d..aa3e786dd9 100644
--- a/packages/react/src/components/Comments/FloatingComposer.tsx
+++ b/packages/react/src/components/Comments/FloatingComposer.tsx
@@ -1,4 +1,12 @@
-import { mergeCSSClasses } from "@blocknote/core";
+import {
+ BlockSchema,
+ DefaultBlockSchema,
+ DefaultInlineContentSchema,
+ DefaultStyleSchema,
+ InlineContentSchema,
+ mergeCSSClasses,
+ StyleSchema,
+} from "@blocknote/core";
import { CommentsExtension } from "@blocknote/core/comments";
import { useComponentsContext } from "../../editor/ComponentsContext.js";
@@ -7,13 +15,21 @@ import { useExtension } from "../../hooks/useExtension.js";
import { useDictionary } from "../../i18n/dictionary.js";
import { CommentEditor } from "./CommentEditor.js";
import { defaultCommentEditorSchema } from "./defaultCommentEditorSchema.js";
+import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js";
+import { TextSelection } from "@tiptap/pm/state";
/**
* The FloatingComposer component displays a comment editor "floating" card.
*
* It's used when the user highlights a parts of the document to create a new comment / thread.
*/
-export function FloatingComposer() {
+export function FloatingComposer<
+ B extends BlockSchema = DefaultBlockSchema,
+ I extends InlineContentSchema = DefaultInlineContentSchema,
+ S extends StyleSchema = DefaultStyleSchema,
+>() {
+ const editor = useBlockNoteEditor();
+
const comments = useExtension(CommentsExtension);
const Components = useComponentsContext()!;
@@ -57,6 +73,12 @@ export function FloatingComposer() {
},
});
comments.stopPendingComment();
+ editor.transact((tr) => {
+ tr.setSelection(
+ TextSelection.create(tr.doc, tr.selection.to),
+ );
+ });
+ editor.focus();
}}
>
Save
diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx
index 00a2e5afe5..5f219ff7fa 100644
--- a/playground/src/examples.gen.tsx
+++ b/playground/src/examples.gen.tsx
@@ -997,11 +997,11 @@
],
"dependencies": {
"@blocknote/code-block": "latest",
- "@shikijs/core": "^3.19.0",
- "@shikijs/engine-javascript": "^3.19.0",
- "@shikijs/langs-precompiled": "^3.19.0",
- "@shikijs/themes": "^3.19.0",
- "@shikijs/types": "^3.19.0"
+ "@shikijs/core": "^4",
+ "@shikijs/engine-javascript": "^4",
+ "@shikijs/langs-precompiled": "^4",
+ "@shikijs/themes": "^4",
+ "@shikijs/types": "^4"
} as any
},
"title": "Custom Code Block Theme & Language",
@@ -1687,6 +1687,30 @@
"slug": "collaboration"
},
"readme": "In this example, we can fork a document and edit it independently of other collaborators. Then, we can choose to merge the changes back into the original document, or discard the changes.\n\n**Try it out:** Open this page in a new browser tab or window to see it in action!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)"
+ },
+ {
+ "projectSlug": "comments-testing",
+ "fullSlug": "collaboration/comments-testing",
+ "pathFromRoot": "examples/07-collaboration/09-comments-testing",
+ "config": {
+ "playground": true,
+ "docs": false,
+ "author": "matthewlipski",
+ "tags": [
+ "Advanced",
+ "Comments",
+ "Testing"
+ ],
+ "dependencies": {
+ "yjs": "^13.6.27"
+ } as any
+ },
+ "title": "Comments Testing",
+ "group": {
+ "pathFromRoot": "examples/07-collaboration",
+ "slug": "collaboration"
+ },
+ "readme": "A minimal comments example used for end-to-end testing. Uses a local Y.Doc (no collaboration provider) with a single hardcoded editor user."
}
]
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d2cd8b1127..202a26aeda 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4006,6 +4006,55 @@ importers:
specifier: ^8.0.8
version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
+ examples/07-collaboration/09-comments-testing:
+ dependencies:
+ '@blocknote/ariakit':
+ specifier: latest
+ version: link:../../../packages/ariakit
+ '@blocknote/core':
+ specifier: latest
+ version: link:../../../packages/core
+ '@blocknote/mantine':
+ specifier: latest
+ version: link:../../../packages/mantine
+ '@blocknote/react':
+ specifier: latest
+ version: link:../../../packages/react
+ '@blocknote/shadcn':
+ specifier: latest
+ version: link:../../../packages/shadcn
+ '@mantine/core':
+ specifier: ^8.3.11
+ version: 8.3.18(@mantine/hooks@8.3.18(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+ '@mantine/hooks':
+ specifier: ^8.3.11
+ version: 8.3.18(react@19.2.5)
+ '@mantine/utils':
+ specifier: ^6.0.22
+ version: 6.0.22(react@19.2.5)
+ react:
+ specifier: ^19.2.3
+ version: 19.2.5
+ react-dom:
+ specifier: ^19.2.3
+ version: 19.2.5(react@19.2.5)
+ yjs:
+ specifier: ^13.6.27
+ version: 13.6.30
+ devDependencies:
+ '@types/react':
+ specifier: ^19.2.3
+ version: 19.2.14
+ '@types/react-dom':
+ specifier: ^19.2.3
+ version: 19.2.3(@types/react@19.2.14)
+ '@vitejs/plugin-react':
+ specifier: ^6.0.1
+ version: 6.0.1(babel-plugin-react-compiler@1.0.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
+ vite:
+ specifier: ^8.0.8
+ version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
+
examples/08-extensions/01-tiptap-arrow-conversion:
dependencies:
'@blocknote/ariakit':
diff --git a/tests/src/end-to-end/ariakit/ariakit.test.ts b/tests/src/end-to-end/ariakit/ariakit.test.ts
index aa8c05c7d6..dd5fa0eccf 100644
--- a/tests/src/end-to-end/ariakit/ariakit.test.ts
+++ b/tests/src/end-to-end/ariakit/ariakit.test.ts
@@ -35,7 +35,9 @@ test.describe("Check Ariakit UI", () => {
await page.keyboard.type("link");
await page.keyboard.press("Enter");
+ await page.waitForTimeout(500);
await page.keyboard.press("ArrowLeft");
+ await page.keyboard.press("ArrowRight");
await page.waitForTimeout(500);
expect(await page.screenshot()).toMatchSnapshot("ariakit-link-toolbar.png");
diff --git a/tests/src/end-to-end/comments/comments.test.ts b/tests/src/end-to-end/comments/comments.test.ts
new file mode 100644
index 0000000000..db29498359
--- /dev/null
+++ b/tests/src/end-to-end/comments/comments.test.ts
@@ -0,0 +1,35 @@
+import { expect } from "@playwright/test";
+import { test } from "../../setup/setupScript.js";
+import { COMMENTS_URL, LINK_BUTTON_SELECTOR } from "../../utils/const.js";
+import { focusOnEditor } from "../../utils/editor.js";
+
+test.beforeEach(async ({ page }) => {
+ await page.goto(COMMENTS_URL);
+});
+
+test.describe("Check Comments functionality", () => {
+ test("Should preserve existing comments when adding a link", async ({
+ page,
+ }) => {
+ await focusOnEditor(page);
+
+ await page.keyboard.type("hello");
+ await page.locator("text=hello").dblclick();
+
+ await page.click('[data-test="addcomment"]');
+ await page.waitForSelector(".bn-thread");
+
+ await page.keyboard.type("test comment");
+ await page.click('button[data-test="save"]');
+
+ await page.locator("span.bn-thread-mark").first().dblclick();
+
+ await expect(page.locator(LINK_BUTTON_SELECTOR)).toBeVisible();
+ await page.click(LINK_BUTTON_SELECTOR);
+
+ await page.keyboard.type("https://example.com");
+ await page.keyboard.press("Enter");
+
+ await expect(await page.locator("span.bn-thread-mark")).toBeVisible();
+ });
+});
diff --git a/tests/src/end-to-end/shadcn/shadcn.test.ts b/tests/src/end-to-end/shadcn/shadcn.test.ts
index c4a46ed8cc..fe1a2e385f 100644
--- a/tests/src/end-to-end/shadcn/shadcn.test.ts
+++ b/tests/src/end-to-end/shadcn/shadcn.test.ts
@@ -35,7 +35,9 @@ test.describe("Check ShadCN UI", () => {
await page.keyboard.type("link");
await page.keyboard.press("Enter");
+ await page.waitForTimeout(500);
await page.keyboard.press("ArrowLeft");
+ await page.keyboard.press("ArrowRight");
await page.waitForTimeout(700);
expect(await page.screenshot()).toMatchSnapshot("shadcn-link-toolbar.png");
diff --git a/tests/src/end-to-end/theming/theming.test.ts b/tests/src/end-to-end/theming/theming.test.ts
index 8bc75a6fab..1647cda1a9 100644
--- a/tests/src/end-to-end/theming/theming.test.ts
+++ b/tests/src/end-to-end/theming/theming.test.ts
@@ -46,7 +46,9 @@ test.describe("Check Dark Theme is Automatically Applied", () => {
await page.waitForTimeout(500);
await page.keyboard.type("link");
await page.keyboard.press("Enter");
+ await page.waitForTimeout(500);
await page.keyboard.press("ArrowLeft");
+ await page.keyboard.press("ArrowRight");
await page.waitForTimeout(500);
expect(await page.screenshot()).toMatchSnapshot("dark-link-toolbar.png");
diff --git a/tests/src/utils/const.ts b/tests/src/utils/const.ts
index 61cedc194d..b04b77d6a2 100644
--- a/tests/src/utils/const.ts
+++ b/tests/src/utils/const.ts
@@ -43,6 +43,10 @@ export const ALERT_BLOCK_URL = !process.env.RUN_IN_DOCKER
? `http://localhost:${PORT}/custom-schema/alert-block?hideMenu`
: `http://host.docker.internal:${PORT}/custom-schema/alert-block?hideMenu`;
+export const COMMENTS_URL = !process.env.RUN_IN_DOCKER
+ ? `http://localhost:${PORT}/collaboration/comments-testing?hideMenu`
+ : `http://host.docker.internal:${PORT}/collaboration/comments-testing?hideMenu`;
+
export const PASTE_ZONE_SELECTOR = "#pasteZone";
export const EDITOR_SELECTOR = `.bn-editor`;