diff --git a/docs/app/(home)/hero/DemoEditor.tsx b/docs/app/(home)/hero/DemoEditor.tsx new file mode 100644 index 0000000000..e2587414af --- /dev/null +++ b/docs/app/(home)/hero/DemoEditor.tsx @@ -0,0 +1,143 @@ +import { + BlockNoteSchema, + combineByGroup, + uploadToTmpFilesDotOrg_DEV_ONLY, +} from "@blocknote/core"; +import { filterSuggestionItems } from "@blocknote/core/extensions"; +import "@blocknote/core/fonts/inter.css"; +import * as locales from "@blocknote/core/locales"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { + getDefaultReactSlashMenuItems, + SuggestionMenuController, + useCreateBlockNote, +} from "@blocknote/react"; +import { + getMultiColumnSlashMenuItems, + multiColumnDropCursor, + locales as multiColumnLocales, + withMultiColumn, +} from "@blocknote/xl-multi-column"; +import { useTheme } from "next-themes"; +import { useCallback, useMemo, useState } from "react"; +import YPartyKitProvider from "y-partykit/provider"; +import * as Y from "@y/y"; + +const colors = [ + "#958DF1", + "#F98181", + "#FBBC88", + "#FAF594", + "#70CFF8", + "#94FADB", + "#B9F18D", +]; +const names = [ + "Lorem Ipsumovich", + "Typy McTypeface", + "Collabo Rative", + "Edito Von Editz", + "Wordsworth Writywrite", + "Docu D. Mentor", + "Scrivener Scribblesworth", + "Digi Penman", + "Ernest Wordway", + "Sir Typalot", + "Comic Sans-Serif", + "Miss Spellcheck", + "Bullet Liston", + "Autonomy Backspace", + "Ctrl Zedson", +]; + +const getRandomElement = (list: any[]) => + list[Math.floor(Math.random() * list.length)]; + +const getRandomColor = () => getRandomElement(colors); +const getRandomName = () => getRandomElement(names); + +function getUTCDateYYYYMMDD() { + const now = new Date(); + const year = now.getUTCFullYear(); + const month = now.getUTCMonth() + 1; // January is 0 + const day = now.getUTCDate(); + + // Add leading zeros to month and day if needed + const formattedMonth = month < 10 ? `0${month}` : `${month}`; + const formattedDay = day < 10 ? `0${day}` : `${day}`; + + return `${year}${formattedMonth}${formattedDay}`; +} + +export default function DemoEditor() { + const { resolvedTheme } = useTheme(); + + const [doc, provider] = useMemo(() => { + const doc = new Y.Doc(); + const provider = new YPartyKitProvider( + "blocknote.yousefed.partykit.dev", + // "127.0.0.1:1999", // (dev server) + "homepage-" + getUTCDateYYYYMMDD(), + doc, + ); + return [doc, provider]; + }, []); + + const editor = useCreateBlockNote( + { + dictionary: { + ...locales.en, + multi_column: multiColumnLocales.en, + }, + schema: withMultiColumn(BlockNoteSchema.create()), + dropCursor: multiColumnDropCursor, + collaboration: { + provider, + fragment: doc.getXmlFragment("blocknote"), + user: { + name: getRandomName(), + color: getRandomColor(), + }, + }, + uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY, + }, + [], + ); + + const [warningShown, setWarningShown] = useState(false); + + const focus = useCallback(() => { + if (!warningShown) { + alert( + "Text you enter in this demo is displayed publicly on the internet to show multiplayer features. Be kind :)", + ); + setWarningShown(true); + } + }, [warningShown]); + + const getSlashMenuItems = useMemo(() => { + return async (query: string) => + filterSuggestionItems( + combineByGroup( + getDefaultReactSlashMenuItems(editor), + getMultiColumnSlashMenuItems(editor), + ), + query, + ); + }, [editor]); + + return ( + + + + ); +} diff --git a/docs/content/docs/features/collaboration/index.mdx b/docs/content/docs/features/collaboration/index.mdx index 2d320ab829..d0c93278a3 100644 --- a/docs/content/docs/features/collaboration/index.mdx +++ b/docs/content/docs/features/collaboration/index.mdx @@ -23,7 +23,7 @@ _Try the live demo on the [homepage](https://www.blocknotejs.org)_ BlockNote uses [Yjs](https://github.com/yjs/yjs) for this, and you can set it up with the `collaboration` option: ```typescript -import * as Y from "yjs"; +import * as Y from "@y/y"; import { WebrtcProvider } from "y-webrtc"; // ... diff --git a/docs/content/docs/reference/editor/yjs-utilities.mdx b/docs/content/docs/reference/editor/yjs-utilities.mdx index 1b2f7cba30..6526de7712 100644 --- a/docs/content/docs/reference/editor/yjs-utilities.mdx +++ b/docs/content/docs/reference/editor/yjs-utilities.mdx @@ -74,7 +74,7 @@ function blocksToYDoc< ```typescript import { BlockNoteEditor } from "@blocknote/core"; import { blocksToYDoc } from "@blocknote/core/yjs"; -import * as Y from "yjs"; +import * as Y from "@y/y"; const editor = BlockNoteEditor.create(); @@ -126,7 +126,7 @@ function blocksToYXmlFragment< ```typescript import { BlockNoteEditor } from "@blocknote/core"; import { blocksToYXmlFragment } from "@blocknote/core/yjs"; -import * as Y from "yjs"; +import * as Y from "@y/y"; const editor = BlockNoteEditor.create(); const doc = new Y.Doc(); @@ -174,7 +174,7 @@ function yDocToBlocks< ```typescript import { BlockNoteEditor } from "@blocknote/core"; import { yDocToBlocks } from "@blocknote/core/yjs"; -import * as Y from "yjs"; +import * as Y from "@y/y"; const editor = BlockNoteEditor.create(); const ydoc = new Y.Doc(); @@ -214,7 +214,7 @@ function yXmlFragmentToBlocks< ```typescript import { BlockNoteEditor } from "@blocknote/core"; import { yXmlFragmentToBlocks } from "@blocknote/core/yjs"; -import * as Y from "yjs"; +import * as Y from "@y/y"; const editor = BlockNoteEditor.create(); const doc = new Y.Doc(); diff --git a/docs/package.json b/docs/package.json index d29fcc0ff4..fecf5440c8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -2,56 +2,50 @@ "name": "docs", "version": "0.0.0", "private": true, - "license": "UNLICENSED", + "type": "module", "scripts": { - "dev": "next dev", - "dev:email": "next dev", + "dev": "next dev --turbopack", + "dev:email": "email dev", "prebuild:site": "nx run @blocknote/dev-scripts:gen", - "build:site": "fumadocs-mdx && next build", + "build:site": "fumadocs-mdx && next build --turbopack", "start": "next start", - "types:check": "fumadocs-mdx && next typegen && tsc --noEmit", "postinstall": "fumadocs-mdx", - "lint": "eslint", "init-db": "pnpx @better-auth/cli migrate", - "test": "pnpm -w run gen && node validate-links.mjs" + "test": "node validate-links.js" }, "dependencies": { - "@ai-sdk/groq": "^3.0.2", + "@ai-sdk/anthropic": "^2.0.31", + "@ai-sdk/google": "^2.0.23", + "@ai-sdk/groq": "^2.0.16", + "@ai-sdk/mistral": "^2.0.19", + "@ai-sdk/openai": "^2.0.52", + "@ai-sdk/openai-compatible": "^1.0.22", "@aws-sdk/client-s3": "^3.609.0", "@aws-sdk/s3-request-presigner": "^3.609.0", - "@base-ui/react": "^1.1.0", - "@blocknote/ariakit": "workspace:*", "@blocknote/code-block": "workspace:*", - "@blocknote/core": "workspace:*", - "@blocknote/mantine": "workspace:*", - "@blocknote/react": "workspace:*", "@blocknote/server-util": "workspace:*", - "@blocknote/shadcn": "workspace:*", "@blocknote/xl-ai": "workspace:*", "@blocknote/xl-docx-exporter": "workspace:*", "@blocknote/xl-email-exporter": "workspace:*", "@blocknote/xl-multi-column": "workspace:*", "@blocknote/xl-odt-exporter": "workspace:*", "@blocknote/xl-pdf-exporter": "workspace:*", - "@fumadocs/base-ui": "16.5.0", - "@liveblocks/client": "3.7.1-tiptap3", - "@liveblocks/react": "3.7.1-tiptap3", - "@liveblocks/react-blocknote": "3.7.1-tiptap3", - "@liveblocks/react-tiptap": "3.7.1-tiptap3", - "@liveblocks/react-ui": "3.7.1-tiptap3", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@fumadocs/mdx-remote": "1.3.0", + "@headlessui/react": "^2.2.9", + "@heroicons/react": "^2.2.0", + "@mantine/core": "^8.3.4", + "@mantine/hooks": "^8.3.4", "@mantine/utils": "^6.0.22", - "@marsidev/react-turnstile": "^1.4.2", "@mui/icons-material": "^5.16.1", "@mui/material": "^5.16.1", - "@orama/orama": "^3.1.18", - "@polar-sh/better-auth": "^1.6.4", - "@polar-sh/sdk": "^0.42.2", - "@react-email/components": "^1.0.4", - "@react-email/render": "^2.0.4", + "@polar-sh/better-auth": "^1.1.9", + "@polar-sh/nextjs": "^0.4.9", + "@polar-sh/sdk": "^0.34.17", + "@react-email/render": "^1.1.2", "@react-pdf/renderer": "^4.3.0", - "@sentry/nextjs": "^10.34.0", + "@sentry/nextjs": "9.14.0", "@shikijs/core": "^3.19.0", "@shikijs/engine-javascript": "^3.19.0", "@shikijs/langs-precompiled": "^3.19.0", @@ -69,66 +63,69 @@ "@uppy/status-bar": "^3.1.1", "@uppy/webcam": "^3.4.2", "@uppy/xhr-upload": "^3.4.0", - "@vercel/analytics": "^1.6.1", - "@y-sweet/react": "^0.6.3", - "ai": "^6.0.5", - "better-auth": "^1.4.15", - "better-sqlite3": "^12.6.2", - "class-variance-authority": "^0.7.1", - "framer-motion": "^12.26.2", - "fumadocs-core": "16.5.0", - "fumadocs-mdx": "^14.2.6", - "fumadocs-twoslash": "^3.1.12", - "fumadocs-typescript": "^5.1.1", - "fumadocs-ui": "npm:@fumadocs/base-ui@16.5.0", - "lucide-react": "^0.562.0", - "motion": "^12.28.1", - "next": "^16.1.6", - "next-themes": "^0.4.6", - "nodemailer": "^7.0.12", - "pg": "^8.17.1", - "react": "^19.2.3", - "react-dom": "^19.2.3", - "react-email": "^5.2.5", + "@vercel/analytics": "^1.5.0", + "@vercel/og": "^0.6.8", + "ai": "^5.0.102", + "babel-plugin-react-compiler": "19.1.0-rc.2", + "better-auth": "^1.3.27", + "better-sqlite3": "^11.10.0", + "classnames": "2.3.2", + "clsx": "2.1.1", + "docx": "^9.5.1", + "framer-motion": "^11.18.2", + "fumadocs-core": "15.5.4", + "fumadocs-docgen": "2.0.1", + "fumadocs-mdx": "11.6.9", + "fumadocs-twoslash": "3.1.4", + "fumadocs-typescript": "4.0.6", + "fumadocs-ui": "15.5.4", + "import-in-the-middle": "^1.15.0", + "next": "15.5.9", + "nodemailer": "^7.0.11", + "pg": "^8.16.3", + "react": "^19.2.1", + "react-dom": "^19.2.1", "react-github-btn": "^1.4.0", - "react-icons": "^5.5.0", - "react-use-measure": "^2.1.7", - "scroll-into-view-if-needed": "^3.1.0", - "shiki": "^3.21.0", - "tailwind-merge": "^3.4.0", - "y-partykit": "^0.0.25", - "yjs": "^13.6.27", - "zod": "^4.3.5" + "react-icons": "^5.2.1", + "remark": "^15.0.1", + "remark-gfm": "^4.0.1", + "remark-mdx": "^3.1.1", + "require-in-the-middle": "^7.5.2", + "shiki": "^3.13.0", + "ts-morph": "26.0.0", + "twoslash": "^0.3.4", + "@y/y": "14.0.0-rc.2", + "zod": "^3.25.76" }, "devDependencies": { + "@blocknote/ariakit": "workspace:*", "@blocknote/code-block": "workspace:*", "@blocknote/core": "workspace:*", "@blocknote/mantine": "workspace:*", "@blocknote/react": "workspace:*", - "@blocknote/server-util": "workspace:*", "@blocknote/shadcn": "workspace:*", - "@blocknote/xl-ai": "workspace:*", "@blocknote/xl-docx-exporter": "workspace:*", - "@blocknote/xl-email-exporter": "workspace:*", "@blocknote/xl-multi-column": "workspace:*", - "@blocknote/xl-odt-exporter": "workspace:*", "@blocknote/xl-pdf-exporter": "workspace:*", - "@tailwindcss/postcss": "^4.1.18", - "@types/better-sqlite3": "^7.6.13", + "@mui/material": "^5.17.1", + "@react-email/components": "^0.0.36", + "@react-pdf/renderer": "^4.3.0", + "@tailwindcss/postcss": "^4.1.14", + "@types/better-sqlite3": "7.6.13", "@types/mdx": "^2.0.13", - "@types/node": "^25.0.5", - "@types/nodemailer": "^7.0.5", - "@types/pg": "^8.16.0", - "@types/react": "^19.2.8", - "@types/react-dom": "^19.2.3", - "babel-plugin-react-compiler": "^1.0.0", - "eslint": "^9.39.2", - "eslint-config-next": "^16.1.6", - "next-validate-link": "^1.6.4", + "@types/node": "22.15.2", + "@types/nodemailer": "6.4.17", + "@types/pg": "8.11.14", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "next-themes": "0.4.6", + "next-validate-link": "^1.6.3", "postcss": "^8.5.6", - "serve": "^14.2.5", - "tailwindcss": "^4.1.18", + "react-email": "^4.3.0", + "react-icons": "^5.5.0", + "tailwindcss": "^4.1.14", "tw-animate-css": "^1.4.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "@y/y": "14.0.0-rc.2" } -} \ No newline at end of file +} diff --git a/examples/01-basic/01-minimal/package.json b/examples/01-basic/01-minimal/package.json index af406c6b8f..22e5659932 100644 --- a/examples/01-basic/01-minimal/package.json +++ b/examples/01-basic/01-minimal/package.json @@ -11,6 +11,8 @@ "preview": "vite preview" }, "dependencies": { + "@y/protocols": "1.0.6-rc.1", + "@y/y": "14.0.0-rc.2", "@blocknote/ariakit": "latest", "@blocknote/core": "latest", "@blocknote/mantine": "latest", @@ -19,6 +21,7 @@ "@mantine/core": "^8.3.11", "@mantine/hooks": "^8.3.11", "@mantine/utils": "^6.0.22", + "@blocknote/xl-ai": "latest", "react": "^19.2.3", "react-dom": "^19.2.3" }, @@ -28,4 +31,4 @@ "@vitejs/plugin-react": "^4.7.0", "vite": "^5.4.20" } -} \ No newline at end of file +} diff --git a/examples/01-basic/01-minimal/src/AIDemo.tsx b/examples/01-basic/01-minimal/src/AIDemo.tsx new file mode 100644 index 0000000000..32730a4b21 --- /dev/null +++ b/examples/01-basic/01-minimal/src/AIDemo.tsx @@ -0,0 +1,202 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import { AIExtension } from "@blocknote/xl-ai"; +import * as Y from "@y/y"; +import { Awareness } from "@y/protocols/awareness"; +import { useState } from "react"; + +const doc = new Y.Doc(); +const provider = { + awareness: new Awareness(doc), +}; + +export function AIDemo() { + const [status, setStatus] = useState("Ready"); + + const editor = useCreateBlockNote({ + collaboration: { + fragment: doc.get("doc"), + provider, + user: { name: "Alice", color: "#3b82f6" }, + }, + extensions: [ + AIExtension({ + agentCursor: { + name: "AI Assistant", + color: "#8b5cf6", + }, + }), + ], + }); + + const handleTestFork = () => { + try { + const aiExt = editor.getExtension("ai" as any); + if (!aiExt) { + setStatus("AI extension not found"); + return; + } + + // Test that the AI extension registered successfully + setStatus( + `AI extension loaded successfully.\n` + + `Extension key: "ai"\n` + + `Agent cursor: AI Assistant (#8b5cf6)\n\n` + + `The AI extension integrates with the collaboration system via:\n` + + `- ForkYDoc: isolates AI edits from remote sync\n` + + `- Suggestion marks: uses insertion/deletion marks for track changes\n` + + `- Agent cursor: shows AI cursor position to other users`, + ); + } catch (e) { + setStatus(`Error: ${e}`); + } + }; + + const handleCheckExtensions = () => { + try { + const extensions: string[] = []; + + // Check which collaboration extensions are registered + const ySync = editor.getExtension("ySync" as any); + if (ySync) extensions.push("ySync (sync plugin)"); + + const yCursor = editor.getExtension("yCursor" as any); + if (yCursor) extensions.push("yCursor (remote cursors)"); + + const yUndo = editor.getExtension("yUndo" as any); + if (yUndo) extensions.push("yUndo (collaborative undo/redo)"); + + const yForkDoc = editor.getExtension("yForkDoc" as any); + if (yForkDoc) extensions.push("yForkDoc (document forking)"); + + const ai = editor.getExtension("ai" as any); + if (ai) extensions.push("ai (AI extension)"); + + const collaboration = editor.getExtension("collaboration" as any); + if (collaboration) extensions.push("collaboration (parent)"); + + const history = editor.getExtension("history" as any); + if (history) extensions.push("history (ProseMirror history)"); + + const comments = editor.getExtension("comments" as any); + if (comments) extensions.push("comments"); + + setStatus( + `Registered extensions (${extensions.length}):\n\n` + + extensions.map((e) => ` ✓ ${e}`).join("\n") + + `\n\nNote: When collaboration is enabled, "history" should NOT be present\n` + + `(it's replaced by yUndo for collaborative undo/redo).`, + ); + } catch (e) { + setStatus(`Error: ${e}`); + } + }; + + const handleCheckSuggestionMarks = () => { + try { + const schema = editor.pmSchema; + const markNames = Object.keys(schema.marks); + const suggestionMarks = markNames.filter((n) => + ["insertion", "deletion", "modification", "comment"].includes(n), + ); + const allMarks = markNames; + + setStatus( + `ProseMirror Schema Marks:\n\n` + + `All marks (${allMarks.length}):\n` + + allMarks.map((m) => ` - ${m}`).join("\n") + + `\n\nSuggestion/Collaboration marks:\n` + + (suggestionMarks.length > 0 + ? suggestionMarks.map((m) => ` ✓ ${m}`).join("\n") + : " ✗ None found") + + `\n\nThese marks are used by both the AI extension (for suggestions)\n` + + `and the sync plugin's mapAttributionToMark (for track changes).`, + ); + } catch (e) { + setStatus(`Error: ${e}`); + } + }; + + return ( +
+

AI Extension + Collaboration Demo

+

+ Tests that the AI extension loads correctly alongside the collaboration + system. The AI extension uses ForkYDoc to isolate AI edits and + suggestion marks for track changes. +

+

+ Note: No AI backend is connected. This demo verifies the extension + integration, not AI functionality. +

+ +
+ + + +
+ {status && ( +
+          {status}
+        
+ )} +
+ ); +} diff --git a/examples/01-basic/01-minimal/src/App.tsx b/examples/01-basic/01-minimal/src/App.tsx index a3b92bafd2..0e9b1358e2 100644 --- a/examples/01-basic/01-minimal/src/App.tsx +++ b/examples/01-basic/01-minimal/src/App.tsx @@ -1,12 +1,58 @@ -import "@blocknote/core/fonts/inter.css"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; -import { useCreateBlockNote } from "@blocknote/react"; +import { useState } from "react"; +import { CollabDemo } from "./CollabDemo"; +import { ForkDemo } from "./ForkDemo"; +import { ConversionsDemo } from "./ConversionsDemo"; +import { AIDemo } from "./AIDemo"; + +const TABS = [ + { id: "collab", label: "Sync + Cursors + Undo + Comments", component: CollabDemo }, + { id: "fork", label: "ForkYDoc", component: ForkDemo }, + { id: "conversions", label: "Yjs Conversions", component: ConversionsDemo }, + { id: "ai", label: "AI Extension", component: AIDemo }, +] as const; export default function App() { - // Creates a new editor instance. - const editor = useCreateBlockNote(); + const [activeTab, setActiveTab] = useState("collab"); + + const ActiveComponent = + TABS.find((t) => t.id === activeTab)?.component ?? CollabDemo; - // Renders the editor instance using a React component. - return ; + return ( +
+ + +
+ ); } diff --git a/examples/01-basic/01-minimal/src/CollabDemo.tsx b/examples/01-basic/01-minimal/src/CollabDemo.tsx new file mode 100644 index 0000000000..a5098e6c00 --- /dev/null +++ b/examples/01-basic/01-minimal/src/CollabDemo.tsx @@ -0,0 +1,256 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import { + CommentsExtension, + DefaultThreadStoreAuth, + YjsThreadStore, +} from "@blocknote/core/comments"; +import * as Y from "@y/y"; +import { + Awareness, + encodeAwarenessUpdate, + applyAwarenessUpdate, +} from "@y/protocols/awareness"; +import { useMemo } from "react"; + +const doc = new Y.Doc(); +const provider = { + awareness: new Awareness(doc), +}; + +const doc2 = new Y.Doc(); +const provider2 = { + awareness: new Awareness(doc2), +}; + +const suggestingDoc = new Y.Doc({ isSuggestionDoc: true }); +const suggestingProvider = { + awareness: new Awareness(suggestingDoc), +}; + +const createSuggestionAttrs = (prevDoc: Y.Doc, nextDoc: Y.Doc) => + Y.createContentMapFromContentIds( + Y.createContentIdsFromDocDiff(prevDoc, nextDoc), + [Y.createContentAttribute("insert", ["nickthesick"])], + ); + +const suggestingAttributionManager = Y.createAttributionManagerFromDiff( + doc, + suggestingDoc, + { + attrs: createSuggestionAttrs(doc, suggestingDoc), + }, +); +suggestingAttributionManager.suggestionMode = true; + +const suggestionModeDoc = new Y.Doc({ isSuggestionDoc: true }); +const suggestionModeProvider = { + awareness: new Awareness(suggestionModeDoc), +}; +const suggestionModeAttributionManager = Y.createAttributionManagerFromDiff( + doc, + suggestionModeDoc, + { + attrs: createSuggestionAttrs(doc, suggestionModeDoc), + }, +); +suggestionModeAttributionManager.suggestionMode = true; + +// Hardcoded users for the demo +const USERS = [ + { id: "alice", username: "Alice", avatarUrl: "" }, + { id: "bob", username: "Bob", avatarUrl: "" }, +]; + +async function resolveUsers(userIds: string[]) { + return USERS.filter((u) => userIds.includes(u.id)); +} + +// Function to sync two documents +function syncDocs(sourceDoc: Y.Doc, targetDoc: Y.Doc) { + const update = Y.encodeStateAsUpdate(sourceDoc); + Y.applyUpdate(targetDoc, update); +} + +// Set up two-way sync (Y.Doc + Awareness) +function setupTwoWaySync( + doc1: Y.Doc, + doc2: Y.Doc, + awareness1?: Awareness, + awareness2?: Awareness, +) { + syncDocs(doc1, doc2); + syncDocs(doc2, doc1); + + doc1.on("update", (update: Uint8Array) => { + Y.applyUpdate(doc2, update); + }); + + doc2.on("update", (update: Uint8Array) => { + Y.applyUpdate(doc1, update); + }); + + if (awareness1 && awareness2) { + awareness1.on( + "update", + ({ + added, + updated, + removed, + }: { + added: number[]; + updated: number[]; + removed: number[]; + }) => { + const changedClients = added.concat(updated).concat(removed); + const encodedUpdate = encodeAwarenessUpdate(awareness1, changedClients); + applyAwarenessUpdate(awareness2, encodedUpdate, awareness1); + }, + ); + + awareness2.on( + "update", + ({ + added, + updated, + removed, + }: { + added: number[]; + updated: number[]; + removed: number[]; + }) => { + const changedClients = added.concat(updated).concat(removed); + const encodedUpdate = encodeAwarenessUpdate(awareness2, changedClients); + applyAwarenessUpdate(awareness1, encodedUpdate, awareness2); + }, + ); + } +} + +setupTwoWaySync(doc, doc2, provider.awareness, provider2.awareness); + +setupTwoWaySync( + suggestingDoc, + suggestionModeDoc, + suggestingProvider.awareness, + suggestionModeProvider.awareness, +); + +function Editor({ + fragment, + provider, + attributionManager, + user, + userId, + threadsYType, +}: { + fragment: Y.Type; + provider: { awareness: Awareness }; + attributionManager?: Y.AbstractAttributionManager; + user: { name: string; color: string }; + userId: string; + threadsYType: Y.Type; +}) { + const threadStore = useMemo( + () => + new YjsThreadStore( + userId, + threadsYType, + new DefaultThreadStoreAuth(userId, "editor"), + ), + [userId, threadsYType], + ); + + const editor = useCreateBlockNote({ + collaboration: { + fragment, + provider, + user, + attributionManager, + }, + extensions: [CommentsExtension({ threadStore, resolveUsers })], + }); + + return ( +
+
+ + +
+ +
+ ); +} + +export function CollabDemo() { + return ( +
+
+
+ Client A (Alice) + +
+
+ Client B (Bob) + +
+
+
+
+ View Suggestions Mode + +
+
+ Suggestion Mode + +
+
+
+ ); +} diff --git a/examples/01-basic/01-minimal/src/ConversionsDemo.tsx b/examples/01-basic/01-minimal/src/ConversionsDemo.tsx new file mode 100644 index 0000000000..caead89ba5 --- /dev/null +++ b/examples/01-basic/01-minimal/src/ConversionsDemo.tsx @@ -0,0 +1,187 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import { + yXmlFragmentToBlocks, + blocksToYXmlFragment, + yDocToBlocks, + blocksToYDoc, +} from "@blocknote/core/yjs"; +import * as Y from "@y/y"; +import { Awareness } from "@y/protocols/awareness"; +import { useState } from "react"; + +const doc = new Y.Doc(); +const provider = { + awareness: new Awareness(doc), +}; + +export function ConversionsDemo() { + const editor = useCreateBlockNote({ + collaboration: { + fragment: doc.get("doc"), + provider, + user: { name: "Alice", color: "#3b82f6" }, + }, + }); + + const [conversionOutput, setConversionOutput] = useState(""); + + const handleYTypeToBlocks = () => { + try { + const fragment = doc.get("doc"); + const blocks = yXmlFragmentToBlocks(editor, fragment); + setConversionOutput( + `yXmlFragmentToBlocks result (${blocks.length} blocks):\n\n` + + JSON.stringify(blocks, null, 2), + ); + } catch (e) { + setConversionOutput(`Error: ${e}`); + } + }; + + const handleBlocksToYDoc = () => { + try { + const currentBlocks = editor.document; + const newDoc = blocksToYDoc(editor, currentBlocks); + const state = Y.encodeStateAsUpdate(newDoc); + setConversionOutput( + `blocksToYDoc result:\n` + + ` Y.Doc created with ${state.byteLength} bytes of state\n` + + ` Fragment "prosemirror" length: ${newDoc.get("prosemirror").length}`, + ); + } catch (e) { + setConversionOutput(`Error: ${e}`); + } + }; + + const handleBlocksToFragment = () => { + try { + const currentBlocks = editor.document; + const fragment = blocksToYXmlFragment(editor, currentBlocks as any); + setConversionOutput( + `blocksToYXmlFragment result:\n` + + ` Fragment length: ${fragment.length}\n` + + ` Fragment delta: ${JSON.stringify(fragment.toDelta()?.toJSON?.() ?? "N/A", null, 2)}`, + ); + } catch (e) { + setConversionOutput(`Error: ${e}`); + } + }; + + const handleRoundTrip = () => { + try { + const originalBlocks = editor.document; + + // Blocks → Y.Doc → Blocks + const newDoc = blocksToYDoc(editor, originalBlocks); + const roundTrippedBlocks = yDocToBlocks(editor, newDoc); + + const originalJson = JSON.stringify(originalBlocks, null, 2); + const roundTrippedJson = JSON.stringify(roundTrippedBlocks, null, 2); + const match = originalJson === roundTrippedJson; + + setConversionOutput( + `Round-trip test (Blocks → Y.Doc → Blocks):\n` + + ` Original blocks: ${originalBlocks.length}\n` + + ` Round-tripped blocks: ${roundTrippedBlocks.length}\n` + + ` Match: ${match ? "YES ✓" : "NO ✗"}\n\n` + + (match + ? "Content preserved perfectly!" + : `Original:\n${originalJson}\n\nRound-tripped:\n${roundTrippedJson}`), + ); + } catch (e) { + setConversionOutput(`Error: ${e}`); + } + }; + + return ( +
+

Yjs Conversion Utilities Demo

+

+ Test the conversion functions between BlockNote blocks and Yjs types. + Type some content in the editor, then click the buttons below. +

+ +
+ + + + +
+ {conversionOutput && ( +
+          {conversionOutput}
+        
+ )} +
+ ); +} diff --git a/examples/01-basic/01-minimal/src/ForkDemo.tsx b/examples/01-basic/01-minimal/src/ForkDemo.tsx new file mode 100644 index 0000000000..3d9a2f6a3b --- /dev/null +++ b/examples/01-basic/01-minimal/src/ForkDemo.tsx @@ -0,0 +1,117 @@ +import "@blocknote/core/fonts/inter.css"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import * as Y from "@y/y"; +import { Awareness } from "@y/protocols/awareness"; +import { useState } from "react"; + +const doc = new Y.Doc(); +const provider = { + awareness: new Awareness(doc), +}; + +export function ForkDemo() { + const editor = useCreateBlockNote({ + collaboration: { + fragment: doc.get("doc"), + provider, + user: { name: "Alice", color: "#3b82f6" }, + }, + }); + + const [isForked, setIsForked] = useState(false); + + const handleFork = () => { + const forkExt = editor.getExtension("yForkDoc" as any); + if (forkExt && "fork" in forkExt) { + (forkExt as any).fork(); + setIsForked(true); + } else { + console.warn("ForkYDoc extension not found"); + } + }; + + const handleMergeKeep = () => { + const forkExt = editor.getExtension("yForkDoc" as any); + if (forkExt && "merge" in forkExt) { + (forkExt as any).merge({ keepChanges: true }); + setIsForked(false); + } + }; + + const handleMergeDiscard = () => { + const forkExt = editor.getExtension("yForkDoc" as any); + if (forkExt && "merge" in forkExt) { + (forkExt as any).merge({ keepChanges: false }); + setIsForked(false); + } + }; + + return ( +
+

ForkYDoc Demo

+

+ Fork the document to make changes without affecting the original. Then + merge or discard your changes. +

+
+ {!isForked ? ( + + ) : ( + <> + + Document is forked — edits are local only + + + + + )} +
+ +
+ ); +} diff --git a/examples/01-basic/01-minimal/vite.config.ts b/examples/01-basic/01-minimal/vite.config.ts index f62ab20bc2..6132659b3d 100644 --- a/examples/01-basic/01-minimal/vite.config.ts +++ b/examples/01-basic/01-minimal/vite.config.ts @@ -7,7 +7,9 @@ import { defineConfig } from "vite"; // https://vitejs.dev/config/ export default defineConfig((conf) => ({ plugins: [react()], - optimizeDeps: {}, + optimizeDeps: { + exclude: ["@y/prosemirror"], + }, build: { sourcemap: true, }, diff --git a/examples/07-collaboration/01-partykit/.bnexample.json b/examples/07-collaboration/01-partykit/.bnexample.json deleted file mode 100644 index 87250048fe..0000000000 --- a/examples/07-collaboration/01-partykit/.bnexample.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "playground": true, - "docs": true, - "author": "yousefed", - "tags": ["Advanced", "Saving/Loading", "Collaboration"], - "dependencies": { - "y-partykit": "^0.0.25", - "yjs": "^13.6.27" - } -} diff --git a/examples/07-collaboration/01-partykit/README.md b/examples/07-collaboration/01-partykit/README.md deleted file mode 100644 index 600fcd0943..0000000000 --- a/examples/07-collaboration/01-partykit/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Collaborative Editing with PartyKit - -In this example, we use PartyKit to let multiple users collaborate on a single BlockNote document in real-time. - -**Try it out:** Open this page in a new browser tab or window to see it in action! - -**Relevant Docs:** - -- [Editor Setup](/docs/getting-started/editor-setup) -- [PartyKit](/docs/features/collaboration#partykit) diff --git a/examples/07-collaboration/01-partykit/index.html b/examples/07-collaboration/01-partykit/index.html deleted file mode 100644 index 707eae21e1..0000000000 --- a/examples/07-collaboration/01-partykit/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - Collaborative Editing with PartyKit - - - -
- - - diff --git a/examples/07-collaboration/01-partykit/main.tsx b/examples/07-collaboration/01-partykit/main.tsx deleted file mode 100644 index 677c7f7eed..0000000000 --- a/examples/07-collaboration/01-partykit/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 "./src/App.jsx"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/07-collaboration/01-partykit/package.json b/examples/07-collaboration/01-partykit/package.json deleted file mode 100644 index c29a4981a9..0000000000 --- a/examples/07-collaboration/01-partykit/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "@blocknote/example-collaboration-partykit", - "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", - "y-partykit": "^0.0.25", - "yjs": "^13.6.27" - }, - "devDependencies": { - "@types/react": "^19.2.3", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" - } -} \ No newline at end of file diff --git a/examples/07-collaboration/01-partykit/src/App.tsx b/examples/07-collaboration/01-partykit/src/App.tsx deleted file mode 100644 index 4d317c9b3b..0000000000 --- a/examples/07-collaboration/01-partykit/src/App.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; -import YPartyKitProvider from "y-partykit/provider"; -import * as Y from "yjs"; - -// Sets up Yjs document and PartyKit Yjs provider. -const doc = new Y.Doc(); -const provider = new YPartyKitProvider( - "blocknote-dev.yousefed.partykit.dev", - // Use a unique name as a "room" for your application. - "your-project-name", - doc, -); - -export default function App() { - const editor = useCreateBlockNote({ - collaboration: { - // The Yjs Provider responsible for transporting updates: - provider, - // Where to store BlockNote data in the Y.Doc: - fragment: doc.getXmlFragment("document-store"), - // Information (name and color) for this user: - user: { - name: "My Username", - color: "#ff0000", - }, - }, - }); - - // Renders the editor instance. - return ; -} diff --git a/examples/07-collaboration/01-partykit/tsconfig.json b/examples/07-collaboration/01-partykit/tsconfig.json deleted file mode 100644 index dbe3e6f62d..0000000000 --- a/examples/07-collaboration/01-partykit/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__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/01-partykit/vite.config.ts b/examples/07-collaboration/01-partykit/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/07-collaboration/01-partykit/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// 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/07-collaboration/02-liveblocks/.bnexample.json b/examples/07-collaboration/02-liveblocks/.bnexample.json deleted file mode 100644 index b212ead624..0000000000 --- a/examples/07-collaboration/02-liveblocks/.bnexample.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "playground": true, - "docs": true, - "author": "yousefed", - "tags": ["Advanced", "Saving/Loading", "Collaboration"], - "dependencies": { - "@liveblocks/client": "3.7.1-tiptap3", - "@liveblocks/react": "3.7.1-tiptap3", - "@liveblocks/react-blocknote": "3.7.1-tiptap3", - "@liveblocks/react-tiptap": "3.7.1-tiptap3", - "@liveblocks/react-ui": "3.7.1-tiptap3", - "yjs": "^13.6.27" - } -} diff --git a/examples/07-collaboration/02-liveblocks/README.md b/examples/07-collaboration/02-liveblocks/README.md deleted file mode 100644 index 50292b8d42..0000000000 --- a/examples/07-collaboration/02-liveblocks/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Collaborative Editing with Liveblocks - -In this example, we use -the [Liveblocks + BlockNote setup guide](https://liveblocks.io/docs/get-started/react-blocknote) -to create a collaborative BlockNote editor, where multiple people can work on -the same document in real-time. - -Users can also add comments to the documents to start threads, which are -displayed next to the editor. As well as that, they can react to, reply to, and -resolve existing comments. - -**Try it out:** Open this page in a new browser tab or window to see it in -action! - -**Relevant Docs:** - -- [Editor Setup](/docs/getting-started/editor-setup) -- [Liveblocks](/docs/features/collaboration#liveblocks) - -**From Liveblocks Website:** - -- [Get Started with BlockNote](https://liveblocks.io/docs/get-started/react-blocknote) -- [Ready Made Features](https://liveblocks.io/docs/ready-made-features/text-editor/blocknote) -- [API Reference](https://liveblocks.io/docs/api-reference/liveblocks-react-blocknote) -- [Advanced Example](https://liveblocks.io/examples/collaborative-text-editor/nextjs-blocknote) diff --git a/examples/07-collaboration/02-liveblocks/index.html b/examples/07-collaboration/02-liveblocks/index.html deleted file mode 100644 index af8a9f53fb..0000000000 --- a/examples/07-collaboration/02-liveblocks/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - Collaborative Editing with Liveblocks - - - -
- - - diff --git a/examples/07-collaboration/02-liveblocks/liveblocks.config.ts b/examples/07-collaboration/02-liveblocks/liveblocks.config.ts deleted file mode 100644 index 28db2e07be..0000000000 --- a/examples/07-collaboration/02-liveblocks/liveblocks.config.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Define Liveblocks types for your application -// https://liveblocks.io/docs/api-reference/liveblocks-react#Typing-your-data -declare global { - interface Liveblocks { - // Each user's Presence, for useMyPresence, useOthers, etc. - Presence: { - // Example, real-time cursor coordinates - // cursor: { x: number; y: number }; - }; - - // The Storage tree for the room, for useMutation, useStorage, etc. - Storage: { - // Example, a conflict-free list - // animals: LiveList; - }; - - // Custom user info set when authenticating with a secret key - UserMeta: { - id: string; - info: { - // Example properties, for useSelf, useUser, useOthers, etc. - // name: string; - // avatar: string; - }; - }; - - // Custom events, for useBroadcastEvent, useEventListener - RoomEvent: {}; - // Example has two events, using a union - // | { type: "PLAY" } - // | { type: "REACTION"; emoji: "🔥" }; - - // Custom metadata set on threads, for useThreads, useCreateThread, etc. - ThreadMetadata: { - // Example, attaching coordinates to a thread - // x: number; - // y: number; - }; - - // Custom room info set with resolveRoomsInfo, for useRoomInfo - RoomInfo: { - // Example, rooms with a title and url - // title: string; - // url: string; - }; - } -} - -export {}; diff --git a/examples/07-collaboration/02-liveblocks/main.tsx b/examples/07-collaboration/02-liveblocks/main.tsx deleted file mode 100644 index 677c7f7eed..0000000000 --- a/examples/07-collaboration/02-liveblocks/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 "./src/App.jsx"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/07-collaboration/02-liveblocks/package.json b/examples/07-collaboration/02-liveblocks/package.json deleted file mode 100644 index 42dac31b38..0000000000 --- a/examples/07-collaboration/02-liveblocks/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@blocknote/example-collaboration-liveblocks", - "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", - "@liveblocks/client": "3.7.1-tiptap3", - "@liveblocks/react": "3.7.1-tiptap3", - "@liveblocks/react-blocknote": "3.7.1-tiptap3", - "@liveblocks/react-tiptap": "3.7.1-tiptap3", - "@liveblocks/react-ui": "3.7.1-tiptap3", - "yjs": "^13.6.27" - }, - "devDependencies": { - "@types/react": "^19.2.3", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" - } -} \ No newline at end of file diff --git a/examples/07-collaboration/02-liveblocks/src/App.tsx b/examples/07-collaboration/02-liveblocks/src/App.tsx deleted file mode 100644 index e523e79dd2..0000000000 --- a/examples/07-collaboration/02-liveblocks/src/App.tsx +++ /dev/null @@ -1,30 +0,0 @@ -// See https://liveblocks.io/docs/get-started/react-blocknote to see how this -// example was created, and an explanation for all the code. -import { - ClientSideSuspense, - LiveblocksProvider, - RoomProvider, -} from "@liveblocks/react/suspense"; -import "@liveblocks/react-ui/styles.css"; -import "@liveblocks/react-ui/styles/dark/media-query.css"; -import "@liveblocks/react-tiptap/styles.css"; - -import { Editor } from "./Editor"; -import "./globals.css"; -import "./styles.css"; - -export default function App() { - return ( - - - Loading…}> - - - - - ); -} diff --git a/examples/07-collaboration/02-liveblocks/src/Editor.tsx b/examples/07-collaboration/02-liveblocks/src/Editor.tsx deleted file mode 100644 index 2c6da47379..0000000000 --- a/examples/07-collaboration/02-liveblocks/src/Editor.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import "@blocknote/core/fonts/inter.css"; -import { BlockNoteEditor } from "@blocknote/core"; -import { BlockNoteView } from "@blocknote/mantine"; -import "@blocknote/mantine/style.css"; -import { useCreateBlockNoteWithLiveblocks } from "@liveblocks/react-blocknote"; - -import { Threads } from "./Threads"; - -export function Editor() { - const editor = useCreateBlockNoteWithLiveblocks( - {}, - { mentions: true }, - ) as BlockNoteEditor; - - return ( -
- - -
- ); -} diff --git a/examples/07-collaboration/02-liveblocks/src/Threads.tsx b/examples/07-collaboration/02-liveblocks/src/Threads.tsx deleted file mode 100644 index b79c1ecd7f..0000000000 --- a/examples/07-collaboration/02-liveblocks/src/Threads.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { BlockNoteEditor } from "@blocknote/core"; -import { useThreads } from "@liveblocks/react/suspense"; -import { - AnchoredThreads, - FloatingComposer, - FloatingThreads, -} from "@liveblocks/react-blocknote"; - -export function Threads({ editor }: { editor: BlockNoteEditor | null }) { - const { threads } = useThreads({ query: { resolved: false } }); - - if (!editor) { - return null; - } - - return ( - <> -
- -
- - - - ); -} diff --git a/examples/07-collaboration/02-liveblocks/src/globals.css b/examples/07-collaboration/02-liveblocks/src/globals.css deleted file mode 100644 index 1b40f1f4ca..0000000000 --- a/examples/07-collaboration/02-liveblocks/src/globals.css +++ /dev/null @@ -1,47 +0,0 @@ -html { - font-family: Inter, sans-serif; - background: #f9f9f9; -} - -@media (prefers-color-scheme: dark) { - html { - background: #0c0c0c; - } -} - -.editor { - position: absolute; - inset: 0; - max-width: 1024px; - margin: 0 auto; - padding: 48px 0; -} - -.bn-editor { - padding: 36px 52px; - min-height: 100%; -} - -/* For mobile */ -.floating-threads { - display: none; -} - -/* For desktop */ -.anchored-threads { - display: block; - max-width: 300px; - width: 100%; - position: absolute; - right: 12px; -} - -@media (max-width: 640px) { - .floating-threads { - display: block; - } - - .anchored-threads { - display: none; - } -} diff --git a/examples/07-collaboration/02-liveblocks/src/styles.css b/examples/07-collaboration/02-liveblocks/src/styles.css deleted file mode 100644 index 74625c7432..0000000000 --- a/examples/07-collaboration/02-liveblocks/src/styles.css +++ /dev/null @@ -1,8 +0,0 @@ -.editor { - position: relative; - height: 100%; -} - -div:has(> .editor) { - height: 100%; -} diff --git a/examples/07-collaboration/02-liveblocks/tsconfig.json b/examples/07-collaboration/02-liveblocks/tsconfig.json deleted file mode 100644 index dbe3e6f62d..0000000000 --- a/examples/07-collaboration/02-liveblocks/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__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/02-liveblocks/vite.config.ts b/examples/07-collaboration/02-liveblocks/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/07-collaboration/02-liveblocks/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// 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/07-collaboration/03-y-sweet/.bnexample.json b/examples/07-collaboration/03-y-sweet/.bnexample.json deleted file mode 100644 index ec9d562b0e..0000000000 --- a/examples/07-collaboration/03-y-sweet/.bnexample.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "playground": true, - "docs": true, - "author": "jakelazaroff", - "tags": ["Advanced", "Saving/Loading", "Collaboration"], - "dependencies": { - "@y-sweet/react": "^0.6.3" - } -} diff --git a/examples/07-collaboration/03-y-sweet/README.md b/examples/07-collaboration/03-y-sweet/README.md deleted file mode 100644 index aa897aab16..0000000000 --- a/examples/07-collaboration/03-y-sweet/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Collaborative Editing with Y-Sweet - -In this example, we use Y-Sweet to let multiple users collaborate on a single BlockNote document in real-time. - -**Try it out:** Open the [Y-Sweet BlockNote demo](https://demos.y-sweet.dev/blocknote) in multiple browser tabs to see it in action! - -**Relevant Docs:** - -- [Editor Setup](/docs/getting-started/editor-setup) -- [Real-time collaboration](/docs/features/collaboration) -- [Y-Sweet on Jamsocket](https://docs.jamsocket.com/y-sweet/tutorials/blocknote) diff --git a/examples/07-collaboration/03-y-sweet/index.html b/examples/07-collaboration/03-y-sweet/index.html deleted file mode 100644 index 0f89a74040..0000000000 --- a/examples/07-collaboration/03-y-sweet/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - Collaborative Editing with Y-Sweet - - - -
- - - diff --git a/examples/07-collaboration/03-y-sweet/main.tsx b/examples/07-collaboration/03-y-sweet/main.tsx deleted file mode 100644 index 677c7f7eed..0000000000 --- a/examples/07-collaboration/03-y-sweet/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 "./src/App.jsx"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/07-collaboration/03-y-sweet/package.json b/examples/07-collaboration/03-y-sweet/package.json deleted file mode 100644 index ca2e4b0097..0000000000 --- a/examples/07-collaboration/03-y-sweet/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "@blocknote/example-collaboration-y-sweet", - "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", - "@y-sweet/react": "^0.6.3" - }, - "devDependencies": { - "@types/react": "^19.2.3", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" - } -} \ No newline at end of file diff --git a/examples/07-collaboration/03-y-sweet/src/App.tsx b/examples/07-collaboration/03-y-sweet/src/App.tsx deleted file mode 100644 index 5a238ac497..0000000000 --- a/examples/07-collaboration/03-y-sweet/src/App.tsx +++ /dev/null @@ -1,35 +0,0 @@ -"use client"; - -import { useYDoc, useYjsProvider, YDocProvider } from "@y-sweet/react"; -import { useCreateBlockNote } from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; - -import "@blocknote/mantine/style.css"; - -export default function App() { - const docId = "my-blocknote-document"; - - return ( - - - - ); -} - -function Document() { - const provider = useYjsProvider(); - const doc = useYDoc(); - - const editor = useCreateBlockNote({ - collaboration: { - provider, - fragment: doc.getXmlFragment("blocknote"), - user: { color: "#ff0000", name: "My Username" }, - }, - }); - - return ; -} diff --git a/examples/07-collaboration/03-y-sweet/tsconfig.json b/examples/07-collaboration/03-y-sweet/tsconfig.json deleted file mode 100644 index dbe3e6f62d..0000000000 --- a/examples/07-collaboration/03-y-sweet/tsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "__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/03-y-sweet/vite.config.ts b/examples/07-collaboration/03-y-sweet/vite.config.ts deleted file mode 100644 index f62ab20bc2..0000000000 --- a/examples/07-collaboration/03-y-sweet/vite.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -// 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/07-collaboration/04-electric-sql/.bnexample.json b/examples/07-collaboration/04-electric-sql/.bnexample.json deleted file mode 100644 index 29a56bc991..0000000000 --- a/examples/07-collaboration/04-electric-sql/.bnexample.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "playground": true, - "docs": true, - "author": "matthewlipski", - "tags": ["Advanced", "Saving/Loading", "Collaboration"] -} diff --git a/examples/07-collaboration/04-electric-sql/README.md b/examples/07-collaboration/04-electric-sql/README.md deleted file mode 100644 index 58296a8e95..0000000000 --- a/examples/07-collaboration/04-electric-sql/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Collaborative Editing with ElectricSQL - -In this example, we use ElectricSQL to let multiple users collaborate on a single BlockNote document in real-time. The setup for this demo is more involved than the other collaboration examples, as it requires a running server and has a more fully-fledged UI. Therefore, the demo just uses an iframe element to show a hosted instance of the full ElectricSQL + BlockNote setup, which you can find the code for [here](https://github.com/TypeCellOS/blocknote-electric-example). - -**Try it out:** Open this page (or the [iframe url](https://blocknote-electric-example.vercel.app/)) in a new browser tab or window to see it in action! - -**Relevant Docs:** - -- [Editor Setup](/docs/getting-started/editor-setup) -- [Real-time collaboration](/docs/features/collaboration) -- [ElectricSQL](https://electric-sql.com/) diff --git a/examples/07-collaboration/04-electric-sql/index.html b/examples/07-collaboration/04-electric-sql/index.html deleted file mode 100644 index a7a5977cc1..0000000000 --- a/examples/07-collaboration/04-electric-sql/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - Collaborative Editing with ElectricSQL - - - -
- - - diff --git a/examples/07-collaboration/04-electric-sql/main.tsx b/examples/07-collaboration/04-electric-sql/main.tsx deleted file mode 100644 index 677c7f7eed..0000000000 --- a/examples/07-collaboration/04-electric-sql/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 "./src/App.jsx"; - -const root = createRoot(document.getElementById("root")!); -root.render( - - - -); diff --git a/examples/07-collaboration/04-electric-sql/package.json b/examples/07-collaboration/04-electric-sql/package.json deleted file mode 100644 index dd9d538875..0000000000 --- a/examples/07-collaboration/04-electric-sql/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@blocknote/example-collaboration-electric-sql", - "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" - }, - "devDependencies": { - "@types/react": "^19.2.3", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" - } -} \ No newline at end of file diff --git a/examples/07-collaboration/04-electric-sql/src/App.tsx b/examples/07-collaboration/04-electric-sql/src/App.tsx deleted file mode 100644 index 10e68bfd35..0000000000 --- a/examples/07-collaboration/04-electric-sql/src/App.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import "./style.css"; - -export default function App() { - return ( -