diff --git a/docs/content/docs/features/ai/autocomplete.mdx b/docs/content/docs/features/ai/autocomplete.mdx new file mode 100644 index 0000000000..cbe981cf68 --- /dev/null +++ b/docs/content/docs/features/ai/autocomplete.mdx @@ -0,0 +1,157 @@ +--- +title: Autocomplete +description: Add AI-powered autocomplete suggestions to your BlockNote editor +imageTitle: BlockNote AI Autocomplete +--- + +import { TypeTable } from 'fumadocs-ui/components/type-table'; + +# AI Autocomplete + +The AI Autocomplete extension provides real-time, AI-powered text completion suggestions as users type. +When enabled, the editor will display inline suggestions that can be accepted with the **Tab** key or dismissed with **Escape**. + +## Installation + +First, ensure you have the `@blocknote/xl-ai` package installed: + +```bash +npm install @blocknote/xl-ai +``` + +## Basic Setup and demo + +To enable AI autocomplete in your editor, add the `AIAutoCompleteExtension` to your editor configuration. +Here's a the basic code setup and live demo: + + + +## Configuration + +The `AIAutoCompleteExtension` accepts the following options: + + + +### Using a Custom Provider + +When you provide a URL (`string`) as provider, the extension will use the default provider that fetches suggestions from that URL. + +Alternatively, you can provide a custom provider function that should return an array of suggestions at the current cursor position. + +## Backend Integration + +With BlockNote AI Autocomplete, you have full control over the backend and LLM integration. + +The default URL provider sends a `POST` request with the following body: + +```json +{ + "text": "The last 300 characters of text before the cursor" +} +``` + +It expects a JSON response with an array of suggestion strings: + +```json +{ + "suggestions": ["completion text", "more text"] +} +``` + +_When multiple suggestions are returned, the first one will be used by default. As the user continues typing, the extension will automatically select alternative matching suggestions (or do a new backend requests if none of the cached suggestions match)._ + +Here's an example API route using the Vercel AI SDK with Mistral's Codestral model: + + + **Codestral** is a code completion model that excels in speed (for as-you-type suggestions low latency is important). + Even though it's designed for code completion, we can use it for general text completion as well. + See the [reference implementation](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-ai-server/src/routes/autocomplete.ts) for a complete example. + + +```typescript +// app/api/autocomplete/route.ts +import { createMistral } from "@ai-sdk/mistral"; +import { generateText } from "ai"; + +const model = createMistral({ + apiKey: process.env.MISTRAL_API_KEY, +})("codestral-latest"); + +export async function POST(req: Request) { + const { text } = await req.json(); + + const result = await generateText({ + model, + system: `You are a writing assistant. Predict the most likely next part of the text. +- Provide up to 3 suggestions separated by newlines +- Keep suggestions short (max 5 words each) +- Only return the text to append (no explanations) +- Add a space before the suggestion if starting a new word`, + messages: [ + { + role: "user", + content: `Complete the following text: \n${text}[SUGGESTION_HERE]`, + }, + ], + abortSignal: req.signal, + }); + + return Response.json({ + suggestions: result.text + .split("\n") + .map((s) => s.trimEnd()) + .filter((s) => s.trim().length > 0), + }); +} +``` + +## Programmatic Control + +You can also control the autocomplete extension programmatically: + +```typescript +const ext = editor.getExtension(AIAutoCompleteExtension); + +// Accept the current suggestion +ext.acceptAutoCompleteSuggestion(); + +// Discard suggestions +ext.discardAutoCompleteSuggestions(); + +// Manually trigger autocomplete at the current cursor position +// (useful if you want to configure a custom key to trigger autocomplete) +ext.triggerAutoComplete(); +``` diff --git a/docs/content/docs/features/ai/meta.json b/docs/content/docs/features/ai/meta.json index 18e7406d17..af99e16643 100644 --- a/docs/content/docs/features/ai/meta.json +++ b/docs/content/docs/features/ai/meta.json @@ -4,6 +4,7 @@ "getting-started", "backend-integration", "custom-commands", + "autocomplete", "reference" ] } diff --git a/examples/09-ai/01-minimal/README.md b/examples/09-ai/01-minimal/README.md index 5ffcb1af89..11ee9d5982 100644 --- a/examples/09-ai/01-minimal/README.md +++ b/examples/09-ai/01-minimal/README.md @@ -1,4 +1,4 @@ -# Rich Text editor AI integration +# Rich Text Editor AI Integration This example shows the minimal setup to add AI integration to your BlockNote rich text editor. @@ -6,6 +6,6 @@ Select some text and click the AI (stars) button, or type `/ai` anywhere in the **Relevant Docs:** -- [Getting Stared with BlockNote AI](/docs/features/ai/getting-started) +- [Getting Started with BlockNote AI](/docs/features/ai/getting-started) - [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar) - [Changing Slash Menu Items](/docs/react/components/suggestion-menus) diff --git a/examples/09-ai/01-minimal/index.html b/examples/09-ai/01-minimal/index.html index b31f43f604..d547ed7f78 100644 --- a/examples/09-ai/01-minimal/index.html +++ b/examples/09-ai/01-minimal/index.html @@ -2,7 +2,7 @@ - Rich Text editor AI integration + Rich Text Editor AI Integration diff --git a/examples/09-ai/01-minimal/src/App.tsx b/examples/09-ai/01-minimal/src/App.tsx index 37417d292b..03e4caa90b 100644 --- a/examples/09-ai/01-minimal/src/App.tsx +++ b/examples/09-ai/01-minimal/src/App.tsx @@ -22,7 +22,6 @@ import { en as aiEn } from "@blocknote/xl-ai/locales"; import "@blocknote/xl-ai/style.css"; import { DefaultChatTransport } from "ai"; -import { useEffect } from "react"; import { getEnv } from "./getEnv"; const BASE_URL = diff --git a/examples/09-ai/08-autocomplete/.bnexample.json b/examples/09-ai/08-autocomplete/.bnexample.json new file mode 100644 index 0000000000..2a0da0969a --- /dev/null +++ b/examples/09-ai/08-autocomplete/.bnexample.json @@ -0,0 +1,11 @@ +{ + "playground": true, + "docs": true, + "author": "yousefed", + "tags": ["AI", "autocomplete"], + "dependencies": { + "@blocknote/xl-ai": "latest", + "@mantine/core": "^8.3.4", + "ai": "^5.0.102" + } +} diff --git a/examples/09-ai/08-autocomplete/README.md b/examples/09-ai/08-autocomplete/README.md new file mode 100644 index 0000000000..8fb6f0c986 --- /dev/null +++ b/examples/09-ai/08-autocomplete/README.md @@ -0,0 +1,10 @@ +# AI Autocomplete + +This example demonstrates the AI autocomplete feature in BlockNote. As you type, the editor will automatically suggest completions using AI. + +Press **Tab** to accept a suggestion or **Escape** to dismiss it. + +**Relevant Docs:** + +- [AI Autocomplete](/docs/features/ai/autocomplete) +- [AI Getting Started](/docs/features/ai/getting-started) diff --git a/examples/09-ai/08-autocomplete/index.html b/examples/09-ai/08-autocomplete/index.html new file mode 100644 index 0000000000..cd02311000 --- /dev/null +++ b/examples/09-ai/08-autocomplete/index.html @@ -0,0 +1,14 @@ + + + + + AI Autocomplete + + + +
+ + + diff --git a/examples/09-ai/08-autocomplete/main.tsx b/examples/09-ai/08-autocomplete/main.tsx new file mode 100644 index 0000000000..677c7f7eed --- /dev/null +++ b/examples/09-ai/08-autocomplete/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/09-ai/08-autocomplete/package.json b/examples/09-ai/08-autocomplete/package.json new file mode 100644 index 0000000000..e09522ab24 --- /dev/null +++ b/examples/09-ai/08-autocomplete/package.json @@ -0,0 +1,33 @@ +{ + "name": "@blocknote/example-ai-autocomplete", + "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.4", + "@mantine/hooks": "^8.3.4", + "@mantine/utils": "^6.0.22", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "@blocknote/xl-ai": "latest", + "ai": "^5.0.102" + }, + "devDependencies": { + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.1", + "@vitejs/plugin-react": "^4.7.0", + "vite": "^5.4.20" + } +} \ No newline at end of file diff --git a/examples/09-ai/08-autocomplete/src/App.tsx b/examples/09-ai/08-autocomplete/src/App.tsx new file mode 100644 index 0000000000..057ad9df8e --- /dev/null +++ b/examples/09-ai/08-autocomplete/src/App.tsx @@ -0,0 +1,65 @@ +import "@blocknote/core/fonts/inter.css"; +import { en } from "@blocknote/core/locales"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import { + AIAutoCompleteExtension +} from "@blocknote/xl-ai"; +import { en as aiEn } from "@blocknote/xl-ai/locales"; +import "@blocknote/xl-ai/style.css"; + +import { getEnv } from "./getEnv"; + +const BASE_URL = + getEnv("BLOCKNOTE_AI_SERVER_BASE_URL") || "https://localhost:3000/ai"; + +export default function App() { + // Creates a new editor instance with AI autocomplete enabled + const editor = useCreateBlockNote({ + dictionary: { + ...en, + ai: aiEn, + }, + // Register the AI Autocomplete extension + extensions: [ + AIAutoCompleteExtension({ + provider: `${BASE_URL}/autocomplete/generateText`, + }), + ], + // We set some initial content for demo purposes + initialContent: [ + { + type: "heading", + props: { + level: 1, + }, + content: "AI Autocomplete Demo", + }, + { + type: "paragraph", + content: + "Start typing and the AI will suggest completions based on the current context. When suggestions appear, you can press Tab to accept the suggestion.", + }, + { + type: "paragraph", + content: + "For example, type at the end of this sentence. Open source software is: ", + }, + { + type: "paragraph", + content: "", + }, + ], + }); + + // Renders the editor instance using a React component. + return ( +
+ +
+ ); +} diff --git a/examples/09-ai/08-autocomplete/src/getEnv.ts b/examples/09-ai/08-autocomplete/src/getEnv.ts new file mode 100644 index 0000000000..b225fc462e --- /dev/null +++ b/examples/09-ai/08-autocomplete/src/getEnv.ts @@ -0,0 +1,20 @@ +// helper function to get env variables across next / vite +// only needed so this example works in BlockNote demos and docs +export function getEnv(key: string) { + const env = (import.meta as any).env + ? { + BLOCKNOTE_AI_SERVER_API_KEY: (import.meta as any).env + .VITE_BLOCKNOTE_AI_SERVER_API_KEY, + BLOCKNOTE_AI_SERVER_BASE_URL: (import.meta as any).env + .VITE_BLOCKNOTE_AI_SERVER_BASE_URL, + } + : { + BLOCKNOTE_AI_SERVER_API_KEY: + process.env.NEXT_PUBLIC_BLOCKNOTE_AI_SERVER_API_KEY, + BLOCKNOTE_AI_SERVER_BASE_URL: + process.env.NEXT_PUBLIC_BLOCKNOTE_AI_SERVER_BASE_URL, + }; + + const value = env[key as keyof typeof env]; + return value; +} diff --git a/examples/09-ai/08-autocomplete/tsconfig.json b/examples/09-ai/08-autocomplete/tsconfig.json new file mode 100644 index 0000000000..dbe3e6f62d --- /dev/null +++ b/examples/09-ai/08-autocomplete/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/09-ai/08-autocomplete/vite.config.ts b/examples/09-ai/08-autocomplete/vite.config.ts new file mode 100644 index 0000000000..f62ab20bc2 --- /dev/null +++ b/examples/09-ai/08-autocomplete/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/packages/core/src/extensions/SuggestionMenu/SuggestionMenu.ts b/packages/core/src/extensions/SuggestionMenu/SuggestionMenu.ts index 029103600a..e7be9a038e 100644 --- a/packages/core/src/extensions/SuggestionMenu/SuggestionMenu.ts +++ b/packages/core/src/extensions/SuggestionMenu/SuggestionMenu.ts @@ -322,7 +322,7 @@ export const SuggestionMenu = createExtension(({ editor }) => { }, props: { - handleTextInput(view, from, to, text) { + handleTextInput(view, from, to, text, deflt) { // only on insert if (from === to) { const doc = view.state.doc; @@ -333,18 +333,21 @@ export const SuggestionMenu = createExtension(({ editor }) => { : text; if (str === snippet) { - view.dispatch(view.state.tr.insertText(text)); view.dispatch( - view.state.tr - .setMeta(suggestionMenuPluginKey, { - triggerCharacter: snippet, - }) - .scrollIntoView(), + deflt().setMeta(suggestionMenuPluginKey, { + triggerCharacter: snippet, + }), ); return true; } } } + if (this.getState(view.state)) { + // when menu is open, we dispatch the default transaction + // and return true so that other event handlers (i.e.: AI AutoComplete) are not triggered + view.dispatch(deflt()); + return true; + } return false; }, diff --git a/packages/xl-ai-server/src/index.ts b/packages/xl-ai-server/src/index.ts index e6bce9ba47..917fc581c2 100644 --- a/packages/xl-ai-server/src/index.ts +++ b/packages/xl-ai-server/src/index.ts @@ -5,6 +5,7 @@ import { cors } from "hono/cors"; import { existsSync, readFileSync } from "node:fs"; import { createSecureServer } from "node:http2"; import { Agent, setGlobalDispatcher } from "undici"; +import { autocompleteRoute } from "./routes/autocomplete.js"; import { modelPlaygroundRoute } from "./routes/model-playground/index.js"; import { objectGenerationRoute } from "./routes/objectGeneration.js"; import { proxyRoute } from "./routes/proxy.js"; @@ -37,6 +38,7 @@ app.route("/ai/proxy", proxyRoute); app.route("/ai/object-generation", objectGenerationRoute); app.route("/ai/server-persistence", serverPersistenceRoute); app.route("/ai/model-playground", modelPlaygroundRoute); +app.route("/ai/autocomplete", autocompleteRoute); const http2 = existsSync("localhost.pem"); serve( diff --git a/packages/xl-ai-server/src/routes/autocomplete.ts b/packages/xl-ai-server/src/routes/autocomplete.ts new file mode 100644 index 0000000000..03bc0ded56 --- /dev/null +++ b/packages/xl-ai-server/src/routes/autocomplete.ts @@ -0,0 +1,55 @@ +import { createMistral } from "@ai-sdk/mistral"; +import { generateText } from "ai"; +import { Hono } from "hono"; + +export const autocompleteRoute = new Hono(); + +// Setup your model +// const model = createOpenAI({ +// apiKey: process.env.OPENAI_API_KEY, +// })("gpt-4.1-nano"); + +// const model = createGroq({ +// apiKey: process.env.GROQ_API_KEY, +// })("openai/gpt-oss-20b"); + +/** + * For this demo, we use `codestral-latest` from Mistral. + * It's originally designed for code completion, but it's + * performance make it a good candidate for fast text completions as well + */ +const model = createMistral({ + apiKey: process.env.MISTRAL_API_KEY, +})("codestral-latest"); + +// Use `streamText` to stream text responses from the LLM +autocompleteRoute.post("/generateText", async (c) => { + const { text } = await c.req.json(); + + const result = await generateText({ + model, + system: `You are a writing assistant, helping the user write text (NOT CODE). Predict and generate the most likely next part of the text. +- separate suggestions by newlines +- max 3 suggestions +- YOU MUST keep it short, USE MAXIMUM 5 (FIVE) WORDS per suggestion +- don't include other text (or explanations) +- YOU MUST ONLY return the text to be appended. Your suggestion will EXACTLY replace [SUGGESTION_HERE]. +- YOU MUST NOT include the original text / characters (prefix) in your suggestion. +- YOU MUST add a space (or other relevant punctuation) before the suggestion IF starting a new word (the suggestion will be directly concatenated to the text)`, + messages: [ + { + role: "user", + content: `Complete the following text: + ${text}[SUGGESTION_HERE]`, + }, + ], + abortSignal: c.req.raw.signal, + }); + + return c.json({ + suggestions: result.text + .split("\n") + .map((suggestion) => suggestion.trimEnd()) + .filter((suggestion) => suggestion.trim().length > 0), + }); +}); diff --git a/packages/xl-ai/src/AIAutoCompleteExtension.test.ts b/packages/xl-ai/src/AIAutoCompleteExtension.test.ts new file mode 100644 index 0000000000..1cabbb7653 --- /dev/null +++ b/packages/xl-ai/src/AIAutoCompleteExtension.test.ts @@ -0,0 +1,114 @@ +import { BlockNoteEditor, InlineContent, isStyledTextInlineContent } from "@blocknote/core"; +import { describe, expect, it, vi } from "vitest"; +import { + AIAutoCompleteExtension, + AutoCompleteSuggestion +} from "./AIAutoCompleteExtension.js"; + +// Helper to wait for debounce +const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +// Common test data +const TEST_SUGGESTION = "orld"; +const INITIAL_TEXT = "Hello "; +const DEBOUNCE_DELAY = 1; +const DEBOUNCE_WAIT = 200; + +// Helper to create an editor with the autocomplete extension +function createTestEditor(provider: (editor: BlockNoteEditor, signal: AbortSignal) => Promise) { + return BlockNoteEditor.create({ + initialContent: [ + { + type: "paragraph", + content: INITIAL_TEXT, + }, + ], + extensions: [ + AIAutoCompleteExtension({ + provider, + debounceDelay: DEBOUNCE_DELAY, + }), + ], + }); +} + +// Helper to mount editor and setup cursor position +function setupMountedEditor(editor: BlockNoteEditor) { + const element = document.createElement("div"); + editor.mount(element); + editor.setTextCursorPosition(editor.document[0].id, "end"); + return element; +} + +// Helper to trigger a text change +async function triggerTextChange(element: HTMLDivElement, newText: string) { + element.querySelector(".bn-inline-content")!.innerHTML = newText; + await wait(DEBOUNCE_WAIT); +} + +describe("AIAutoCompleteExtension", () => { + it("fetches and displays suggestions", async () => { + const provider = vi.fn().mockResolvedValue([{ suggestion: TEST_SUGGESTION }]); + const editor = createTestEditor(provider); + const element = setupMountedEditor(editor); + + await triggerTextChange(element, "Hello w"); + + expect(provider).toHaveBeenCalledOnce(); + expect(element.querySelector(".bn-autocomplete-decorator")!.innerHTML).toBe(TEST_SUGGESTION); + expect(element.querySelector(".bn-inline-content")!.textContent).toBe("Hello world"); + }); + + it("handles simultaneous changes", async () => { + // Manual promise control for testing race conditions + let resolvePromise: () => void; + const manualPromise = new Promise((resolve) => { + resolvePromise = resolve; + }); + + const provider = vi.fn().mockImplementation(async () => { + await manualPromise; + return [{ suggestion: TEST_SUGGESTION }]; + }); + + const editor = createTestEditor(provider); + const element = setupMountedEditor(editor); + + await triggerTextChange(element, "Hello w"); + expect(provider).toHaveBeenCalledOnce(); + + // Insert a block before suggestion has been returned + editor.insertBlocks([ + { + type: "paragraph", + content: "new first paragraph ", + }, + ], editor.document[0].id, "before"); + + // Resolve the promise with suggestions + resolvePromise!(); + await wait(10); + + expect(provider).toHaveBeenCalledOnce(); + expect(element.querySelector(".bn-autocomplete-decorator")!.innerHTML).toBe(TEST_SUGGESTION); + expect(element.querySelectorAll(".bn-inline-content")[1].textContent).toBe("Hello world"); + }); + + it("accepts suggestion with Tab", async () => { + const provider = vi.fn().mockResolvedValue([{ suggestion: TEST_SUGGESTION }]); + const editor = createTestEditor(provider); + const element = setupMountedEditor(editor); + + await triggerTextChange(element, "Hello w"); + + // Simulate Tab press + element.dispatchEvent(new KeyboardEvent("keydown", { key: "Tab" })); + + // Verify text inserted + const content = editor.document[0].content as any as InlineContent[]; + if (!isStyledTextInlineContent(content[0])) { + throw new Error("Content is not styled text"); + } + expect(content[0].text).toBe("Hello world"); + }); +}); \ No newline at end of file diff --git a/packages/xl-ai/src/AIAutoCompleteExtension.ts b/packages/xl-ai/src/AIAutoCompleteExtension.ts new file mode 100644 index 0000000000..ecb0f650ae --- /dev/null +++ b/packages/xl-ai/src/AIAutoCompleteExtension.ts @@ -0,0 +1,435 @@ +import { + BlockNoteEditor, + createExtension, + ExtensionOptions, + trackPosition, +} from "@blocknote/core"; +import { EditorState, Plugin, PluginKey } from "prosemirror-state"; +import { Decoration, DecorationSet } from "prosemirror-view"; + +export type AutoCompleteState = + | { + autoCompleteSuggestion: AutoCompleteSuggestion; + } + | undefined; + +const autoCompletePluginKey = new PluginKey( + "AutoCompletePlugin", +); + +export type AutoCompleteSuggestion = { + position?: number; + suggestion: string; +}; + +type InternalAutoCompleteSuggestion = { + position: number; + suggestion: string; +}; + +export type AutoCompleteProvider = ( + editor: BlockNoteEditor, + signal: AbortSignal, +) => Promise; + +export type AutoCompleteOptions = { + /** + * The provider to fetch autocomplete suggestions from. + * Can be a URL string (uses default provider) or a custom function. + */ + provider: AutoCompleteProvider | string; + /** + * Number of characters of context to send to the API when using the default provider (string URL). + * Default: 300 + */ + contextLength?: number; + /** Key to accept autocomplete suggestion. Default: "Tab" */ + acceptKey?: string; + /** Key to cancel/discard autocomplete suggestion. Default: "Escape" */ + cancelKey?: string; + /** Debounce delay in milliseconds before fetching suggestions. Default: 300 */ + debounceDelay?: number; + /** + * when true: only fetch suggestions when the cursor is at the end of a block + * when false: fetch suggestions at every cursor position (also in the middle of a sentence). + * + * @default: false + */ + onlyAtEndOfBlock?: boolean; +}; + +function getMatchingSuggestions( + autoCompleteSuggestions: InternalAutoCompleteSuggestion[], + state: EditorState, +): InternalAutoCompleteSuggestion[] { + return autoCompleteSuggestions.flatMap((suggestion) => { + // Suggestion must be before or at current cursor position + if (suggestion.position > state.selection.from) { + return []; + } + + // Suggestion must be in the same parent block + if ( + !state.doc + .resolve(suggestion.position) + .sameParent(state.selection.$from) + ) { + return []; + } + + // Get text that has been typed since the suggestion + // start position + const text = state.doc.textBetween( + suggestion.position, + state.selection.from, + ); + + // User's typed text must be a prefix of the suggestion + if ( + suggestion.suggestion.startsWith(text) && + suggestion.suggestion.length > text.length + ) { + return [ + { + position: suggestion.position, + suggestion: suggestion.suggestion.slice(text.length), + }, + ]; + } + + return []; + }); +} + +function createDefaultAutoCompleteProvider(args: { + url: string; + contextLength?: number; +}): AutoCompleteProvider { + const { url, contextLength = 300 } = args; + return async (editor, signal) => { + const state = editor.prosemirrorState; + // Get last N chars of context + const text = state.doc.textBetween( + Math.max(0, state.selection.from - contextLength), + state.selection.from, + "\n", + ); + + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ text }), + signal, + }); + + if (!response.ok) { + throw new Error(`AutoComplete request failed: ${response.statusText}`); + } + + const data = await response.json(); + return data.suggestions.map((suggestion: string) => ({ + suggestion: suggestion, + })); + }; +} + +export const AIAutoCompleteExtension = createExtension( + ({ + editor, + options, + }: ExtensionOptions) => { + let autoCompleteSuggestions: InternalAutoCompleteSuggestion[] = []; + + const acceptKey = options.acceptKey || "Tab"; + const cancelKey = options.cancelKey || "Escape"; + const debounceDelay = options.debounceDelay ?? 300; + + // Determine the provider to use: + // 1. If a string is provided, create a default provider that fetches from that URL. + // 2. If a function is provided, use it directly. + const provider = + typeof options.provider === "string" + ? createDefaultAutoCompleteProvider({ + url: options.provider, + contextLength: options.contextLength, + }) + : options.provider; + + const debounceFetchSuggestions = debounceWithAbort( + async (editor: BlockNoteEditor, signal: AbortSignal) => { + const position = editor.prosemirrorState.selection.from; + + if (options.onlyAtEndOfBlock) { + const pos = editor.prosemirrorState.doc.resolve(position); + const textAfter = editor.prosemirrorState.doc.textBetween(position, pos.after()) + + if (textAfter.trim() !== "") { + return; + } + } + + const tracked = trackPosition(editor, position); + + // fetch suggestions + const newAutoCompleteSuggestions = await provider(editor, signal); + + if (signal.aborted) { + return; + } + + // Fill in missing positions with current cursor position + const processedSuggestions = newAutoCompleteSuggestions.map( + (suggestion) => ({ + position: tracked(), + ...suggestion, + }), + ); + + autoCompleteSuggestions = processedSuggestions; + // Force plugin state update to trigger decorations refresh + editor.transact((tr) => { + tr.setMeta(autoCompletePluginKey, {}); + }); + }, + debounceDelay, + ); + + /** + * Accepts the current autocomplete suggestion and inserts it into the editor. + * @returns true if a suggestion was accepted, false otherwise + */ + const acceptAutoCompleteSuggestion = (): boolean => { + const state = autoCompletePluginKey.getState(editor.prosemirrorState); + if (state) { + editor.transact((tr) => { + tr.insertText(state.autoCompleteSuggestion.suggestion).setMeta( + autoCompletePluginKey, + { isUserInput: true }, + ); + }); + return true; + } + return false; + }; + + /** + * Discards all current autocomplete suggestions. + */ + const discardAutoCompleteSuggestions = (): void => { + autoCompleteSuggestions = []; + debounceFetchSuggestions.cancel(); + editor.transact((tr) => { + tr.setMeta(autoCompletePluginKey, {}); + }); + }; + + /** + * manually trigger fetching autocomplete suggestions at the current + * cursor position + * + * (can be used if you want to configure a key to let the user trigger + * autocomplete suggestions without having to type) + */ + const triggerAutoComplete = (): void => { + editor.transact((tr) => { + tr.setMeta(autoCompletePluginKey, { + isUserInput: true, + }); + }); + }; + + return { + acceptAutoCompleteSuggestion, + discardAutoCompleteSuggestions, + triggerAutoComplete, + key: "aiAutoCompleteExtension", + priority: 1000000, // should be lower (e.g.: -1000 to be below suggestion menu, but that currently breaks Tab) + prosemirrorPlugins: [ + new Plugin({ + key: autoCompletePluginKey, + + state: { + // Initialize the plugin's internal state. + init(): AutoCompleteState { + return undefined; + }, + + // Apply changes to the plugin state from an editor transaction. + apply: ( + transaction, + _prev, + _oldState, + newState, + ): AutoCompleteState => { + // selection is active, no autocomplete + if (newState.selection.from !== newState.selection.to) { + debounceFetchSuggestions.cancel(); + return undefined; + } + + // Are there matching suggestions? + const matchingSuggestions = getMatchingSuggestions( + autoCompleteSuggestions, + newState, + ); + + if (matchingSuggestions.length > 0) { + debounceFetchSuggestions.cancel(); + return { + autoCompleteSuggestion: matchingSuggestions[0], + }; + } + + // No matching suggestions, if isUserInput is true, debounce fetch suggestions + if (transaction.getMeta(autoCompletePluginKey)?.isUserInput) { + // (discuss with Nick what ideal architecture would be) + queueMicrotask(() => { + debounceFetchSuggestions(editor).catch((error) => { + if (error.name === "AbortError") { + // don't log + return; + } + /* eslint-disable-next-line no-console */ + console.error(error); + }); + }); + } else { + // clear suggestions + autoCompleteSuggestions = []; + } + return undefined; + }, + }, + + // TODO (discuss with Nick): + // - We need to make sure autocomplete is not triggered when a suggestion menu is open + // - --> priority of handleTextInput needs to be below suggestion menu so that is handled first + // (currently broken) + // - We need to make sure Tab completion (handleKeyDown) is handled before Tab-to-indent + // (currently ok) + props: { + handleKeyDown(view, event) { + if (event.key === acceptKey) { + const autoCompleteState = this.getState(view.state); + + if (autoCompleteState) { + // insert suggestion + view.dispatch( + view.state.tr + .insertText( + autoCompleteState.autoCompleteSuggestion.suggestion, + ) + .setMeta(autoCompletePluginKey, { isUserInput: true }), // isUserInput true to trigger new fetch + ); + return true; + } + + return false; + } + + if (event.key === cancelKey) { + discardAutoCompleteSuggestions(); + return true; + } + + return false; + }, + handleTextInput(view, _from, _to, _text, deflt) { + const tr = deflt(); + tr.setMeta(autoCompletePluginKey, { + isUserInput: true, + }); + view.dispatch(tr); + return true; + }, + + // Setup decorator on the currently active suggestion. + decorations(state) { + const autoCompleteState: AutoCompleteState = this.getState(state); + + if (!autoCompleteState) { + return null; + } + + // console.log(autoCompleteState); + // Creates an inline decoration around the trigger character. + return DecorationSet.create(state.doc, [ + Decoration.widget( + state.selection.from, + renderAutoCompleteSuggestion( + autoCompleteState.autoCompleteSuggestion.suggestion, + ), + {}, + ), + ]); + }, + }, + }), + ], + }; + }, +); + +function renderAutoCompleteSuggestion(suggestion: string) { + const element = document.createElement("span"); + element.classList.add("bn-autocomplete-decorator"); + element.textContent = suggestion; + return element; +} + +export function debounceWithAbort( + fn: (...args: [...T, AbortSignal]) => Promise | R, + delay = 300, +) { + let timeoutId: ReturnType | null = null; + let controller: AbortController | null = null; + + const debounced = (...args: T): Promise => { + // Clear pending timeout + if (timeoutId) { + clearTimeout(timeoutId); + } + + // Abort any in-flight execution + if (controller) { + controller.abort(); + } + + controller = new AbortController(); + const signal = controller.signal; + + return new Promise((resolve, reject) => { + timeoutId = setTimeout(async () => { + try { + const result = await fn(...args, signal); + resolve(result); + } catch (err) { + reject(err); + } + }, delay); + }); + }; + + // External cancel method + debounced.cancel = () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + timeoutId = null; + + if (controller) { + controller.abort(); + } + controller = null; + }; + + return debounced; +} + +// Add a type for the cancel method +export interface DebouncedFunction { + (...args: T): Promise; + cancel(): void; +} diff --git a/packages/xl-ai/src/index.ts b/packages/xl-ai/src/index.ts index 2f7dd58c09..8055a88196 100644 --- a/packages/xl-ai/src/index.ts +++ b/packages/xl-ai/src/index.ts @@ -1,5 +1,6 @@ import "./style.css"; +export * from "./AIAutoCompleteExtension.js"; export * from "./AIExtension.js"; export * from "./components/AIMenu/AIMenu.js"; export * from "./components/AIMenu/AIMenuController.js"; @@ -9,3 +10,5 @@ export * from "./components/FormattingToolbar/AIToolbarButton.js"; export * from "./components/SuggestionMenu/getAISlashMenuItems.js"; export * from "./hooks/useAIDictionary.js"; export * from "./server.js"; +export * from "./streamTool/index.js"; + diff --git a/packages/xl-ai/src/style.css b/packages/xl-ai/src/style.css index 4b7558d518..5ebcf45d53 100644 --- a/packages/xl-ai/src/style.css +++ b/packages/xl-ai/src/style.css @@ -31,3 +31,8 @@ del, text-decoration: line-through; text-decoration-thickness: 1px; } + +.bn-autocomplete-decorator { + display: inline; + color: var(--bn-colors-side-menu); /* Hacky, shoyuld review variable setup */ +} diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index 8b2688dfe0..70325d9607 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -1,588 +1,615 @@ // generated by dev-scripts/examples/gen.ts -export const examples = { - basic: { - pathFromRoot: "examples/01-basic", - slug: "basic", - projects: [ - { - projectSlug: "minimal", - fullSlug: "basic/minimal", - pathFromRoot: "examples/01-basic/01-minimal", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Basic"], - }, - title: "Basic Setup", - group: { - pathFromRoot: "examples/01-basic", - slug: "basic", - }, - readme: - "This example shows the minimal code required to set up a BlockNote editor in React.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)", - }, - { - projectSlug: "block-objects", - fullSlug: "basic/block-objects", - pathFromRoot: "examples/01-basic/02-block-objects", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["Basic", "Blocks", "Inline Content"], - }, - title: "Displaying Document JSON", - group: { - pathFromRoot: "examples/01-basic", - slug: "basic", - }, - readme: - "In this example, the document's JSON representation is displayed below the editor.\n\n**Try it out:** Try typing in the editor and see the JSON update!\n\n**Relevant Docs:**\n\n- [Document Structure](/docs/foundations/document-structure)\n- [Getting the Document](/docs/reference/editor/manipulating-content)", - }, - { - projectSlug: "multi-column", - fullSlug: "basic/multi-column", - pathFromRoot: "examples/01-basic/03-multi-column", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["Basic", "Blocks"], - dependencies: { - "@blocknote/xl-multi-column": "latest", + export const examples = { + "basic": { + "pathFromRoot": "examples/01-basic", + "slug": "basic", + "projects": [ + { + "projectSlug": "minimal", + "fullSlug": "basic/minimal", + "pathFromRoot": "examples/01-basic/01-minimal", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic" + ] + }, + "title": "Basic Setup", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + }, + "readme": "This example shows the minimal code required to set up a BlockNote editor in React.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)" + }, + { + "projectSlug": "block-objects", + "fullSlug": "basic/block-objects", + "pathFromRoot": "examples/01-basic/02-block-objects", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Basic", + "Blocks", + "Inline Content" + ] + }, + "title": "Displaying Document JSON", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + }, + "readme": "In this example, the document's JSON representation is displayed below the editor.\n\n**Try it out:** Try typing in the editor and see the JSON update!\n\n**Relevant Docs:**\n\n- [Document Structure](/docs/foundations/document-structure)\n- [Getting the Document](/docs/reference/editor/manipulating-content)" + }, + { + "projectSlug": "multi-column", + "fullSlug": "basic/multi-column", + "pathFromRoot": "examples/01-basic/03-multi-column", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Basic", + "Blocks" + ], + "dependencies": { + "@blocknote/xl-multi-column": "latest" } as any, - pro: true, - }, - title: "Multi-Column Blocks", - group: { - pathFromRoot: "examples/01-basic", - slug: "basic", - }, - readme: - "This example showcases multi-column blocks, allowing you to stack blocks next to each other. These come as part of the `@blocknote/xl-multi-column` package.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Document Structure](/docs/foundations/document-structure)", - }, - { - projectSlug: "default-blocks", - fullSlug: "basic/default-blocks", - pathFromRoot: "examples/01-basic/04-default-blocks", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["Basic", "Blocks", "Inline Content"], - }, - title: "Default Schema Showcase", - group: { - pathFromRoot: "examples/01-basic", - slug: "basic", - }, - readme: - "This example showcases each block and inline content type in BlockNote's default schema.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Document Structure](/docs/foundations/document-structure)\n- [Default Schema](/docs/foundations/schemas)", - }, - { - projectSlug: "removing-default-blocks", - fullSlug: "basic/removing-default-blocks", - pathFromRoot: "examples/01-basic/05-removing-default-blocks", - config: { - playground: true, - docs: true, - author: "hunxjunedo", - tags: ["Basic", "removing", "blocks"], - }, - title: "Removing Default Blocks from Schema", - group: { - pathFromRoot: "examples/01-basic", - slug: "basic", - }, - readme: - "This example shows how to change the default schema and disable the Audio and Image blocks. To do this, we pass in a custom schema based on the built-in, default schema, with two specific blocks removed.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Custom Schemas](/docs/features/custom-schemas)\n- [Default Schema](/docs/foundations/schemas)", - }, - { - projectSlug: "block-manipulation", - fullSlug: "basic/block-manipulation", - pathFromRoot: "examples/01-basic/06-block-manipulation", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Basic", "Blocks"], - }, - title: "Manipulating Blocks", - group: { - pathFromRoot: "examples/01-basic", - slug: "basic", - }, - readme: - "This example shows 4 buttons to manipulate the first block using the `insertBlocks`, `updateBlock`, `removeBlocks` and `replaceBlocks` methods.\n\n**Relevant Docs:**\n\n- [Block Manipulation](/docs/reference/editor/manipulating-content)", - }, - { - projectSlug: "selection-blocks", - fullSlug: "basic/selection-blocks", - pathFromRoot: "examples/01-basic/07-selection-blocks", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Basic", "Blocks"], - }, - title: "Displaying Selected Blocks", - group: { - pathFromRoot: "examples/01-basic", - slug: "basic", - }, - readme: - "In this example, the JSON representation of blocks spanned by the user's selection, is displayed below the editor.\n\n**Try it out:** Select different blocks in the editor and see the JSON update!\n\n**Relevant Docs:**\n\n- [Cursor Selections](/docs/reference/editor/cursor-selections)", - }, - { - projectSlug: "ariakit", - fullSlug: "basic/ariakit", - pathFromRoot: "examples/01-basic/08-ariakit", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Basic"], - }, - title: "Use with Ariakit", - group: { - pathFromRoot: "examples/01-basic", - slug: "basic", - }, - readme: - "This example shows how you can use BlockNote with Ariakit (instead of Mantine).\n\n**Relevant Docs:**\n\n- [Ariakit Docs](/docs/getting-started/ariakit)\n- [Editor Setup](/docs/getting-started/editor-setup)", - }, - { - projectSlug: "shadcn", - fullSlug: "basic/shadcn", - pathFromRoot: "examples/01-basic/09-shadcn", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Basic"], - tailwind: true, - stackBlitz: false, - }, - title: "Use with ShadCN", - group: { - pathFromRoot: "examples/01-basic", - slug: "basic", - }, - readme: - "This example shows how you can use BlockNote with ShadCN (instead of Mantine).\n\n**Relevant Docs:**\n\n- [Getting Started with ShadCN](/docs/getting-started/shadcn)", - }, - { - projectSlug: "localization", - fullSlug: "basic/localization", - pathFromRoot: "examples/01-basic/10-localization", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["Basic"], - }, - title: "Localization (i18n)", - group: { - pathFromRoot: "examples/01-basic", - slug: "basic", - }, - readme: - "In this example, we pass in a custom dictionary to change the interface of the editor to use Dutch (NL) strings.\n\nYou can also provide your own dictionary to customize the strings used in the editor, or submit a Pull Request to add support for your language of your choice.\n\n**Relevant Docs:**\n\n- [Localization](/docs/features/localization)", - }, - { - projectSlug: "custom-placeholder", - fullSlug: "basic/custom-placeholder", - pathFromRoot: "examples/01-basic/11-custom-placeholder", - config: { - playground: true, - docs: true, - author: "ezhil56x", - tags: ["Basic"], - }, - title: "Change placeholder text", - group: { - pathFromRoot: "examples/01-basic", - slug: "basic", - }, - readme: - "In this example, we show how to change the placeholders:\n\n- For an empty document, we show a placeholder `Start typing..` (by default, this is not set)\n- the default placeholder in this editor shows `Custom default placeholder` instead of the default (`Enter text or type '/' for commands`)\n- for Headings, the placeholder shows `Custom heading placeholder` instead of the default (`Heading`). Try adding a Heading to see the change\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Localization (i18n)](/examples/basic/localization)", - }, - { - projectSlug: "multi-editor", - fullSlug: "basic/multi-editor", - pathFromRoot: "examples/01-basic/12-multi-editor", - config: { - playground: true, - docs: true, - author: "areknawo", - tags: ["Basic"], - }, - title: "Multi-Editor Setup", - group: { - pathFromRoot: "examples/01-basic", - slug: "basic", - }, - readme: - "This example showcases use of multiple editors in a single page - you can even drag blocks between them.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)", - }, - { - projectSlug: "custom-paste-handler", - fullSlug: "basic/custom-paste-handler", - pathFromRoot: "examples/01-basic/13-custom-paste-handler", - config: { - playground: true, - docs: true, - author: "nperez0111", - tags: ["Basic"], - }, - title: "Custom Paste Handler", - group: { - pathFromRoot: "examples/01-basic", - slug: "basic", - }, - readme: - "In this example, we change the default paste handler to append some text to the pasted content when the content is plain text.\n\n**Try it out:** Use the buttons to copy some content to the clipboard and paste it in the editor to trigger our custom paste handler.\n\n**Relevant Docs:**\n\n- [Paste Handling](/docs/reference/editor/paste-handling)", - }, - { - projectSlug: "testing", - fullSlug: "basic/testing", - pathFromRoot: "examples/01-basic/testing", - config: { - playground: true, - docs: false, - }, - title: "Test Editor", - group: { - pathFromRoot: "examples/01-basic", - slug: "basic", - }, - readme: "This example is meant for use in end-to-end tests.", - }, - ], + "pro": true + }, + "title": "Multi-Column Blocks", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + }, + "readme": "This example showcases multi-column blocks, allowing you to stack blocks next to each other. These come as part of the `@blocknote/xl-multi-column` package.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Document Structure](/docs/foundations/document-structure)" + }, + { + "projectSlug": "default-blocks", + "fullSlug": "basic/default-blocks", + "pathFromRoot": "examples/01-basic/04-default-blocks", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Basic", + "Blocks", + "Inline Content" + ] + }, + "title": "Default Schema Showcase", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + }, + "readme": "This example showcases each block and inline content type in BlockNote's default schema.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Document Structure](/docs/foundations/document-structure)\n- [Default Schema](/docs/foundations/schemas)" + }, + { + "projectSlug": "removing-default-blocks", + "fullSlug": "basic/removing-default-blocks", + "pathFromRoot": "examples/01-basic/05-removing-default-blocks", + "config": { + "playground": true, + "docs": true, + "author": "hunxjunedo", + "tags": [ + "Basic", + "removing", + "blocks" + ] + }, + "title": "Removing Default Blocks from Schema", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + }, + "readme": "This example shows how to change the default schema and disable the Audio and Image blocks. To do this, we pass in a custom schema based on the built-in, default schema, with two specific blocks removed.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Custom Schemas](/docs/features/custom-schemas)\n- [Default Schema](/docs/foundations/schemas)" + }, + { + "projectSlug": "block-manipulation", + "fullSlug": "basic/block-manipulation", + "pathFromRoot": "examples/01-basic/06-block-manipulation", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic", + "Blocks" + ] + }, + "title": "Manipulating Blocks", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + }, + "readme": "This example shows 4 buttons to manipulate the first block using the `insertBlocks`, `updateBlock`, `removeBlocks` and `replaceBlocks` methods.\n\n**Relevant Docs:**\n\n- [Block Manipulation](/docs/reference/editor/manipulating-content)" + }, + { + "projectSlug": "selection-blocks", + "fullSlug": "basic/selection-blocks", + "pathFromRoot": "examples/01-basic/07-selection-blocks", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic", + "Blocks" + ] + }, + "title": "Displaying Selected Blocks", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + }, + "readme": "In this example, the JSON representation of blocks spanned by the user's selection, is displayed below the editor.\n\n**Try it out:** Select different blocks in the editor and see the JSON update!\n\n**Relevant Docs:**\n\n- [Cursor Selections](/docs/reference/editor/cursor-selections)" + }, + { + "projectSlug": "ariakit", + "fullSlug": "basic/ariakit", + "pathFromRoot": "examples/01-basic/08-ariakit", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic" + ] + }, + "title": "Use with Ariakit", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + }, + "readme": "This example shows how you can use BlockNote with Ariakit (instead of Mantine).\n\n**Relevant Docs:**\n\n- [Ariakit Docs](/docs/getting-started/ariakit)\n- [Editor Setup](/docs/getting-started/editor-setup)" + }, + { + "projectSlug": "shadcn", + "fullSlug": "basic/shadcn", + "pathFromRoot": "examples/01-basic/09-shadcn", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic" + ], + "tailwind": true, + "stackBlitz": false + }, + "title": "Use with ShadCN", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + }, + "readme": "This example shows how you can use BlockNote with ShadCN (instead of Mantine).\n\n**Relevant Docs:**\n\n- [Getting Started with ShadCN](/docs/getting-started/shadcn)" + }, + { + "projectSlug": "localization", + "fullSlug": "basic/localization", + "pathFromRoot": "examples/01-basic/10-localization", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Basic" + ] + }, + "title": "Localization (i18n)", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + }, + "readme": "In this example, we pass in a custom dictionary to change the interface of the editor to use Dutch (NL) strings.\n\nYou can also provide your own dictionary to customize the strings used in the editor, or submit a Pull Request to add support for your language of your choice.\n\n**Relevant Docs:**\n\n- [Localization](/docs/features/localization)" + }, + { + "projectSlug": "custom-placeholder", + "fullSlug": "basic/custom-placeholder", + "pathFromRoot": "examples/01-basic/11-custom-placeholder", + "config": { + "playground": true, + "docs": true, + "author": "ezhil56x", + "tags": [ + "Basic" + ] + }, + "title": "Change placeholder text", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + }, + "readme": "In this example, we show how to change the placeholders:\n\n- For an empty document, we show a placeholder `Start typing..` (by default, this is not set)\n- the default placeholder in this editor shows `Custom default placeholder` instead of the default (`Enter text or type '/' for commands`)\n- for Headings, the placeholder shows `Custom heading placeholder` instead of the default (`Heading`). Try adding a Heading to see the change\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Localization (i18n)](/examples/basic/localization)" + }, + { + "projectSlug": "multi-editor", + "fullSlug": "basic/multi-editor", + "pathFromRoot": "examples/01-basic/12-multi-editor", + "config": { + "playground": true, + "docs": true, + "author": "areknawo", + "tags": [ + "Basic" + ] + }, + "title": "Multi-Editor Setup", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + }, + "readme": "This example showcases use of multiple editors in a single page - you can even drag blocks between them.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)" + }, + { + "projectSlug": "custom-paste-handler", + "fullSlug": "basic/custom-paste-handler", + "pathFromRoot": "examples/01-basic/13-custom-paste-handler", + "config": { + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": [ + "Basic" + ] + }, + "title": "Custom Paste Handler", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + }, + "readme": "In this example, we change the default paste handler to append some text to the pasted content when the content is plain text.\n\n**Try it out:** Use the buttons to copy some content to the clipboard and paste it in the editor to trigger our custom paste handler.\n\n**Relevant Docs:**\n\n- [Paste Handling](/docs/reference/editor/paste-handling)" + }, + { + "projectSlug": "testing", + "fullSlug": "basic/testing", + "pathFromRoot": "examples/01-basic/testing", + "config": { + "playground": true, + "docs": false + }, + "title": "Test Editor", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + }, + "readme": "This example is meant for use in end-to-end tests." + } + ] }, - backend: { - pathFromRoot: "examples/02-backend", - slug: "backend", - projects: [ - { - projectSlug: "file-uploading", - fullSlug: "backend/file-uploading", - pathFromRoot: "examples/02-backend/01-file-uploading", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Intermediate", "Saving/Loading"], - }, - title: "Upload Files", - group: { - pathFromRoot: "examples/02-backend", - slug: "backend", - }, - readme: - 'This example allows users to upload files and use them in the editor. The files are uploaded to [/TMP/Files](https://tmpfiles.org/), and can be used for File, Image, Video, and Audio blocks.\n\n**Try it out:** Click the "Add Image" button and see there\'s now an "Upload" tab in the toolbar!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [File Block](/docs/features/blocks/embeds#file)', - }, - { - projectSlug: "saving-loading", - fullSlug: "backend/saving-loading", - pathFromRoot: "examples/02-backend/02-saving-loading", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["Intermediate", "Blocks", "Saving/Loading"], - }, - title: "Saving & Loading", - group: { - pathFromRoot: "examples/02-backend", - slug: "backend", - }, - readme: - "This example shows how to save the editor contents to local storage whenever a change is made, and load the saved contents when the editor is created.\n\nYou can replace the `saveToStorage` and `loadFromStorage` functions with calls to your backend or database.\n\n**Try it out:** Try typing in the editor and reloading the page!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Getting the Document](/docs/foundations/manipulating-content#reading-blocks)", - }, - { - projectSlug: "s3", - fullSlug: "backend/s3", - pathFromRoot: "examples/02-backend/03-s3", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Intermediate", "Saving/Loading"], - dependencies: { + "backend": { + "pathFromRoot": "examples/02-backend", + "slug": "backend", + "projects": [ + { + "projectSlug": "file-uploading", + "fullSlug": "backend/file-uploading", + "pathFromRoot": "examples/02-backend/01-file-uploading", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Intermediate", + "Saving/Loading" + ] + }, + "title": "Upload Files", + "group": { + "pathFromRoot": "examples/02-backend", + "slug": "backend" + }, + "readme": "This example allows users to upload files and use them in the editor. The files are uploaded to [/TMP/Files](https://tmpfiles.org/), and can be used for File, Image, Video, and Audio blocks.\n\n**Try it out:** Click the \"Add Image\" button and see there's now an \"Upload\" tab in the toolbar!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [File Block](/docs/features/blocks/embeds#file)" + }, + { + "projectSlug": "saving-loading", + "fullSlug": "backend/saving-loading", + "pathFromRoot": "examples/02-backend/02-saving-loading", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Intermediate", + "Blocks", + "Saving/Loading" + ] + }, + "title": "Saving & Loading", + "group": { + "pathFromRoot": "examples/02-backend", + "slug": "backend" + }, + "readme": "This example shows how to save the editor contents to local storage whenever a change is made, and load the saved contents when the editor is created.\n\nYou can replace the `saveToStorage` and `loadFromStorage` functions with calls to your backend or database.\n\n**Try it out:** Try typing in the editor and reloading the page!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Getting the Document](/docs/foundations/manipulating-content#reading-blocks)" + }, + { + "projectSlug": "s3", + "fullSlug": "backend/s3", + "pathFromRoot": "examples/02-backend/03-s3", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Intermediate", + "Saving/Loading" + ], + "dependencies": { "@aws-sdk/client-s3": "^3.609.0", - "@aws-sdk/s3-request-presigner": "^3.609.0", - } as any, - pro: true, - }, - title: "Upload Files to AWS S3", - group: { - pathFromRoot: "examples/02-backend", - slug: "backend", - }, - readme: - 'This example allows users to upload files to an AWS S3 bucket and use them in the editor. The files can be used for File, Image, Video, and Audio blocks.\n\n**Try it out:** Click the "Add Image" button and see there\'s now an "Upload" tab in the toolbar!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [File Block](/docs/features/blocks/embeds#file)', - }, - { - projectSlug: "rendering-static-documents", - fullSlug: "backend/rendering-static-documents", - pathFromRoot: "examples/02-backend/04-rendering-static-documents", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["server"], - dependencies: { - "@blocknote/server-util": "latest", + "@aws-sdk/s3-request-presigner": "^3.609.0" } as any, + "pro": true }, - title: "Rendering static documents", - group: { - pathFromRoot: "examples/02-backend", - slug: "backend", + "title": "Upload Files to AWS S3", + "group": { + "pathFromRoot": "examples/02-backend", + "slug": "backend" }, - readme: - "This example shows how you can use HTML exported using the `blocksToFullHTML` and render it as a static document (a view-only document, without the editor). You can use this for example if you use BlockNote to edit blog posts in a CMS, but want to display non-editable static, published pages to end-users.\n\n**Relevant Docs:**\n\n- [Server-side processing](/docs/features/server-processing)", + "readme": "This example allows users to upload files to an AWS S3 bucket and use them in the editor. The files can be used for File, Image, Video, and Audio blocks.\n\n**Try it out:** Click the \"Add Image\" button and see there's now an \"Upload\" tab in the toolbar!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [File Block](/docs/features/blocks/embeds#file)" }, - ], + { + "projectSlug": "rendering-static-documents", + "fullSlug": "backend/rendering-static-documents", + "pathFromRoot": "examples/02-backend/04-rendering-static-documents", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "server" + ], + "dependencies": { + "@blocknote/server-util": "latest" + } as any + }, + "title": "Rendering static documents", + "group": { + "pathFromRoot": "examples/02-backend", + "slug": "backend" + }, + "readme": "This example shows how you can use HTML exported using the `blocksToFullHTML` and render it as a static document (a view-only document, without the editor). You can use this for example if you use BlockNote to edit blog posts in a CMS, but want to display non-editable static, published pages to end-users.\n\n**Relevant Docs:**\n\n- [Server-side processing](/docs/features/server-processing)" + } + ] }, "ui-components": { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", - projects: [ - { - projectSlug: "formatting-toolbar-buttons", - fullSlug: "ui-components/formatting-toolbar-buttons", - pathFromRoot: "examples/03-ui-components/02-formatting-toolbar-buttons", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: [ + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components", + "projects": [ + { + "projectSlug": "formatting-toolbar-buttons", + "fullSlug": "ui-components/formatting-toolbar-buttons", + "pathFromRoot": "examples/03-ui-components/02-formatting-toolbar-buttons", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ "Intermediate", "Inline Content", "UI Components", - "Formatting Toolbar", - ], + "Formatting Toolbar" + ] }, - title: "Adding Formatting Toolbar Buttons", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", + "title": "Adding Formatting Toolbar Buttons", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" }, - readme: - "In this example, we add a blue text/background color and code style button to the Formatting Toolbar. We also make sure it only shows up when some text is selected.\n\n**Try it out:** Select some text to open the Formatting Toolbar, and click one of the new buttons!\n\n**Relevant Docs:**\n\n- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar)\n- [Manipulating Inline Content](/docs/reference/editor/manipulating-content)\n- [Editor Setup](/docs/getting-started/editor-setup)", + "readme": "In this example, we add a blue text/background color and code style button to the Formatting Toolbar. We also make sure it only shows up when some text is selected.\n\n**Try it out:** Select some text to open the Formatting Toolbar, and click one of the new buttons!\n\n**Relevant Docs:**\n\n- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar)\n- [Manipulating Inline Content](/docs/reference/editor/manipulating-content)\n- [Editor Setup](/docs/getting-started/editor-setup)" }, { - projectSlug: "formatting-toolbar-block-type-items", - fullSlug: "ui-components/formatting-toolbar-block-type-items", - pathFromRoot: - "examples/03-ui-components/03-formatting-toolbar-block-type-items", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: [ + "projectSlug": "formatting-toolbar-block-type-items", + "fullSlug": "ui-components/formatting-toolbar-block-type-items", + "pathFromRoot": "examples/03-ui-components/03-formatting-toolbar-block-type-items", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ "Intermediate", "Blocks", "UI Components", "Formatting Toolbar", - "Custom Schemas", + "Custom Schemas" ], - dependencies: { + "dependencies": { "@mantine/core": "^8.3.4", - "react-icons": "^5.2.1", - } as any, + "react-icons": "^5.2.1" + } as any }, - title: "Adding Block Type Select Items", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", - }, - readme: - 'In this example, we add an item to the Block Type Select, so that it works for a custom alert block we create.\n\n**Try it out:** Select some text to open the Formatting Toolbar, and click "Alert" in the Block Type Select to change the selected block!\n\n**Relevant Docs:**\n\n- [Changing Block Type Select Items](/docs/react/components/formatting-toolbar)\n- [Custom Block Types](/docs/features/custom-schemas/custom-blocks)\n- [Editor Setup](/docs/getting-started/editor-setup)', - }, - { - projectSlug: "side-menu-buttons", - fullSlug: "ui-components/side-menu-buttons", - pathFromRoot: "examples/03-ui-components/04-side-menu-buttons", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Intermediate", "Blocks", "UI Components", "Block Side Menu"], - dependencies: { - "react-icons": "^5.2.1", - } as any, - }, - title: "Adding Block Side Menu Buttons", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", - }, - readme: - "In this example, we replace the button to add a block in the Block Side Menu, with a button to remove the hovered block.\n\n**Try it out:** Hover a block to open the Block Side Menu, and click the new button!\n\n**Relevant Docs:**\n\n- [Changing the Block Side Menu](/docs/react/components/side-menu)\n- [Removing Blocks](/docs/reference/editor/manipulating-content)\n- [Editor Setup](/docs/getting-started/editor-setup)", - }, - { - projectSlug: "side-menu-drag-handle-items", - fullSlug: "ui-components/side-menu-drag-handle-items", - pathFromRoot: - "examples/03-ui-components/05-side-menu-drag-handle-items", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Intermediate", "Blocks", "UI Components", "Block Side Menu"], - dependencies: { - "react-icons": "^5.2.1", - } as any, - }, - title: "Adding Drag Handle Menu Items", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", + "title": "Adding Block Type Select Items", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" }, - readme: - 'In this example, we add an item to the Drag Handle Menu, which resets the hovered block to a paragraph.\n\n**Try it out:** Hover a block to open the Block Side Menu, and click "Reset Type" in the Drag Handle Menu to reset the selected block!\n\n**Relevant Docs:**\n\n- [Changing Drag Handle Menu Items](/docs/react/components/side-menu)\n- [Updating Blocks](/docs/reference/editor/manipulating-content)\n- [Editor Setup](/docs/getting-started/editor-setup)', + "readme": "In this example, we add an item to the Block Type Select, so that it works for a custom alert block we create.\n\n**Try it out:** Select some text to open the Formatting Toolbar, and click \"Alert\" in the Block Type Select to change the selected block!\n\n**Relevant Docs:**\n\n- [Changing Block Type Select Items](/docs/react/components/formatting-toolbar)\n- [Custom Block Types](/docs/features/custom-schemas/custom-blocks)\n- [Editor Setup](/docs/getting-started/editor-setup)" }, { - projectSlug: "suggestion-menus-slash-menu-items", - fullSlug: "ui-components/suggestion-menus-slash-menu-items", - pathFromRoot: - "examples/03-ui-components/06-suggestion-menus-slash-menu-items", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: [ + "projectSlug": "side-menu-buttons", + "fullSlug": "ui-components/side-menu-buttons", + "pathFromRoot": "examples/03-ui-components/04-side-menu-buttons", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Intermediate", + "Blocks", + "UI Components", + "Block Side Menu" + ], + "dependencies": { + "react-icons": "^5.2.1" + } as any + }, + "title": "Adding Block Side Menu Buttons", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" + }, + "readme": "In this example, we replace the button to add a block in the Block Side Menu, with a button to remove the hovered block.\n\n**Try it out:** Hover a block to open the Block Side Menu, and click the new button!\n\n**Relevant Docs:**\n\n- [Changing the Block Side Menu](/docs/react/components/side-menu)\n- [Removing Blocks](/docs/reference/editor/manipulating-content)\n- [Editor Setup](/docs/getting-started/editor-setup)" + }, + { + "projectSlug": "side-menu-drag-handle-items", + "fullSlug": "ui-components/side-menu-drag-handle-items", + "pathFromRoot": "examples/03-ui-components/05-side-menu-drag-handle-items", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Intermediate", + "Blocks", + "UI Components", + "Block Side Menu" + ], + "dependencies": { + "react-icons": "^5.2.1" + } as any + }, + "title": "Adding Drag Handle Menu Items", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" + }, + "readme": "In this example, we add an item to the Drag Handle Menu, which resets the hovered block to a paragraph.\n\n**Try it out:** Hover a block to open the Block Side Menu, and click \"Reset Type\" in the Drag Handle Menu to reset the selected block!\n\n**Relevant Docs:**\n\n- [Changing Drag Handle Menu Items](/docs/react/components/side-menu)\n- [Updating Blocks](/docs/reference/editor/manipulating-content)\n- [Editor Setup](/docs/getting-started/editor-setup)" + }, + { + "projectSlug": "suggestion-menus-slash-menu-items", + "fullSlug": "ui-components/suggestion-menus-slash-menu-items", + "pathFromRoot": "examples/03-ui-components/06-suggestion-menus-slash-menu-items", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ "Intermediate", "Blocks", "UI Components", "Suggestion Menus", - "Slash Menu", + "Slash Menu" ], - dependencies: { - "react-icons": "^5.2.1", - } as any, - }, - title: "Adding Slash Menu Items", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", - }, - readme: - 'In this example, we add an item to the Slash Menu, which adds a new block below with a bold "Hello World" string.\n\n**Try it out:** Press the "/" key to open the Slash Menu and select the new item!\n\n**Relevant Docs:**\n\n- [Changing Slash Menu Items](/docs/react/components/suggestion-menus)\n- [Getting Text Cursor Position](/docs/reference/editor/cursor-selections)\n- [Inserting New Blocks](/docs/reference/editor/manipulating-content)\n- [Editor Setup](/docs/getting-started/editor-setup)', - }, - { - projectSlug: "suggestion-menus-slash-menu-component", - fullSlug: "ui-components/suggestion-menus-slash-menu-component", - pathFromRoot: - "examples/03-ui-components/07-suggestion-menus-slash-menu-component", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: [ + "dependencies": { + "react-icons": "^5.2.1" + } as any + }, + "title": "Adding Slash Menu Items", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" + }, + "readme": "In this example, we add an item to the Slash Menu, which adds a new block below with a bold \"Hello World\" string.\n\n**Try it out:** Press the \"/\" key to open the Slash Menu and select the new item!\n\n**Relevant Docs:**\n\n- [Changing Slash Menu Items](/docs/react/components/suggestion-menus)\n- [Getting Text Cursor Position](/docs/reference/editor/cursor-selections)\n- [Inserting New Blocks](/docs/reference/editor/manipulating-content)\n- [Editor Setup](/docs/getting-started/editor-setup)" + }, + { + "projectSlug": "suggestion-menus-slash-menu-component", + "fullSlug": "ui-components/suggestion-menus-slash-menu-component", + "pathFromRoot": "examples/03-ui-components/07-suggestion-menus-slash-menu-component", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ "Intermediate", "UI Components", "Suggestion Menus", "Slash Menu", - "Appearance & Styling", - ], + "Appearance & Styling" + ] }, - title: "Replacing Slash Menu Component", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", + "title": "Replacing Slash Menu Component", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" }, - readme: - 'In this example, we replace the default Slash Menu component with a basic custom one.\n\n**Try it out:** Press the "/" key to see the new Slash Menu!\n\n**Relevant Docs:**\n\n- [Replacing the Slash Menu Component](/docs/react/components/suggestion-menus)\n- [Editor Setup](/docs/getting-started/editor-setup)', + "readme": "In this example, we replace the default Slash Menu component with a basic custom one.\n\n**Try it out:** Press the \"/\" key to see the new Slash Menu!\n\n**Relevant Docs:**\n\n- [Replacing the Slash Menu Component](/docs/react/components/suggestion-menus)\n- [Editor Setup](/docs/getting-started/editor-setup)" }, { - projectSlug: "suggestion-menus-emoji-picker-columns", - fullSlug: "ui-components/suggestion-menus-emoji-picker-columns", - pathFromRoot: - "examples/03-ui-components/08-suggestion-menus-emoji-picker-columns", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: [ + "projectSlug": "suggestion-menus-emoji-picker-columns", + "fullSlug": "ui-components/suggestion-menus-emoji-picker-columns", + "pathFromRoot": "examples/03-ui-components/08-suggestion-menus-emoji-picker-columns", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ "Intermediate", "Blocks", "UI Components", "Suggestion Menus", - "Emoji Picker", - ], + "Emoji Picker" + ] }, - title: "Changing Emoji Picker Columns", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", + "title": "Changing Emoji Picker Columns", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" }, - readme: - 'In this example, we change the Emoji Picker to display 5 columns instead of 10.\n\n**Try it out:** Press the ":" key to open the Emoji Picker!\n\n**Relevant Docs:**\n\n- [Changing Emoji Picker Columns](/docs/react/components/suggestion-menus)\n- [Editor Setup](/docs/getting-started/editor-setup)', + "readme": "In this example, we change the Emoji Picker to display 5 columns instead of 10.\n\n**Try it out:** Press the \":\" key to open the Emoji Picker!\n\n**Relevant Docs:**\n\n- [Changing Emoji Picker Columns](/docs/react/components/suggestion-menus)\n- [Editor Setup](/docs/getting-started/editor-setup)" }, { - projectSlug: "suggestion-menus-emoji-picker-component", - fullSlug: "ui-components/suggestion-menus-emoji-picker-component", - pathFromRoot: - "examples/03-ui-components/09-suggestion-menus-emoji-picker-component", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: [ + "projectSlug": "suggestion-menus-emoji-picker-component", + "fullSlug": "ui-components/suggestion-menus-emoji-picker-component", + "pathFromRoot": "examples/03-ui-components/09-suggestion-menus-emoji-picker-component", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ "Intermediate", "UI Components", "Suggestion Menus", "Emoji Picker", - "Appearance & Styling", - ], + "Appearance & Styling" + ] }, - title: "Replacing Emoji Picker Component", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", + "title": "Replacing Emoji Picker Component", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" }, - readme: - 'In this example, we replace the default Emoji Picker component with a basic custom one.\n\n**Try it out:** Press the ":" key to see the new Emoji Picker!\n\n**Relevant Docs:**\n\n- [Replacing the Emoji Picker Component](/docs/react/components/suggestion-menus)\n- [Editor Setup](/docs/getting-started/editor-setup)', + "readme": "In this example, we replace the default Emoji Picker component with a basic custom one.\n\n**Try it out:** Press the \":\" key to see the new Emoji Picker!\n\n**Relevant Docs:**\n\n- [Replacing the Emoji Picker Component](/docs/react/components/suggestion-menus)\n- [Editor Setup](/docs/getting-started/editor-setup)" }, { - projectSlug: "suggestion-menus-grid-mentions", - fullSlug: "ui-components/suggestion-menus-grid-mentions", - pathFromRoot: - "examples/03-ui-components/10-suggestion-menus-grid-mentions", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: [ + "projectSlug": "suggestion-menus-grid-mentions", + "fullSlug": "ui-components/suggestion-menus-grid-mentions", + "pathFromRoot": "examples/03-ui-components/10-suggestion-menus-grid-mentions", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ "Intermediate", "Inline Content", "Custom Schemas", - "Suggestion Menus", - ], + "Suggestion Menus" + ] }, - title: "Grid Mentions Menu", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", + "title": "Grid Mentions Menu", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" }, - readme: - 'In this example, we create a custom `Mention` inline content type which is used to tag people. In addition, we create a new Suggestion Menu for mentions which opens with the "@" character. This Suggestion Menu is displayed as a grid of 2 columns, where each item is the first letter of the person\'s name.\n\n**Try it out:** Press the "@" key to open the mentions menu and insert a mention!\n\n**Relevant Docs:**\n\n- [Custom Inline Content Types](/docs/features/custom-schemas/custom-inline-content)\n- [Creating Suggestion Menus](/docs/react/components/suggestion-menus)\n- [Editor Setup](/docs/getting-started/editor-setup)', + "readme": "In this example, we create a custom `Mention` inline content type which is used to tag people. In addition, we create a new Suggestion Menu for mentions which opens with the \"@\" character. This Suggestion Menu is displayed as a grid of 2 columns, where each item is the first letter of the person's name.\n\n**Try it out:** Press the \"@\" key to open the mentions menu and insert a mention!\n\n**Relevant Docs:**\n\n- [Custom Inline Content Types](/docs/features/custom-schemas/custom-inline-content)\n- [Creating Suggestion Menus](/docs/react/components/suggestion-menus)\n- [Editor Setup](/docs/getting-started/editor-setup)" }, { - projectSlug: "uppy-file-panel", - fullSlug: "ui-components/uppy-file-panel", - pathFromRoot: "examples/03-ui-components/11-uppy-file-panel", - config: { - playground: true, - docs: true, - author: "ezhil56x", - tags: ["Intermediate", "Files"], - dependencies: { + "projectSlug": "uppy-file-panel", + "fullSlug": "ui-components/uppy-file-panel", + "pathFromRoot": "examples/03-ui-components/11-uppy-file-panel", + "config": { + "playground": true, + "docs": true, + "author": "ezhil56x", + "tags": [ + "Intermediate", + "Files" + ], + "dependencies": { "@uppy/core": "^3.13.1", "@uppy/dashboard": "^3.9.1", "@uppy/drag-drop": "^3.1.1", @@ -594,50 +621,48 @@ export const examples = { "@uppy/status-bar": "^3.1.1", "@uppy/webcam": "^3.4.2", "@uppy/xhr-upload": "^3.4.0", - "react-icons": "^5.2.1", + "react-icons": "^5.2.1" } as any, - pro: true, + "pro": true }, - title: "Uppy File Panel", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", + "title": "Uppy File Panel", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" }, - readme: - 'This example allows users to upload files using [Uppy](https://uppy.io/) by replacing the default File Panel with an Uppy Dashboard.\n\nUppy is highly extensible and has an extensive ecosystem of plugins. For example, you can:\n\n- Record audio, screen or webcam\n- Import files from Box / Dropbox / Facebook / Google Drive / Google Photos / Instagram / OneDrive / Zoom\n- Select files from Unsplash\n- Show an image editor (crop, rotate, etc)\n\nIn this example, we\'ve enabled the Webcam, ScreenCapture and Image Editor plugins.\n\n**Try it out:** Click the "Add Image" button and you can either drop files or click "browse files" to upload them.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Image](/docs/foundations/schemas)', + "readme": "This example allows users to upload files using [Uppy](https://uppy.io/) by replacing the default File Panel with an Uppy Dashboard.\n\nUppy is highly extensible and has an extensive ecosystem of plugins. For example, you can:\n\n- Record audio, screen or webcam\n- Import files from Box / Dropbox / Facebook / Google Drive / Google Photos / Instagram / OneDrive / Zoom\n- Select files from Unsplash\n- Show an image editor (crop, rotate, etc)\n\nIn this example, we've enabled the Webcam, ScreenCapture and Image Editor plugins.\n\n**Try it out:** Click the \"Add Image\" button and you can either drop files or click \"browse files\" to upload them.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Image](/docs/foundations/schemas)" }, { - projectSlug: "static-formatting-toolbar", - fullSlug: "ui-components/static-formatting-toolbar", - pathFromRoot: "examples/03-ui-components/12-static-formatting-toolbar", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: [ + "projectSlug": "static-formatting-toolbar", + "fullSlug": "ui-components/static-formatting-toolbar", + "pathFromRoot": "examples/03-ui-components/12-static-formatting-toolbar", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ "Basic", "UI Components", "Formatting Toolbar", - "Appearance & Styling", - ], + "Appearance & Styling" + ] }, - title: "Static Formatting Toolbar", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", + "title": "Static Formatting Toolbar", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" }, - readme: - "This example shows how to make the formatting toolbar always visible and static\nabove the editor.\n\n**Relevant Docs:**\n\n- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar)\n- [Editor Setup](/docs/getting-started/editor-setup)", + "readme": "This example shows how to make the formatting toolbar always visible and static\nabove the editor.\n\n**Relevant Docs:**\n\n- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar)\n- [Editor Setup](/docs/getting-started/editor-setup)" }, { - projectSlug: "custom-ui", - fullSlug: "ui-components/custom-ui", - pathFromRoot: "examples/03-ui-components/13-custom-ui", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: [ + "projectSlug": "custom-ui", + "fullSlug": "ui-components/custom-ui", + "pathFromRoot": "examples/03-ui-components/13-custom-ui", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ "Advanced", "Inline Content", "UI Components", @@ -645,1124 +670,1204 @@ export const examples = { "Formatting Toolbar", "Suggestion Menus", "Slash Menu", - "Appearance & Styling", + "Appearance & Styling" ], - dependencies: { + "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", "@mui/icons-material": "^5.16.1", - "@mui/material": "^5.16.1", + "@mui/material": "^5.16.1" } as any, - pro: true, - }, - title: "UI With Third-Party Components", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", - }, - readme: - "In this example, we implement a basic editor interface using components from Material UI. We replace the Formatting Toolbar, Slash Menu, and Block Side Menu while disabling the other default elements. Additionally, the Formatting Toolbar is made static and always visible above the editor.\n\n**Relevant Docs:**\n\n- [Formatting Toolbar](/docs/react/components/formatting-toolbar)\n- [Manipulating Inline Content](/docs/reference/editor/manipulating-content)\n- [Slash Menu](/docs/react/components/suggestion-menus)\n- [Side Menu](/docs/react/components/side-menu)\n- [Editor Setup](/docs/getting-started/editor-setup)", - }, - { - projectSlug: "experimental-mobile-formatting-toolbar", - fullSlug: "ui-components/experimental-mobile-formatting-toolbar", - pathFromRoot: - "examples/03-ui-components/14-experimental-mobile-formatting-toolbar", - config: { - playground: true, - docs: true, - author: "areknawo", - tags: [ + "pro": true + }, + "title": "UI With Third-Party Components", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" + }, + "readme": "In this example, we implement a basic editor interface using components from Material UI. We replace the Formatting Toolbar, Slash Menu, and Block Side Menu while disabling the other default elements. Additionally, the Formatting Toolbar is made static and always visible above the editor.\n\n**Relevant Docs:**\n\n- [Formatting Toolbar](/docs/react/components/formatting-toolbar)\n- [Manipulating Inline Content](/docs/reference/editor/manipulating-content)\n- [Slash Menu](/docs/react/components/suggestion-menus)\n- [Side Menu](/docs/react/components/side-menu)\n- [Editor Setup](/docs/getting-started/editor-setup)" + }, + { + "projectSlug": "experimental-mobile-formatting-toolbar", + "fullSlug": "ui-components/experimental-mobile-formatting-toolbar", + "pathFromRoot": "examples/03-ui-components/14-experimental-mobile-formatting-toolbar", + "config": { + "playground": true, + "docs": true, + "author": "areknawo", + "tags": [ "Intermediate", "UI Components", "Formatting Toolbar", - "Appearance & Styling", - ], + "Appearance & Styling" + ] }, - title: "Experimental Mobile Formatting Toolbar", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", + "title": "Experimental Mobile Formatting Toolbar", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" }, - readme: - "This example shows how to use the experimental mobile formatting toolbar, which uses [Visual Viewport API](https://developer.mozilla.org/en-US/docs/Web/API/Visual_Viewport_API) to position the toolbar right above the virtual keyboard on mobile devices.\n\nController is currently marked **experimental** due to the flickering issue with positioning (caused by delays of the Visual Viewport API)\n\n**Relevant Docs:**\n\n- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar)\n- [Editor Setup](/docs/getting-started/editor-setup)", + "readme": "This example shows how to use the experimental mobile formatting toolbar, which uses [Visual Viewport API](https://developer.mozilla.org/en-US/docs/Web/API/Visual_Viewport_API) to position the toolbar right above the virtual keyboard on mobile devices.\n\nController is currently marked **experimental** due to the flickering issue with positioning (caused by delays of the Visual Viewport API)\n\n**Relevant Docs:**\n\n- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar)\n- [Editor Setup](/docs/getting-started/editor-setup)" }, { - projectSlug: "advanced-tables", - fullSlug: "ui-components/advanced-tables", - pathFromRoot: "examples/03-ui-components/15-advanced-tables", - config: { - playground: true, - docs: true, - author: "nperez0111", - tags: [ + "projectSlug": "advanced-tables", + "fullSlug": "ui-components/advanced-tables", + "pathFromRoot": "examples/03-ui-components/15-advanced-tables", + "config": { + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": [ "Intermediate", "UI Components", "Tables", - "Appearance & Styling", - ], + "Appearance & Styling" + ] }, - title: "Advanced Tables", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", + "title": "Advanced Tables", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" }, - readme: - "This example enables the following features in tables:\n\n- Split cells\n- Cell background color\n- Cell text color\n- Table row and column headers\n\n**Relevant Docs:**\n\n- [Tables](/docs/features/blocks/tables)\n- [Editor Setup](/docs/getting-started/editor-setup)", + "readme": "This example enables the following features in tables:\n\n- Split cells\n- Cell background color\n- Cell text color\n- Table row and column headers\n\n**Relevant Docs:**\n\n- [Tables](/docs/features/blocks/tables)\n- [Editor Setup](/docs/getting-started/editor-setup)" }, { - projectSlug: "link-toolbar-buttons", - fullSlug: "ui-components/link-toolbar-buttons", - pathFromRoot: "examples/03-ui-components/16-link-toolbar-buttons", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: [ + "projectSlug": "link-toolbar-buttons", + "fullSlug": "ui-components/link-toolbar-buttons", + "pathFromRoot": "examples/03-ui-components/16-link-toolbar-buttons", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ "Intermediate", "Inline Content", "UI Components", - "Link Toolbar", - ], + "Link Toolbar" + ] }, - title: "Adding Link Toolbar Buttons", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", + "title": "Adding Link Toolbar Buttons", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" }, - readme: - 'In this example, we add a button to the Link Toolbar which opens a browser alert.\n\n**Try it out:** Hover the link open the Link Toolbar, and click the new "Open Alert" button!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)', + "readme": "In this example, we add a button to the Link Toolbar which opens a browser alert.\n\n**Try it out:** Hover the link open the Link Toolbar, and click the new \"Open Alert\" button!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)" }, { - projectSlug: "advanced-tables-2", - fullSlug: "ui-components/advanced-tables-2", - pathFromRoot: "examples/03-ui-components/17-advanced-tables-2", - config: { - playground: true, - docs: true, - author: "must", - tags: [ + "projectSlug": "advanced-tables-2", + "fullSlug": "ui-components/advanced-tables-2", + "pathFromRoot": "examples/03-ui-components/17-advanced-tables-2", + "config": { + "playground": true, + "docs": true, + "author": "must", + "tags": [ "Intermediate", "UI Components", "Tables", + "Appearance & Styling" + ] + }, + "title": "Advanced Tables with Calculated Columns", + "group": { + "pathFromRoot": "examples/03-ui-components", + "slug": "ui-components" + }, + "readme": "This example demonstrates advanced table features including automatic calculations. It shows how to create a table with calculated columns that automatically update when values change.\n\n## Features\n\n- **Automatic Calculations**: Quantity × Price = Total for each row\n- **Grand Total**: Automatically calculated sum of all totals\n- **Real-time Updates**: Calculations update immediately when you change quantity or price values\n- **Split cells**: Merge and split table cells\n- **Cell background color**: Color individual cells\n- **Cell text color**: Change text color in cells\n- **Table row and column headers**: Use headers for better organization\n\n## How It Works\n\nThe example uses the `onChange` event listener to detect when table content changes. When a table is updated, it automatically:\n\n1. Extracts quantity and price values from each data row\n2. Calculates the total (quantity × price) for each row\n3. Updates the total column with the calculated values\n4. Calculates and updates the grand total\n\n## Code Highlights\n\n```tsx\n {\n const changes = getChanges();\n\n changes.forEach((change) => {\n if (change.type === \"update\" && change.block.type === \"table\") {\n const updatedRows = calculateTableTotals(change.block);\n if (updatedRows) {\n editor.updateBlock(change.block, {\n type: \"table\",\n content: {\n ...change.block.content,\n rows: updatedRows as any,\n } as any,\n });\n }\n }\n });\n }}\n>\n```\n\n**Relevant Docs:**\n\n- [Tables](/docs/features/blocks/tables)\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Events](/docs/reference/editor/events)" + } + ] + }, + "theming": { + "pathFromRoot": "examples/04-theming", + "slug": "theming", + "projects": [ + { + "projectSlug": "theming-dom-attributes", + "fullSlug": "theming/theming-dom-attributes", + "pathFromRoot": "examples/04-theming/01-theming-dom-attributes", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic", + "Blocks", + "Appearance & Styling" + ] + }, + "title": "Adding CSS Class to Blocks", + "group": { + "pathFromRoot": "examples/04-theming", + "slug": "theming" + }, + "readme": "In this example, we add a `hello-world-block` class to each block in the editor. We also create a CSS rule to add a border to all elements with that class.\n\n**Relevant Docs:**\n\n- [Adding DOM Attributes](/docs/react/styling-theming/adding-dom-attributes)" + }, + { + "projectSlug": "changing-font", + "fullSlug": "theming/changing-font", + "pathFromRoot": "examples/04-theming/02-changing-font", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic", + "Appearance & Styling" + ] + }, + "title": "Changing Editor Font", + "group": { + "pathFromRoot": "examples/04-theming", + "slug": "theming" + }, + "readme": "In this example, we override some of the default editor CSS to change font within the editor.\n\n**Relevant Docs:**\n\n- [Overriding CSS](/docs/react/styling-theming/overriding-css)" + }, + { + "projectSlug": "theming-css", + "fullSlug": "theming/theming-css", + "pathFromRoot": "examples/04-theming/03-theming-css", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic", "Appearance & Styling", - ], + "UI Components" + ] }, - title: "Advanced Tables with Calculated Columns", - group: { - pathFromRoot: "examples/03-ui-components", - slug: "ui-components", + "title": "Overriding CSS Styles", + "group": { + "pathFromRoot": "examples/04-theming", + "slug": "theming" }, - readme: - 'This example demonstrates advanced table features including automatic calculations. It shows how to create a table with calculated columns that automatically update when values change.\n\n## Features\n\n- **Automatic Calculations**: Quantity × Price = Total for each row\n- **Grand Total**: Automatically calculated sum of all totals\n- **Real-time Updates**: Calculations update immediately when you change quantity or price values\n- **Split cells**: Merge and split table cells\n- **Cell background color**: Color individual cells\n- **Cell text color**: Change text color in cells\n- **Table row and column headers**: Use headers for better organization\n\n## How It Works\n\nThe example uses the `onChange` event listener to detect when table content changes. When a table is updated, it automatically:\n\n1. Extracts quantity and price values from each data row\n2. Calculates the total (quantity × price) for each row\n3. Updates the total column with the calculated values\n4. Calculates and updates the grand total\n\n## Code Highlights\n\n```tsx\n {\n const changes = getChanges();\n\n changes.forEach((change) => {\n if (change.type === "update" && change.block.type === "table") {\n const updatedRows = calculateTableTotals(change.block);\n if (updatedRows) {\n editor.updateBlock(change.block, {\n type: "table",\n content: {\n ...change.block.content,\n rows: updatedRows as any,\n } as any,\n });\n }\n }\n });\n }}\n>\n```\n\n**Relevant Docs:**\n\n- [Tables](/docs/features/blocks/tables)\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Events](/docs/reference/editor/events)', + "readme": "In this example, we override some of the default editor CSS to make the editor text and hovered Slash Menu items blue.\n\n**Relevant Docs:**\n\n- [Overriding CSS](/docs/react/styling-theming/overriding-css)" }, - ], - }, - theming: { - pathFromRoot: "examples/04-theming", - slug: "theming", - projects: [ - { - projectSlug: "theming-dom-attributes", - fullSlug: "theming/theming-dom-attributes", - pathFromRoot: "examples/04-theming/01-theming-dom-attributes", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Basic", "Blocks", "Appearance & Styling"], - }, - title: "Adding CSS Class to Blocks", - group: { - pathFromRoot: "examples/04-theming", - slug: "theming", - }, - readme: - "In this example, we add a `hello-world-block` class to each block in the editor. We also create a CSS rule to add a border to all elements with that class.\n\n**Relevant Docs:**\n\n- [Adding DOM Attributes](/docs/react/styling-theming/adding-dom-attributes)", - }, - { - projectSlug: "changing-font", - fullSlug: "theming/changing-font", - pathFromRoot: "examples/04-theming/02-changing-font", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Basic", "Appearance & Styling"], - }, - title: "Changing Editor Font", - group: { - pathFromRoot: "examples/04-theming", - slug: "theming", - }, - readme: - "In this example, we override some of the default editor CSS to change font within the editor.\n\n**Relevant Docs:**\n\n- [Overriding CSS](/docs/react/styling-theming/overriding-css)", - }, - { - projectSlug: "theming-css", - fullSlug: "theming/theming-css", - pathFromRoot: "examples/04-theming/03-theming-css", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Basic", "Appearance & Styling", "UI Components"], - }, - title: "Overriding CSS Styles", - group: { - pathFromRoot: "examples/04-theming", - slug: "theming", - }, - readme: - "In this example, we override some of the default editor CSS to make the editor text and hovered Slash Menu items blue.\n\n**Relevant Docs:**\n\n- [Overriding CSS](/docs/react/styling-theming/overriding-css)", - }, - { - projectSlug: "theming-css-variables", - fullSlug: "theming/theming-css-variables", - pathFromRoot: "examples/04-theming/04-theming-css-variables", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Basic", "Appearance & Styling", "UI Components"], - }, - title: "Overriding Theme CSS Variables", - group: { - pathFromRoot: "examples/04-theming", - slug: "theming", - }, - readme: - "In this example, we override the editor's default theme CSS variables to create a red theme for both light and dark modes.\n\n**Relevant Docs:**\n\n- [Theme CSS Variables](/docs/react/styling-theming/themes#css-variables)", - }, - { - projectSlug: "theming-css-variables-code", - fullSlug: "theming/theming-css-variables-code", - pathFromRoot: "examples/04-theming/05-theming-css-variables-code", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Basic", "Appearance & Styling", "UI Components"], - }, - title: "Changing Themes Through Code", - group: { - pathFromRoot: "examples/04-theming", - slug: "theming", - }, - readme: - "In this example, we use the `BlockNoteView` component's `theme` props to create a red theme for both light and dark modes.\n\n**Relevant Docs:**\n\n- [Changing CSS Variables Through Code](/docs/react/styling-theming/themes#programmatic-configuration)", - }, - { - projectSlug: "code-block", - fullSlug: "theming/code-block", - pathFromRoot: "examples/04-theming/06-code-block", - config: { - playground: true, - docs: true, - author: "nperez0111", - tags: ["Basic"], - dependencies: { - "@blocknote/code-block": "latest", - } as any, + { + "projectSlug": "theming-css-variables", + "fullSlug": "theming/theming-css-variables", + "pathFromRoot": "examples/04-theming/04-theming-css-variables", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic", + "Appearance & Styling", + "UI Components" + ] }, - title: "Code Block Syntax Highlighting", - group: { - pathFromRoot: "examples/04-theming", - slug: "theming", + "title": "Overriding Theme CSS Variables", + "group": { + "pathFromRoot": "examples/04-theming", + "slug": "theming" }, - readme: - "To enable code block syntax highlighting, you can extend the editor's default schema with a new `codeBlock`, which you can pass options into when creating. By passing the default options from `@blocknote/code-block`, you can enable syntax highlighting. This is excluded by default to reduce bundle size.\n\n**Relevant Docs:**\n\n- [Code Block Syntax Highlighting](/docs/features/blocks/code-blocks)\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Custom Schema](/docs/features/custom-schemas)", + "readme": "In this example, we override the editor's default theme CSS variables to create a red theme for both light and dark modes.\n\n**Relevant Docs:**\n\n- [Theme CSS Variables](/docs/react/styling-theming/themes#css-variables)" }, { - projectSlug: "custom-code-block", - fullSlug: "theming/custom-code-block", - pathFromRoot: "examples/04-theming/07-custom-code-block", - config: { - playground: true, - docs: true, - author: "nperez0111", - tags: ["Basic"], - dependencies: { + "projectSlug": "theming-css-variables-code", + "fullSlug": "theming/theming-css-variables-code", + "pathFromRoot": "examples/04-theming/05-theming-css-variables-code", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic", + "Appearance & Styling", + "UI Components" + ] + }, + "title": "Changing Themes Through Code", + "group": { + "pathFromRoot": "examples/04-theming", + "slug": "theming" + }, + "readme": "In this example, we use the `BlockNoteView` component's `theme` props to create a red theme for both light and dark modes.\n\n**Relevant Docs:**\n\n- [Changing CSS Variables Through Code](/docs/react/styling-theming/themes#programmatic-configuration)" + }, + { + "projectSlug": "code-block", + "fullSlug": "theming/code-block", + "pathFromRoot": "examples/04-theming/06-code-block", + "config": { + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": [ + "Basic" + ], + "dependencies": { + "@blocknote/code-block": "latest" + } as any + }, + "title": "Code Block Syntax Highlighting", + "group": { + "pathFromRoot": "examples/04-theming", + "slug": "theming" + }, + "readme": "To enable code block syntax highlighting, you can extend the editor's default schema with a new `codeBlock`, which you can pass options into when creating. By passing the default options from `@blocknote/code-block`, you can enable syntax highlighting. This is excluded by default to reduce bundle size.\n\n**Relevant Docs:**\n\n- [Code Block Syntax Highlighting](/docs/features/blocks/code-blocks)\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Custom Schema](/docs/features/custom-schemas)" + }, + { + "projectSlug": "custom-code-block", + "fullSlug": "theming/custom-code-block", + "pathFromRoot": "examples/04-theming/07-custom-code-block", + "config": { + "playground": true, + "docs": true, + "author": "nperez0111", + "tags": [ + "Basic" + ], + "dependencies": { "@blocknote/code-block": "latest", "@shikijs/types": "^3.2.1", "@shikijs/core": "^3.2.1", "@shikijs/engine-javascript": "^3.2.1", "@shikijs/langs-precompiled": "^3.2.1", - "@shikijs/themes": "^3.2.1", - } as any, + "@shikijs/themes": "^3.2.1" + } as any }, - title: "Custom Code Block Theme & Language", - group: { - pathFromRoot: "examples/04-theming", - slug: "theming", + "title": "Custom Code Block Theme & Language", + "group": { + "pathFromRoot": "examples/04-theming", + "slug": "theming" }, - readme: - "To configure a code block highlighting theme and language, you can extend the editor's default schema with a new `codeBlock`, which you can pass options into when creating. You can then use a shiki highlighter to add custom syntax highlighting.\n\nFirst use the [shiki-codegen](https://shiki.style/packages/codegen) CLI to create a `shiki.bundle.ts` file. You can then pass this file into the `codeBlock` options when creating it.\n\n**Relevant Docs:**\n\n- [Code Blocks](/docs/features/blocks/code-blocks)\n- [shiki-codegen](https://shiki.style/packages/codegen)\n- [Custom Schema](/docs/features/custom-schemas)", - }, - ], + "readme": "To configure a code block highlighting theme and language, you can extend the editor's default schema with a new `codeBlock`, which you can pass options into when creating. You can then use a shiki highlighter to add custom syntax highlighting.\n\nFirst use the [shiki-codegen](https://shiki.style/packages/codegen) CLI to create a `shiki.bundle.ts` file. You can then pass this file into the `codeBlock` options when creating it.\n\n**Relevant Docs:**\n\n- [Code Blocks](/docs/features/blocks/code-blocks)\n- [shiki-codegen](https://shiki.style/packages/codegen)\n- [Custom Schema](/docs/features/custom-schemas)" + } + ] }, - interoperability: { - pathFromRoot: "examples/05-interoperability", - slug: "interoperability", - projects: [ - { - projectSlug: "converting-blocks-to-html", - fullSlug: "interoperability/converting-blocks-to-html", - pathFromRoot: - "examples/05-interoperability/01-converting-blocks-to-html", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Basic", "Blocks", "Import/Export"], - }, - title: "Converting Blocks to HTML", - group: { - pathFromRoot: "examples/05-interoperability", - slug: "interoperability", - }, - readme: - "This example exports the current document (all blocks) as HTML and displays it below the editor.\n\n**Try it out:** Edit the document to see the HTML representation!\n\n**Relevant Docs:**\n\n- [Converting Blocks to HTML](/docs/features/export/html)", - }, - { - projectSlug: "converting-blocks-from-html", - fullSlug: "interoperability/converting-blocks-from-html", - pathFromRoot: - "examples/05-interoperability/02-converting-blocks-from-html", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Basic", "Blocks", "Import/Export"], - }, - title: "Parsing HTML to Blocks", - group: { - pathFromRoot: "examples/05-interoperability", - slug: "interoperability", - }, - readme: - "This example shows how you can convert HTML content to a BlockNote document.\n\nNote that the editor itself is locked for editing by setting `editable` to `false`.\n\n**Try it out:** Edit the HTML in the textarea to see the BlockNote document update!\n\n**Relevant Docs:**\n\n- [Parsing HTML to Blocks](/docs/features/import/html)", - }, - { - projectSlug: "converting-blocks-to-md", - fullSlug: "interoperability/converting-blocks-to-md", - pathFromRoot: "examples/05-interoperability/03-converting-blocks-to-md", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["Basic", "Blocks", "Import/Export"], - }, - title: "Converting Blocks to Markdown", - group: { - pathFromRoot: "examples/05-interoperability", - slug: "interoperability", - }, - readme: - "This example exports the current document (all blocks) as Markdown and displays it below the editor.\n\n**Try it out:** Edit the document to see the Markdown representation!\n\n**Relevant Docs:**\n\n- [Converting Blocks to Markdown](/docs/features/export/markdown)", - }, - { - projectSlug: "converting-blocks-from-md", - fullSlug: "interoperability/converting-blocks-from-md", - pathFromRoot: - "examples/05-interoperability/04-converting-blocks-from-md", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["Basic", "Blocks", "Import/Export"], - }, - title: "Parsing Markdown to Blocks", - group: { - pathFromRoot: "examples/05-interoperability", - slug: "interoperability", - }, - readme: - "This example shows how you can convert HTML content to a BlockNote document.\n\nNote that the editor itself is locked for editing by setting `editable` to `false`.\n\n**Try it out:** Edit the Markdown in the textarea to see the BlockNote document update!\n\n**Relevant Docs:**\n\n- [Parsing Markdown to Blocks](/docs/features/import/markdown)", - }, - { - projectSlug: "converting-blocks-to-pdf", - fullSlug: "interoperability/converting-blocks-to-pdf", - pathFromRoot: - "examples/05-interoperability/05-converting-blocks-to-pdf", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["Interoperability"], - dependencies: { + "interoperability": { + "pathFromRoot": "examples/05-interoperability", + "slug": "interoperability", + "projects": [ + { + "projectSlug": "converting-blocks-to-html", + "fullSlug": "interoperability/converting-blocks-to-html", + "pathFromRoot": "examples/05-interoperability/01-converting-blocks-to-html", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic", + "Blocks", + "Import/Export" + ] + }, + "title": "Converting Blocks to HTML", + "group": { + "pathFromRoot": "examples/05-interoperability", + "slug": "interoperability" + }, + "readme": "This example exports the current document (all blocks) as HTML and displays it below the editor.\n\n**Try it out:** Edit the document to see the HTML representation!\n\n**Relevant Docs:**\n\n- [Converting Blocks to HTML](/docs/features/export/html)" + }, + { + "projectSlug": "converting-blocks-from-html", + "fullSlug": "interoperability/converting-blocks-from-html", + "pathFromRoot": "examples/05-interoperability/02-converting-blocks-from-html", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic", + "Blocks", + "Import/Export" + ] + }, + "title": "Parsing HTML to Blocks", + "group": { + "pathFromRoot": "examples/05-interoperability", + "slug": "interoperability" + }, + "readme": "This example shows how you can convert HTML content to a BlockNote document.\n\nNote that the editor itself is locked for editing by setting `editable` to `false`.\n\n**Try it out:** Edit the HTML in the textarea to see the BlockNote document update!\n\n**Relevant Docs:**\n\n- [Parsing HTML to Blocks](/docs/features/import/html)" + }, + { + "projectSlug": "converting-blocks-to-md", + "fullSlug": "interoperability/converting-blocks-to-md", + "pathFromRoot": "examples/05-interoperability/03-converting-blocks-to-md", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Basic", + "Blocks", + "Import/Export" + ] + }, + "title": "Converting Blocks to Markdown", + "group": { + "pathFromRoot": "examples/05-interoperability", + "slug": "interoperability" + }, + "readme": "This example exports the current document (all blocks) as Markdown and displays it below the editor.\n\n**Try it out:** Edit the document to see the Markdown representation!\n\n**Relevant Docs:**\n\n- [Converting Blocks to Markdown](/docs/features/export/markdown)" + }, + { + "projectSlug": "converting-blocks-from-md", + "fullSlug": "interoperability/converting-blocks-from-md", + "pathFromRoot": "examples/05-interoperability/04-converting-blocks-from-md", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Basic", + "Blocks", + "Import/Export" + ] + }, + "title": "Parsing Markdown to Blocks", + "group": { + "pathFromRoot": "examples/05-interoperability", + "slug": "interoperability" + }, + "readme": "This example shows how you can convert HTML content to a BlockNote document.\n\nNote that the editor itself is locked for editing by setting `editable` to `false`.\n\n**Try it out:** Edit the Markdown in the textarea to see the BlockNote document update!\n\n**Relevant Docs:**\n\n- [Parsing Markdown to Blocks](/docs/features/import/markdown)" + }, + { + "projectSlug": "converting-blocks-to-pdf", + "fullSlug": "interoperability/converting-blocks-to-pdf", + "pathFromRoot": "examples/05-interoperability/05-converting-blocks-to-pdf", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Interoperability" + ], + "dependencies": { "@blocknote/xl-pdf-exporter": "latest", "@blocknote/xl-multi-column": "latest", - "@react-pdf/renderer": "^4.3.0", + "@react-pdf/renderer": "^4.3.0" } as any, - pro: true, - }, - title: "Exporting documents to PDF", - group: { - pathFromRoot: "examples/05-interoperability", - slug: "interoperability", - }, - readme: - 'This example exports the current document (all blocks) as an PDF file and downloads it to your computer.\n\n**Try it out:** Edit the document and click "Download .pdf" at the top to download the PDF file.', - }, - { - projectSlug: "converting-blocks-to-docx", - fullSlug: "interoperability/converting-blocks-to-docx", - pathFromRoot: - "examples/05-interoperability/06-converting-blocks-to-docx", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: [""], - dependencies: { + "pro": true + }, + "title": "Exporting documents to PDF", + "group": { + "pathFromRoot": "examples/05-interoperability", + "slug": "interoperability" + }, + "readme": "This example exports the current document (all blocks) as an PDF file and downloads it to your computer.\n\n**Try it out:** Edit the document and click \"Download .pdf\" at the top to download the PDF file." + }, + { + "projectSlug": "converting-blocks-to-docx", + "fullSlug": "interoperability/converting-blocks-to-docx", + "pathFromRoot": "examples/05-interoperability/06-converting-blocks-to-docx", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "" + ], + "dependencies": { "@blocknote/xl-docx-exporter": "latest", "@blocknote/xl-multi-column": "latest", - docx: "^9.5.1", + "docx": "^9.5.1" } as any, - pro: true, - }, - title: "Exporting documents to DOCX (Office Open XML)", - group: { - pathFromRoot: "examples/05-interoperability", - slug: "interoperability", - }, - readme: - 'This example exports the current document (all blocks) as an Microsoft Word Document (DOCX) file and downloads it to your computer.\n\n**Try it out:** Edit the document and click "Download .docx" at the top to download the DOCX file.', - }, - { - projectSlug: "converting-blocks-to-odt", - fullSlug: "interoperability/converting-blocks-to-odt", - pathFromRoot: - "examples/05-interoperability/07-converting-blocks-to-odt", - config: { - playground: true, - docs: true, - author: "areknawo", - tags: [""], - dependencies: { + "pro": true + }, + "title": "Exporting documents to DOCX (Office Open XML)", + "group": { + "pathFromRoot": "examples/05-interoperability", + "slug": "interoperability" + }, + "readme": "This example exports the current document (all blocks) as an Microsoft Word Document (DOCX) file and downloads it to your computer.\n\n**Try it out:** Edit the document and click \"Download .docx\" at the top to download the DOCX file." + }, + { + "projectSlug": "converting-blocks-to-odt", + "fullSlug": "interoperability/converting-blocks-to-odt", + "pathFromRoot": "examples/05-interoperability/07-converting-blocks-to-odt", + "config": { + "playground": true, + "docs": true, + "author": "areknawo", + "tags": [ + "" + ], + "dependencies": { "@blocknote/xl-odt-exporter": "latest", - "@blocknote/xl-multi-column": "latest", + "@blocknote/xl-multi-column": "latest" } as any, - pro: true, - }, - title: "Exporting documents to ODT (Open Document Text)", - group: { - pathFromRoot: "examples/05-interoperability", - slug: "interoperability", - }, - readme: - 'This example exports the current document (all blocks) as an Open Document Text (ODT) file and downloads it to your computer.\n\n**Try it out:** Edit the document and click "Download .odt" at the top to download the ODT file.', - }, - { - projectSlug: "converting-blocks-to-react-email", - fullSlug: "interoperability/converting-blocks-to-react-email", - pathFromRoot: - "examples/05-interoperability/08-converting-blocks-to-react-email", - config: { - playground: true, - docs: true, - author: "jmarbutt", - tags: [""], - dependencies: { + "pro": true + }, + "title": "Exporting documents to ODT (Open Document Text)", + "group": { + "pathFromRoot": "examples/05-interoperability", + "slug": "interoperability" + }, + "readme": "This example exports the current document (all blocks) as an Open Document Text (ODT) file and downloads it to your computer.\n\n**Try it out:** Edit the document and click \"Download .odt\" at the top to download the ODT file." + }, + { + "projectSlug": "converting-blocks-to-react-email", + "fullSlug": "interoperability/converting-blocks-to-react-email", + "pathFromRoot": "examples/05-interoperability/08-converting-blocks-to-react-email", + "config": { + "playground": true, + "docs": true, + "author": "jmarbutt", + "tags": [ + "" + ], + "dependencies": { "@blocknote/xl-email-exporter": "latest", - "@react-email/render": "^1.1.2", + "@react-email/render": "^1.1.2" } as any, - pro: true, + "pro": true }, - title: "Exporting documents to Email (HTML)", - group: { - pathFromRoot: "examples/05-interoperability", - slug: "interoperability", + "title": "Exporting documents to Email (HTML)", + "group": { + "pathFromRoot": "examples/05-interoperability", + "slug": "interoperability" }, - readme: - 'This example exports the current document (all blocks) as an HTML file for use in emails, and downloads it to your computer.\n\n**Try it out:** Edit the document and click "Download email .html" at the top to download the HTML file.', - }, - ], + "readme": "This example exports the current document (all blocks) as an HTML file for use in emails, and downloads it to your computer.\n\n**Try it out:** Edit the document and click \"Download email .html\" at the top to download the HTML file." + } + ] }, "custom-schema": { - pathFromRoot: "examples/06-custom-schema", - slug: "custom-schema", - projects: [ - { - projectSlug: "alert-block", - fullSlug: "custom-schema/alert-block", - pathFromRoot: "examples/06-custom-schema/01-alert-block", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: [ + "pathFromRoot": "examples/06-custom-schema", + "slug": "custom-schema", + "projects": [ + { + "projectSlug": "alert-block", + "fullSlug": "custom-schema/alert-block", + "pathFromRoot": "examples/06-custom-schema/01-alert-block", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ "Intermediate", "Blocks", "Custom Schemas", "Suggestion Menus", - "Slash Menu", + "Slash Menu" ], - dependencies: { + "dependencies": { "@mantine/core": "^8.3.4", - "react-icons": "^5.2.1", - } as any, + "react-icons": "^5.2.1" + } as any }, - title: "Alert Block", - group: { - pathFromRoot: "examples/06-custom-schema", - slug: "custom-schema", + "title": "Alert Block", + "group": { + "pathFromRoot": "examples/06-custom-schema", + "slug": "custom-schema" }, - readme: - 'In this example, we create a custom `Alert` block which is used to emphasize text.\n\n**Try it out:** Click the "!" icon to change the alert type!\n\n**Relevant Docs:**\n\n- [Custom Blocks](/docs/features/custom-schemas/custom-blocks)\n- [Editor Setup](/docs/getting-started/editor-setup)', + "readme": "In this example, we create a custom `Alert` block which is used to emphasize text.\n\n**Try it out:** Click the \"!\" icon to change the alert type!\n\n**Relevant Docs:**\n\n- [Custom Blocks](/docs/features/custom-schemas/custom-blocks)\n- [Editor Setup](/docs/getting-started/editor-setup)" }, { - projectSlug: "suggestion-menus-mentions", - fullSlug: "custom-schema/suggestion-menus-mentions", - pathFromRoot: "examples/06-custom-schema/02-suggestion-menus-mentions", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: [ + "projectSlug": "suggestion-menus-mentions", + "fullSlug": "custom-schema/suggestion-menus-mentions", + "pathFromRoot": "examples/06-custom-schema/02-suggestion-menus-mentions", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ "Intermediate", "Inline Content", "Custom Schemas", - "Suggestion Menus", - ], + "Suggestion Menus" + ] }, - title: "Mentions Menu", - group: { - pathFromRoot: "examples/06-custom-schema", - slug: "custom-schema", + "title": "Mentions Menu", + "group": { + "pathFromRoot": "examples/06-custom-schema", + "slug": "custom-schema" }, - readme: - 'In this example, we create a custom `Mention` inline content type which is used to tag people. In addition, we create a new Suggestion Menu for mentions which opens with the "@" character.\n\n**Try it out:** Press the "@" key to open the mentions menu and insert a mention!\n\n**Relevant Docs:**\n\n- [Custom Inline Content Types](/docs/features/custom-schemas/custom-inline-content)\n- [Creating Suggestion Menus](/docs/react/components/suggestion-menus#creating-additional-suggestion-menus)\n- [Editor Setup](/docs/getting-started/editor-setup)', + "readme": "In this example, we create a custom `Mention` inline content type which is used to tag people. In addition, we create a new Suggestion Menu for mentions which opens with the \"@\" character.\n\n**Try it out:** Press the \"@\" key to open the mentions menu and insert a mention!\n\n**Relevant Docs:**\n\n- [Custom Inline Content Types](/docs/features/custom-schemas/custom-inline-content)\n- [Creating Suggestion Menus](/docs/react/components/suggestion-menus#creating-additional-suggestion-menus)\n- [Editor Setup](/docs/getting-started/editor-setup)" }, { - projectSlug: "font-style", - fullSlug: "custom-schema/font-style", - pathFromRoot: "examples/06-custom-schema/03-font-style", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: [ + "projectSlug": "font-style", + "fullSlug": "custom-schema/font-style", + "pathFromRoot": "examples/06-custom-schema/03-font-style", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ "Intermediate", "Inline Content", "Custom Schemas", - "Formatting Toolbar", + "Formatting Toolbar" ], - dependencies: { - "react-icons": "^5.2.1", - } as any, - }, - title: "Font Style", - group: { - pathFromRoot: "examples/06-custom-schema", - slug: "custom-schema", - }, - readme: - "In this example, we create a custom `Font` style which is used to set the `fontFamily` style. In addition, we create a Formatting Toolbar button which sets the `Font` style on the selected text.\n\n**Try it out:** Highlight some text to open the Formatting Toolbar and set the `Font` style!\n\n**Relevant Docs:**\n\n- [Custom Styles](/docs/features/custom-schemas/custom-styles)\n- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar)\n- [Editor Setup](/docs/getting-started/editor-setup)", - }, - { - projectSlug: "pdf-file-block", - fullSlug: "custom-schema/pdf-file-block", - pathFromRoot: "examples/06-custom-schema/04-pdf-file-block", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: [ + "dependencies": { + "react-icons": "^5.2.1" + } as any + }, + "title": "Font Style", + "group": { + "pathFromRoot": "examples/06-custom-schema", + "slug": "custom-schema" + }, + "readme": "In this example, we create a custom `Font` style which is used to set the `fontFamily` style. In addition, we create a Formatting Toolbar button which sets the `Font` style on the selected text.\n\n**Try it out:** Highlight some text to open the Formatting Toolbar and set the `Font` style!\n\n**Relevant Docs:**\n\n- [Custom Styles](/docs/features/custom-schemas/custom-styles)\n- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar)\n- [Editor Setup](/docs/getting-started/editor-setup)" + }, + { + "projectSlug": "pdf-file-block", + "fullSlug": "custom-schema/pdf-file-block", + "pathFromRoot": "examples/06-custom-schema/04-pdf-file-block", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ "Intermediate", "Blocks", "Custom Schemas", "Suggestion Menus", - "Slash Menu", + "Slash Menu" ], - dependencies: { + "dependencies": { "@mantine/core": "^8.3.4", - "react-icons": "^5.2.1", + "react-icons": "^5.2.1" } as any, - pro: true, + "pro": true }, - title: "PDF Block", - group: { - pathFromRoot: "examples/06-custom-schema", - slug: "custom-schema", + "title": "PDF Block", + "group": { + "pathFromRoot": "examples/06-custom-schema", + "slug": "custom-schema" }, - readme: - 'In this example, we create a custom `PDF` block which extends the built-in `File` block. In addition, we create a Slash Menu item which inserts a `PDF` block.\n\n**Try it out:** Press the "/" key to open the Slash Menu and insert an `PDF` block!\n\n**Relevant Docs:**\n\n- [Custom Blocks](/docs/features/custom-schemas/custom-blocks)\n- [Changing Slash Menu Items](/docs/react/components/suggestion-menus)\n- [Editor Setup](/docs/getting-started/editor-setup)', + "readme": "In this example, we create a custom `PDF` block which extends the built-in `File` block. In addition, we create a Slash Menu item which inserts a `PDF` block.\n\n**Try it out:** Press the \"/\" key to open the Slash Menu and insert an `PDF` block!\n\n**Relevant Docs:**\n\n- [Custom Blocks](/docs/features/custom-schemas/custom-blocks)\n- [Changing Slash Menu Items](/docs/react/components/suggestion-menus)\n- [Editor Setup](/docs/getting-started/editor-setup)" }, { - projectSlug: "alert-block-full-ux", - fullSlug: "custom-schema/alert-block-full-ux", - pathFromRoot: "examples/06-custom-schema/05-alert-block-full-ux", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: [ + "projectSlug": "alert-block-full-ux", + "fullSlug": "custom-schema/alert-block-full-ux", + "pathFromRoot": "examples/06-custom-schema/05-alert-block-full-ux", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ "Intermediate", "Blocks", "Custom Schemas", "Formatting Toolbar", "Suggestion Menus", - "Slash Menu", + "Slash Menu" ], - dependencies: { + "dependencies": { "@mantine/core": "^8.3.4", - "react-icons": "^5.2.1", - } as any, - }, - title: "Alert Block with Full UX", - group: { - pathFromRoot: "examples/06-custom-schema", - slug: "custom-schema", - }, - readme: - 'In this example, we create a custom `Alert` block which is used to emphasize text, same as in the [minimal `Alert` block example](/examples/custom-schema/alert-block). However, in this example, we also add a command to insert the block via the Slash Menu, and an entry in the Formatting Toolbar\'s Block Type Select to change the current block to an `Alert`.\n\n**Try it out:** Press the "/" key to open the Slash Menu and insert an `Alert` block! Or highlight text in a paragraph, then change the block type to an `Alert` using the Block Type Select in the Formatting Toolbar!\n\n**Relevant Docs:**\n\n- [Minimal Alert Block Example](/examples/custom-schema/alert-block)\n- [Changing Slash Menu Items](/docs/react/components/suggestion-menus)\n- [Changing Block Type Select Items](/docs/react/components/formatting-toolbar)\n- [Custom Blocks](/docs/features/custom-schemas/custom-blocks)\n- [Editor Setup](/docs/getting-started/editor-setup)', - }, - { - projectSlug: "toggleable-blocks", - fullSlug: "custom-schema/toggleable-blocks", - pathFromRoot: "examples/06-custom-schema/06-toggleable-blocks", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Basic"], - }, - title: "Toggleable Custom Blocks", - group: { - pathFromRoot: "examples/06-custom-schema", - slug: "custom-schema", - }, - readme: - "This example shows how to create custom blocks with a toggle button to show/hide their children, like with the default toggle heading and list item blocks. This is done using the use the `ToggleWrapper` component from `@blocknote/react`.\n\n**Relevant Docs:**\n\n- [Custom Blocks](/docs/features/custom-schemas/custom-blocks)\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Default Schema](/docs/features/blocks)", - }, - { - projectSlug: "configuring-blocks", - fullSlug: "custom-schema/configuring-blocks", - pathFromRoot: "examples/06-custom-schema/07-configuring-blocks", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Basic"], - }, - title: "Configuring Default Blocks", - group: { - pathFromRoot: "examples/06-custom-schema", - slug: "custom-schema", - }, - readme: - "This example shows how you can configure the editor's default blocks. Specifically, heading blocks are made to only support levels 1-3, and cannot be toggleable.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Default Schema](/docs/foundations/schemas)\n- [Custom Schemas](/docs/features/custom-schemas)", - }, - { - projectSlug: "draggable-inline-content", - fullSlug: "custom-schema/draggable-inline-content", - pathFromRoot: "examples/06-custom-schema/draggable-inline-content", - config: { - playground: true, - docs: false, - author: "hectorzhuang", - tags: [], - }, - title: "Draggable Inline Content", - group: { - pathFromRoot: "examples/06-custom-schema", - slug: "custom-schema", - }, - readme: "", - }, - { - projectSlug: "react-custom-blocks", - fullSlug: "custom-schema/react-custom-blocks", - pathFromRoot: "examples/06-custom-schema/react-custom-blocks", - config: { - playground: true, - docs: false, - author: "matthewlipski", - tags: [], - }, - title: "Custom Blocks - React API", - group: { - pathFromRoot: "examples/06-custom-schema", - slug: "custom-schema", - }, - readme: "", - }, - { - projectSlug: "react-custom-inline-content", - fullSlug: "custom-schema/react-custom-inline-content", - pathFromRoot: "examples/06-custom-schema/react-custom-inline-content", - config: { - playground: true, - docs: false, - author: "matthewlipski", - tags: [], - }, - title: "Custom Inline Content - React API", - group: { - pathFromRoot: "examples/06-custom-schema", - slug: "custom-schema", - }, - readme: "", - }, - { - projectSlug: "react-custom-styles", - fullSlug: "custom-schema/react-custom-styles", - pathFromRoot: "examples/06-custom-schema/react-custom-styles", - config: { - playground: true, - docs: false, - author: "matthewlipski", - tags: [], - }, - title: "Custom Styles - React API", - group: { - pathFromRoot: "examples/06-custom-schema", - slug: "custom-schema", - }, - readme: "", - }, - ], + "react-icons": "^5.2.1" + } as any + }, + "title": "Alert Block with Full UX", + "group": { + "pathFromRoot": "examples/06-custom-schema", + "slug": "custom-schema" + }, + "readme": "In this example, we create a custom `Alert` block which is used to emphasize text, same as in the [minimal `Alert` block example](/examples/custom-schema/alert-block). However, in this example, we also add a command to insert the block via the Slash Menu, and an entry in the Formatting Toolbar's Block Type Select to change the current block to an `Alert`.\n\n**Try it out:** Press the \"/\" key to open the Slash Menu and insert an `Alert` block! Or highlight text in a paragraph, then change the block type to an `Alert` using the Block Type Select in the Formatting Toolbar!\n\n**Relevant Docs:**\n\n- [Minimal Alert Block Example](/examples/custom-schema/alert-block)\n- [Changing Slash Menu Items](/docs/react/components/suggestion-menus)\n- [Changing Block Type Select Items](/docs/react/components/formatting-toolbar)\n- [Custom Blocks](/docs/features/custom-schemas/custom-blocks)\n- [Editor Setup](/docs/getting-started/editor-setup)" + }, + { + "projectSlug": "toggleable-blocks", + "fullSlug": "custom-schema/toggleable-blocks", + "pathFromRoot": "examples/06-custom-schema/06-toggleable-blocks", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic" + ] + }, + "title": "Toggleable Custom Blocks", + "group": { + "pathFromRoot": "examples/06-custom-schema", + "slug": "custom-schema" + }, + "readme": "This example shows how to create custom blocks with a toggle button to show/hide their children, like with the default toggle heading and list item blocks. This is done using the use the `ToggleWrapper` component from `@blocknote/react`.\n\n**Relevant Docs:**\n\n- [Custom Blocks](/docs/features/custom-schemas/custom-blocks)\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Default Schema](/docs/features/blocks)" + }, + { + "projectSlug": "configuring-blocks", + "fullSlug": "custom-schema/configuring-blocks", + "pathFromRoot": "examples/06-custom-schema/07-configuring-blocks", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Basic" + ] + }, + "title": "Configuring Default Blocks", + "group": { + "pathFromRoot": "examples/06-custom-schema", + "slug": "custom-schema" + }, + "readme": "This example shows how you can configure the editor's default blocks. Specifically, heading blocks are made to only support levels 1-3, and cannot be toggleable.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Default Schema](/docs/foundations/schemas)\n- [Custom Schemas](/docs/features/custom-schemas)" + }, + { + "projectSlug": "draggable-inline-content", + "fullSlug": "custom-schema/draggable-inline-content", + "pathFromRoot": "examples/06-custom-schema/draggable-inline-content", + "config": { + "playground": true, + "docs": false, + "author": "hectorzhuang", + "tags": [] + }, + "title": "Draggable Inline Content", + "group": { + "pathFromRoot": "examples/06-custom-schema", + "slug": "custom-schema" + }, + "readme": "" + }, + { + "projectSlug": "react-custom-blocks", + "fullSlug": "custom-schema/react-custom-blocks", + "pathFromRoot": "examples/06-custom-schema/react-custom-blocks", + "config": { + "playground": true, + "docs": false, + "author": "matthewlipski", + "tags": [] + }, + "title": "Custom Blocks - React API", + "group": { + "pathFromRoot": "examples/06-custom-schema", + "slug": "custom-schema" + }, + "readme": "" + }, + { + "projectSlug": "react-custom-inline-content", + "fullSlug": "custom-schema/react-custom-inline-content", + "pathFromRoot": "examples/06-custom-schema/react-custom-inline-content", + "config": { + "playground": true, + "docs": false, + "author": "matthewlipski", + "tags": [] + }, + "title": "Custom Inline Content - React API", + "group": { + "pathFromRoot": "examples/06-custom-schema", + "slug": "custom-schema" + }, + "readme": "" + }, + { + "projectSlug": "react-custom-styles", + "fullSlug": "custom-schema/react-custom-styles", + "pathFromRoot": "examples/06-custom-schema/react-custom-styles", + "config": { + "playground": true, + "docs": false, + "author": "matthewlipski", + "tags": [] + }, + "title": "Custom Styles - React API", + "group": { + "pathFromRoot": "examples/06-custom-schema", + "slug": "custom-schema" + }, + "readme": "" + } + ] }, - collaboration: { - pathFromRoot: "examples/07-collaboration", - slug: "collaboration", - projects: [ - { - projectSlug: "partykit", - fullSlug: "collaboration/partykit", - pathFromRoot: "examples/07-collaboration/01-partykit", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["Advanced", "Saving/Loading", "Collaboration"], - dependencies: { + "collaboration": { + "pathFromRoot": "examples/07-collaboration", + "slug": "collaboration", + "projects": [ + { + "projectSlug": "partykit", + "fullSlug": "collaboration/partykit", + "pathFromRoot": "examples/07-collaboration/01-partykit", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Advanced", + "Saving/Loading", + "Collaboration" + ], + "dependencies": { "y-partykit": "^0.0.25", - yjs: "^13.6.27", - } as any, + "yjs": "^13.6.27" + } as any }, - title: "Collaborative Editing with PartyKit", - group: { - pathFromRoot: "examples/07-collaboration", - slug: "collaboration", + "title": "Collaborative Editing with PartyKit", + "group": { + "pathFromRoot": "examples/07-collaboration", + "slug": "collaboration" }, - readme: - "In this example, we use PartyKit to let multiple users collaborate on a single BlockNote document in real-time.\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)\n- [PartyKit](/docs/features/collaboration#partykit)", + "readme": "In this example, we use PartyKit to let multiple users collaborate on a single BlockNote document in real-time.\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)\n- [PartyKit](/docs/features/collaboration#partykit)" }, { - projectSlug: "liveblocks", - fullSlug: "collaboration/liveblocks", - pathFromRoot: "examples/07-collaboration/02-liveblocks", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["Advanced", "Saving/Loading", "Collaboration"], - dependencies: { + "projectSlug": "liveblocks", + "fullSlug": "collaboration/liveblocks", + "pathFromRoot": "examples/07-collaboration/02-liveblocks", + "config": { + "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", - } as any, + "yjs": "^13.6.27" + } as any }, - title: "Collaborative Editing with Liveblocks", - group: { - pathFromRoot: "examples/07-collaboration", - slug: "collaboration", + "title": "Collaborative Editing with Liveblocks", + "group": { + "pathFromRoot": "examples/07-collaboration", + "slug": "collaboration" }, - readme: - "In this example, we use\nthe [Liveblocks + BlockNote setup guide](https://liveblocks.io/docs/get-started/react-blocknote)\nto create a collaborative BlockNote editor, where multiple people can work on\nthe same document in real-time.\n\nUsers can also add comments to the documents to start threads, which are\ndisplayed next to the editor. As well as that, they can react to, reply to, and\nresolve existing comments.\n\n**Try it out:** Open this page in a new browser tab or window to see it in\naction!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Liveblocks](/docs/features/collaboration#liveblocks)\n\n**From Liveblocks Website:**\n\n- [Get Started with BlockNote](https://liveblocks.io/docs/get-started/react-blocknote)\n- [Ready Made Features](https://liveblocks.io/docs/ready-made-features/text-editor/blocknote)\n- [API Reference](https://liveblocks.io/docs/api-reference/liveblocks-react-blocknote)\n- [Advanced Example](https://liveblocks.io/examples/collaborative-text-editor/nextjs-blocknote)", + "readme": "In this example, we use\nthe [Liveblocks + BlockNote setup guide](https://liveblocks.io/docs/get-started/react-blocknote)\nto create a collaborative BlockNote editor, where multiple people can work on\nthe same document in real-time.\n\nUsers can also add comments to the documents to start threads, which are\ndisplayed next to the editor. As well as that, they can react to, reply to, and\nresolve existing comments.\n\n**Try it out:** Open this page in a new browser tab or window to see it in\naction!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Liveblocks](/docs/features/collaboration#liveblocks)\n\n**From Liveblocks Website:**\n\n- [Get Started with BlockNote](https://liveblocks.io/docs/get-started/react-blocknote)\n- [Ready Made Features](https://liveblocks.io/docs/ready-made-features/text-editor/blocknote)\n- [API Reference](https://liveblocks.io/docs/api-reference/liveblocks-react-blocknote)\n- [Advanced Example](https://liveblocks.io/examples/collaborative-text-editor/nextjs-blocknote)" }, { - projectSlug: "y-sweet", - fullSlug: "collaboration/y-sweet", - pathFromRoot: "examples/07-collaboration/03-y-sweet", - config: { - playground: true, - docs: true, - author: "jakelazaroff", - tags: ["Advanced", "Saving/Loading", "Collaboration"], - dependencies: { - "@y-sweet/react": "^0.6.3", - } as any, - }, - title: "Collaborative Editing with Y-Sweet", - group: { - pathFromRoot: "examples/07-collaboration", - slug: "collaboration", - }, - readme: - "In this example, we use Y-Sweet to let multiple users collaborate on a single BlockNote document in real-time.\n\n**Try it out:** Open the [Y-Sweet BlockNote demo](https://demos.y-sweet.dev/blocknote) in multiple browser tabs to see it in action!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Real-time collaboration](/docs/features/collaboration)\n- [Y-Sweet on Jamsocket](https://docs.jamsocket.com/y-sweet/tutorials/blocknote)", - }, - { - projectSlug: "electric-sql", - fullSlug: "collaboration/electric-sql", - pathFromRoot: "examples/07-collaboration/04-electric-sql", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Advanced", "Saving/Loading", "Collaboration"], - }, - title: "Collaborative Editing with ElectricSQL", - group: { - pathFromRoot: "examples/07-collaboration", - slug: "collaboration", - }, - readme: - "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).\n\n**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!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Real-time collaboration](/docs/features/collaboration)\n- [ElectricSQL](https://electric-sql.com/)", - }, - { - projectSlug: "comments", - fullSlug: "collaboration/comments", - pathFromRoot: "examples/07-collaboration/05-comments", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["Advanced", "Comments", "Collaboration"], - dependencies: { + "projectSlug": "y-sweet", + "fullSlug": "collaboration/y-sweet", + "pathFromRoot": "examples/07-collaboration/03-y-sweet", + "config": { + "playground": true, + "docs": true, + "author": "jakelazaroff", + "tags": [ + "Advanced", + "Saving/Loading", + "Collaboration" + ], + "dependencies": { + "@y-sweet/react": "^0.6.3" + } as any + }, + "title": "Collaborative Editing with Y-Sweet", + "group": { + "pathFromRoot": "examples/07-collaboration", + "slug": "collaboration" + }, + "readme": "In this example, we use Y-Sweet to let multiple users collaborate on a single BlockNote document in real-time.\n\n**Try it out:** Open the [Y-Sweet BlockNote demo](https://demos.y-sweet.dev/blocknote) in multiple browser tabs to see it in action!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Real-time collaboration](/docs/features/collaboration)\n- [Y-Sweet on Jamsocket](https://docs.jamsocket.com/y-sweet/tutorials/blocknote)" + }, + { + "projectSlug": "electric-sql", + "fullSlug": "collaboration/electric-sql", + "pathFromRoot": "examples/07-collaboration/04-electric-sql", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Advanced", + "Saving/Loading", + "Collaboration" + ] + }, + "title": "Collaborative Editing with ElectricSQL", + "group": { + "pathFromRoot": "examples/07-collaboration", + "slug": "collaboration" + }, + "readme": "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).\n\n**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!\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Real-time collaboration](/docs/features/collaboration)\n- [ElectricSQL](https://electric-sql.com/)" + }, + { + "projectSlug": "comments", + "fullSlug": "collaboration/comments", + "pathFromRoot": "examples/07-collaboration/05-comments", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "Advanced", + "Comments", + "Collaboration" + ], + "dependencies": { "@y-sweet/react": "^0.6.3", - "@mantine/core": "^8.3.4", - } as any, + "@mantine/core": "^8.3.4" + } as any }, - title: "Comments & Threads", - group: { - pathFromRoot: "examples/07-collaboration", - slug: "collaboration", + "title": "Comments & Threads", + "group": { + "pathFromRoot": "examples/07-collaboration", + "slug": "collaboration" }, - readme: - 'In this example, you can add comments to the document while collaborating with others. You can also pick user accounts with different permissions, as well as react to, reply to, and resolve existing comments. The comments are displayed floating next to the text they refer to, and appear when selecting said text.\n\n**Try it out:** Click the "Add comment" button in the [Formatting Toolbar](/docs/react/components/formatting-toolbar) to add a comment!\n\n**Relevant Docs:**\n\n- [Comments](/docs/features/collaboration/comments)\n- [Real-time collaboration](/docs/features/collaboration)\n- [Y-Sweet on Jamsocket](https://docs.jamsocket.com/y-sweet/tutorials/blocknote)\n- [Editor Setup](/docs/getting-started/editor-setup)', + "readme": "In this example, you can add comments to the document while collaborating with others. You can also pick user accounts with different permissions, as well as react to, reply to, and resolve existing comments. The comments are displayed floating next to the text they refer to, and appear when selecting said text.\n\n**Try it out:** Click the \"Add comment\" button in the [Formatting Toolbar](/docs/react/components/formatting-toolbar) to add a comment!\n\n**Relevant Docs:**\n\n- [Comments](/docs/features/collaboration/comments)\n- [Real-time collaboration](/docs/features/collaboration)\n- [Y-Sweet on Jamsocket](https://docs.jamsocket.com/y-sweet/tutorials/blocknote)\n- [Editor Setup](/docs/getting-started/editor-setup)" }, { - projectSlug: "comments-with-sidebar", - fullSlug: "collaboration/comments-with-sidebar", - pathFromRoot: "examples/07-collaboration/06-comments-with-sidebar", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["Advanced", "Comments", "Collaboration"], - dependencies: { + "projectSlug": "comments-with-sidebar", + "fullSlug": "collaboration/comments-with-sidebar", + "pathFromRoot": "examples/07-collaboration/06-comments-with-sidebar", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "Advanced", + "Comments", + "Collaboration" + ], + "dependencies": { "y-partykit": "^0.0.25", - yjs: "^13.6.27", - "@mantine/core": "^8.3.4", - } as any, - }, - title: "Threads Sidebar", - group: { - pathFromRoot: "examples/07-collaboration", - slug: "collaboration", - }, - readme: - 'In this example, you can add comments to the document while collaborating with others. You can also pick user accounts with different permissions, as well as react to, reply to, and resolve existing comments. The comments are displayed floating next to the text they refer to, and appear when selecting said text. The comments are shown in a separate sidebar using the `ThreadsSidebar` component.\n\n**Try it out:** Click the "Add comment" button in\nthe [Formatting Toolbar](/docs/react/components/formatting-toolbar) to add a\ncomment!\n\n**Relevant Docs:**\n\n- [Comments Sidebar](/docs/features/collaboration/comments#sidebar-view)\n- [Real-time collaboration](/docs/features/collaboration)\n- [Y-Sweet on Jamsocket](https://docs.jamsocket.com/y-sweet/tutorials/blocknote)\n- [Editor Setup](/docs/getting-started/editor-setup)', - }, - { - projectSlug: "ghost-writer", - fullSlug: "collaboration/ghost-writer", - pathFromRoot: "examples/07-collaboration/07-ghost-writer", - config: { - playground: true, - docs: false, - author: "nperez0111", - tags: ["Advanced", "Development", "Collaboration"], - dependencies: { + "yjs": "^13.6.27", + "@mantine/core": "^8.3.4" + } as any + }, + "title": "Threads Sidebar", + "group": { + "pathFromRoot": "examples/07-collaboration", + "slug": "collaboration" + }, + "readme": "In this example, you can add comments to the document while collaborating with others. You can also pick user accounts with different permissions, as well as react to, reply to, and resolve existing comments. The comments are displayed floating next to the text they refer to, and appear when selecting said text. The comments are shown in a separate sidebar using the `ThreadsSidebar` component.\n\n**Try it out:** Click the \"Add comment\" button in\nthe [Formatting Toolbar](/docs/react/components/formatting-toolbar) to add a\ncomment!\n\n**Relevant Docs:**\n\n- [Comments Sidebar](/docs/features/collaboration/comments#sidebar-view)\n- [Real-time collaboration](/docs/features/collaboration)\n- [Y-Sweet on Jamsocket](https://docs.jamsocket.com/y-sweet/tutorials/blocknote)\n- [Editor Setup](/docs/getting-started/editor-setup)" + }, + { + "projectSlug": "ghost-writer", + "fullSlug": "collaboration/ghost-writer", + "pathFromRoot": "examples/07-collaboration/07-ghost-writer", + "config": { + "playground": true, + "docs": false, + "author": "nperez0111", + "tags": [ + "Advanced", + "Development", + "Collaboration" + ], + "dependencies": { "y-partykit": "^0.0.25", - yjs: "^13.6.27", - } as any, + "yjs": "^13.6.27" + } as any }, - title: "Ghost Writer", - group: { - pathFromRoot: "examples/07-collaboration", - slug: "collaboration", + "title": "Ghost Writer", + "group": { + "pathFromRoot": "examples/07-collaboration", + "slug": "collaboration" }, - readme: - "In this example, we use a local Yjs document to store the document state, and have a ghost writer that edits the document in real-time.\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)", + "readme": "In this example, we use a local Yjs document to store the document state, and have a ghost writer that edits the document in real-time.\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: "forking", - fullSlug: "collaboration/forking", - pathFromRoot: "examples/07-collaboration/08-forking", - config: { - playground: true, - docs: false, - author: "nperez0111", - tags: ["Advanced", "Development", "Collaboration"], - dependencies: { + "projectSlug": "forking", + "fullSlug": "collaboration/forking", + "pathFromRoot": "examples/07-collaboration/08-forking", + "config": { + "playground": true, + "docs": false, + "author": "nperez0111", + "tags": [ + "Advanced", + "Development", + "Collaboration" + ], + "dependencies": { "y-partykit": "^0.0.25", - yjs: "^13.6.27", - } as any, + "yjs": "^13.6.27" + } as any }, - title: "Collaborative Editing with Forking", - group: { - pathFromRoot: "examples/07-collaboration", - slug: "collaboration", + "title": "Collaborative Editing with Forking", + "group": { + "pathFromRoot": "examples/07-collaboration", + "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)", - }, - ], + "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)" + } + ] }, - extensions: { - pathFromRoot: "examples/08-extensions", - slug: "extensions", - projects: [ - { - projectSlug: "tiptap-arrow-conversion", - fullSlug: "extensions/tiptap-arrow-conversion", - pathFromRoot: "examples/08-extensions/01-tiptap-arrow-conversion", - config: { - playground: true, - docs: true, - author: "komsenapati", - tags: ["Extension"], - pro: true, - dependencies: { - "@tiptap/core": "^3.11.0", - } as any, - }, - title: "TipTap extension (arrow InputRule)", - group: { - pathFromRoot: "examples/08-extensions", - slug: "extensions", - }, - readme: - "This example shows how to set up a BlockNote editor with a TipTap extension that registers an InputRule to convert `->` into `→`.\n\n**Try it out:** Type `->` anywhere in the editor and see how it's automatically converted to a single arrow unicode character.", - }, - ], + "extensions": { + "pathFromRoot": "examples/08-extensions", + "slug": "extensions", + "projects": [ + { + "projectSlug": "tiptap-arrow-conversion", + "fullSlug": "extensions/tiptap-arrow-conversion", + "pathFromRoot": "examples/08-extensions/01-tiptap-arrow-conversion", + "config": { + "playground": true, + "docs": true, + "author": "komsenapati", + "tags": [ + "Extension" + ], + "pro": true, + "dependencies": { + "@tiptap/core": "^3.11.0" + } as any + }, + "title": "TipTap extension (arrow InputRule)", + "group": { + "pathFromRoot": "examples/08-extensions", + "slug": "extensions" + }, + "readme": "This example shows how to set up a BlockNote editor with a TipTap extension that registers an InputRule to convert `->` into `→`.\n\n**Try it out:** Type `->` anywhere in the editor and see how it's automatically converted to a single arrow unicode character." + } + ] }, - ai: { - pathFromRoot: "examples/09-ai", - slug: "ai", - projects: [ - { - projectSlug: "minimal", - fullSlug: "ai/minimal", - pathFromRoot: "examples/09-ai/01-minimal", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["AI", "llm"], - dependencies: { + "ai": { + "pathFromRoot": "examples/09-ai", + "slug": "ai", + "projects": [ + { + "projectSlug": "minimal", + "fullSlug": "ai/minimal", + "pathFromRoot": "examples/09-ai/01-minimal", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "AI", + "llm" + ], + "dependencies": { "@blocknote/xl-ai": "latest", "@mantine/core": "^8.3.4", - ai: "^5.0.102", - } as any, - }, - title: "Rich Text editor AI integration", - group: { - pathFromRoot: "examples/09-ai", - slug: "ai", - }, - readme: - "This example shows the minimal setup to add AI integration to your BlockNote rich text editor.\n\nSelect some text and click the AI (stars) button, or type `/ai` anywhere in the editor to access AI functionality.\n\n**Relevant Docs:**\n\n- [Getting Stared with BlockNote AI](/docs/features/ai/getting-started)\n- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar)\n- [Changing Slash Menu Items](/docs/react/components/suggestion-menus)", - }, - { - projectSlug: "playground", - fullSlug: "ai/playground", - pathFromRoot: "examples/09-ai/02-playground", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["AI", "llm"], - dependencies: { + "ai": "^5.0.102" + } as any + }, + "title": "Rich Text Editor AI Integration", + "group": { + "pathFromRoot": "examples/09-ai", + "slug": "ai" + }, + "readme": "This example shows the minimal setup to add AI integration to your BlockNote rich text editor.\n\nSelect some text and click the AI (stars) button, or type `/ai` anywhere in the editor to access AI functionality.\n\n**Relevant Docs:**\n\n- [Getting Started with BlockNote AI](/docs/features/ai/getting-started)\n- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar)\n- [Changing Slash Menu Items](/docs/react/components/suggestion-menus)" + }, + { + "projectSlug": "playground", + "fullSlug": "ai/playground", + "pathFromRoot": "examples/09-ai/02-playground", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "AI", + "llm" + ], + "dependencies": { "@blocknote/xl-ai": "latest", "@mantine/core": "^8.3.4", - ai: "^5.0.102", - } as any, - }, - title: "AI Playground", - group: { - pathFromRoot: "examples/09-ai", - slug: "ai", - }, - readme: - "Explore different LLM models integrated with BlockNote in the AI Playground.\n\nChange the configuration, then highlight some text to access the AI menu, or type `/ai` anywhere in the editor.\n\n**Relevant Docs:**\n\n- [Getting Stared with BlockNote AI](/docs/features/ai/getting-started)\n- [BlockNote AI Reference](/docs/features/ai/reference)\n- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar)\n- [Changing Slash Menu Items](/docs/react/components/suggestion-menus)", - }, - { - projectSlug: "custom-ai-menu-items", - fullSlug: "ai/custom-ai-menu-items", - pathFromRoot: "examples/09-ai/03-custom-ai-menu-items", - config: { - playground: true, - docs: true, - author: "matthewlipski", - tags: ["AI", "llm"], - dependencies: { + "ai": "^5.0.102" + } as any + }, + "title": "AI Playground", + "group": { + "pathFromRoot": "examples/09-ai", + "slug": "ai" + }, + "readme": "Explore different LLM models integrated with BlockNote in the AI Playground.\n\nChange the configuration, then highlight some text to access the AI menu, or type `/ai` anywhere in the editor.\n\n**Relevant Docs:**\n\n- [Getting Stared with BlockNote AI](/docs/features/ai/getting-started)\n- [BlockNote AI Reference](/docs/features/ai/reference)\n- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar)\n- [Changing Slash Menu Items](/docs/react/components/suggestion-menus)" + }, + { + "projectSlug": "custom-ai-menu-items", + "fullSlug": "ai/custom-ai-menu-items", + "pathFromRoot": "examples/09-ai/03-custom-ai-menu-items", + "config": { + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": [ + "AI", + "llm" + ], + "dependencies": { "@blocknote/xl-ai": "latest", "@mantine/core": "^8.3.4", - ai: "^5.0.102", - "react-icons": "^5.2.1", - } as any, - }, - title: "Adding AI Menu Items", - group: { - pathFromRoot: "examples/09-ai", - slug: "ai", - }, - readme: - 'In this example, we add two items to the AI Menu. The first prompts the AI to make the selected text more casual, and can be found by selecting some text and click the AI (stars) button. The second prompts the AI to give ideas on related topics to extend the document with, and can be found by clicking the "Ask AI" Slash Menu item.\n\nSelect some text and click the AI (stars) button, or type `/ai` anywhere in the editor to access AI functionality.\n\n**Relevant Docs:**\n\n- [Getting Stared with BlockNote AI](/docs/features/ai/getting-started)\n- [Custom AI Menu Items](/docs/features/ai/custom-commands)', - }, - { - projectSlug: "with-collaboration", - fullSlug: "ai/with-collaboration", - pathFromRoot: "examples/09-ai/04-with-collaboration", - config: { - playground: true, - docs: false, - author: "nperez0111", - tags: ["AI", "llm"], - dependencies: { + "ai": "^5.0.102", + "react-icons": "^5.2.1" + } as any + }, + "title": "Adding AI Menu Items", + "group": { + "pathFromRoot": "examples/09-ai", + "slug": "ai" + }, + "readme": "In this example, we add two items to the AI Menu. The first prompts the AI to make the selected text more casual, and can be found by selecting some text and click the AI (stars) button. The second prompts the AI to give ideas on related topics to extend the document with, and can be found by clicking the \"Ask AI\" Slash Menu item.\n\nSelect some text and click the AI (stars) button, or type `/ai` anywhere in the editor to access AI functionality.\n\n**Relevant Docs:**\n\n- [Getting Stared with BlockNote AI](/docs/features/ai/getting-started)\n- [Custom AI Menu Items](/docs/features/ai/custom-commands)" + }, + { + "projectSlug": "with-collaboration", + "fullSlug": "ai/with-collaboration", + "pathFromRoot": "examples/09-ai/04-with-collaboration", + "config": { + "playground": true, + "docs": false, + "author": "nperez0111", + "tags": [ + "AI", + "llm" + ], + "dependencies": { "@blocknote/xl-ai": "latest", "@mantine/core": "^8.3.4", - ai: "^5.0.102", + "ai": "^5.0.102", "y-partykit": "^0.0.25", - yjs: "^13.6.27", - } as any, - }, - title: "AI + Ghost Writer", - group: { - pathFromRoot: "examples/09-ai", - slug: "ai", - }, - readme: - "This example combines the AI extension with the ghost writer example to show how to use the AI extension in a collaborative environment.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar#changing-the-formatting-toolbar)\n- [Changing Slash Menu Items](/docs/react/components/suggestion-menus#changing-slash-menu-items)\n- [Getting Stared with BlockNote AI](/docs/features/ai/setup)", - }, - { - projectSlug: "manual-execution", - fullSlug: "ai/manual-execution", - pathFromRoot: "examples/09-ai/05-manual-execution", - config: { - playground: true, - docs: false, - author: "yousefed", - tags: ["AI", "llm"], - dependencies: { + "yjs": "^13.6.27" + } as any + }, + "title": "AI + Ghost Writer", + "group": { + "pathFromRoot": "examples/09-ai", + "slug": "ai" + }, + "readme": "This example combines the AI extension with the ghost writer example to show how to use the AI extension in a collaborative environment.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Changing the Formatting Toolbar](/docs/react/components/formatting-toolbar#changing-the-formatting-toolbar)\n- [Changing Slash Menu Items](/docs/react/components/suggestion-menus#changing-slash-menu-items)\n- [Getting Stared with BlockNote AI](/docs/features/ai/setup)" + }, + { + "projectSlug": "manual-execution", + "fullSlug": "ai/manual-execution", + "pathFromRoot": "examples/09-ai/05-manual-execution", + "config": { + "playground": true, + "docs": false, + "author": "yousefed", + "tags": [ + "AI", + "llm" + ], + "dependencies": { "@blocknote/xl-ai": "latest", "@mantine/core": "^8.3.4", - ai: "^5.0.102", + "ai": "^5.0.102", "y-partykit": "^0.0.25", - yjs: "^13.6.27", - } as any, - }, - title: "AI manual execution", - group: { - pathFromRoot: "examples/09-ai", - slug: "ai", - }, - readme: - "Instead of calling AI models directly, this example shows how you can use an existing stream of responses and apply them to the editor.", - }, - { - projectSlug: "client-side-transport", - fullSlug: "ai/client-side-transport", - pathFromRoot: "examples/09-ai/06-client-side-transport", - config: { - playground: true, - docs: true, - author: "yousefed", - tags: ["AI", "llm"], - dependencies: { + "yjs": "^13.6.27" + } as any + }, + "title": "AI manual execution", + "group": { + "pathFromRoot": "examples/09-ai", + "slug": "ai" + }, + "readme": "Instead of calling AI models directly, this example shows how you can use an existing stream of responses and apply them to the editor." + }, + { + "projectSlug": "client-side-transport", + "fullSlug": "ai/client-side-transport", + "pathFromRoot": "examples/09-ai/06-client-side-transport", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "AI", + "llm" + ], + "dependencies": { "@ai-sdk/groq": "^2.0.16", "@blocknote/xl-ai": "latest", "@mantine/core": "^8.3.4", - ai: "^5.0.102", - } as any, - }, - title: "AI Integration with ClientSideTransport", - group: { - pathFromRoot: "examples/09-ai", - slug: "ai", - }, - readme: - "The standard setup is to have BlockNote AI call your server, which then calls an LLM of your choice. In this example, we show how you can use the `ClientSideTransport` to make calls directly to your LLM provider.\n\nTo hide API keys of our LLM provider, we do still route calls through a proxy server using `fetchViaProxy` (this is optional).", - }, - { - projectSlug: "server-persistence", - fullSlug: "ai/server-persistence", - pathFromRoot: "examples/09-ai/07-server-persistence", - config: { - playground: true, - docs: false, - author: "yousefed", - tags: ["AI", "llm"], - dependencies: { + "ai": "^5.0.102" + } as any + }, + "title": "AI Integration with ClientSideTransport", + "group": { + "pathFromRoot": "examples/09-ai", + "slug": "ai" + }, + "readme": "The standard setup is to have BlockNote AI call your server, which then calls an LLM of your choice. In this example, we show how you can use the `ClientSideTransport` to make calls directly to your LLM provider.\n\nTo hide API keys of our LLM provider, we do still route calls through a proxy server using `fetchViaProxy` (this is optional)." + }, + { + "projectSlug": "server-persistence", + "fullSlug": "ai/server-persistence", + "pathFromRoot": "examples/09-ai/07-server-persistence", + "config": { + "playground": true, + "docs": false, + "author": "yousefed", + "tags": [ + "AI", + "llm" + ], + "dependencies": { "@blocknote/xl-ai": "latest", "@mantine/core": "^8.3.4", - ai: "^5.0.102", - } as any, + "ai": "^5.0.102" + } as any + }, + "title": "AI Integration with server LLM message persistence", + "group": { + "pathFromRoot": "examples/09-ai", + "slug": "ai" + }, + "readme": "This example shows how to setup to add AI integration while handling the LLM calls (in this case, using the Vercel AI SDK) on your server, using a custom executor.\n\nInstead of sending all messages, these are kept server-side and we only submit the latest message." + }, + { + "projectSlug": "autocomplete", + "fullSlug": "ai/autocomplete", + "pathFromRoot": "examples/09-ai/08-autocomplete", + "config": { + "playground": true, + "docs": true, + "author": "yousefed", + "tags": [ + "AI", + "autocomplete" + ], + "dependencies": { + "@blocknote/xl-ai": "latest", + "@mantine/core": "^8.3.4", + "ai": "^5.0.102" + } as any }, - title: "AI Integration with server LLM message persistence", - group: { - pathFromRoot: "examples/09-ai", - slug: "ai", + "title": "AI Autocomplete", + "group": { + "pathFromRoot": "examples/09-ai", + "slug": "ai" }, - readme: - "This example shows how to setup to add AI integration while handling the LLM calls (in this case, using the Vercel AI SDK) on your server, using a custom executor.\n\nInstead of sending all messages, these are kept server-side and we only submit the latest message.", - }, - ], + "readme": "This example demonstrates the AI autocomplete feature in BlockNote. As you type, the editor will automatically suggest completions using AI.\n\nPress **Tab** to accept a suggestion or **Escape** to dismiss it.\n\n**Relevant Docs:**\n\n- [AI Autocomplete](/docs/features/ai/autocomplete)\n- [AI Getting Started](/docs/features/ai/getting-started)" + } + ] }, "vanilla-js": { - pathFromRoot: "examples/vanilla-js", - slug: "vanilla-js", - projects: [ - { - projectSlug: "react-vanilla-custom-blocks", - fullSlug: "vanilla-js/react-vanilla-custom-blocks", - pathFromRoot: "examples/vanilla-js/react-vanilla-custom-blocks", - config: { - playground: true, - docs: false, - author: "matthewlipski", - tags: [], - }, - title: "Custom Blocks - Vanilla JS API", - group: { - pathFromRoot: "examples/vanilla-js", - slug: "vanilla-js", - }, - readme: "", - }, - { - projectSlug: "react-vanilla-custom-inline-content", - fullSlug: "vanilla-js/react-vanilla-custom-inline-content", - pathFromRoot: "examples/vanilla-js/react-vanilla-custom-inline-content", - config: { - playground: true, - docs: false, - author: "matthewlipski", - tags: [], - }, - title: "Custom Inline Content - Vanilla JS API", - group: { - pathFromRoot: "examples/vanilla-js", - slug: "vanilla-js", - }, - readme: "", - }, - { - projectSlug: "react-vanilla-custom-styles", - fullSlug: "vanilla-js/react-vanilla-custom-styles", - pathFromRoot: "examples/vanilla-js/react-vanilla-custom-styles", - config: { - playground: true, - docs: false, - author: "matthewlipski", - tags: [], - }, - title: "Custom Styles - Vanilla JS API", - group: { - pathFromRoot: "examples/vanilla-js", - slug: "vanilla-js", - }, - readme: "", - }, - ], - }, -}; + "pathFromRoot": "examples/vanilla-js", + "slug": "vanilla-js", + "projects": [ + { + "projectSlug": "react-vanilla-custom-blocks", + "fullSlug": "vanilla-js/react-vanilla-custom-blocks", + "pathFromRoot": "examples/vanilla-js/react-vanilla-custom-blocks", + "config": { + "playground": true, + "docs": false, + "author": "matthewlipski", + "tags": [] + }, + "title": "Custom Blocks - Vanilla JS API", + "group": { + "pathFromRoot": "examples/vanilla-js", + "slug": "vanilla-js" + }, + "readme": "" + }, + { + "projectSlug": "react-vanilla-custom-inline-content", + "fullSlug": "vanilla-js/react-vanilla-custom-inline-content", + "pathFromRoot": "examples/vanilla-js/react-vanilla-custom-inline-content", + "config": { + "playground": true, + "docs": false, + "author": "matthewlipski", + "tags": [] + }, + "title": "Custom Inline Content - Vanilla JS API", + "group": { + "pathFromRoot": "examples/vanilla-js", + "slug": "vanilla-js" + }, + "readme": "" + }, + { + "projectSlug": "react-vanilla-custom-styles", + "fullSlug": "vanilla-js/react-vanilla-custom-styles", + "pathFromRoot": "examples/vanilla-js/react-vanilla-custom-styles", + "config": { + "playground": true, + "docs": false, + "author": "matthewlipski", + "tags": [] + }, + "title": "Custom Styles - Vanilla JS API", + "group": { + "pathFromRoot": "examples/vanilla-js", + "slug": "vanilla-js" + }, + "readme": "" + } + ] + } +}; \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aec701f239..7587ed0f71 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4193,6 +4193,58 @@ importers: specifier: ^5.4.20 version: 5.4.20(@types/node@24.8.1)(lightningcss@1.30.1)(terser@5.44.1) + examples/09-ai/08-autocomplete: + 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 + '@blocknote/xl-ai': + specifier: latest + version: link:../../../packages/xl-ai + '@mantine/core': + specifier: ^8.3.4 + version: 8.3.4(@mantine/hooks@8.3.4(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@mantine/hooks': + specifier: ^8.3.4 + version: 8.3.4(react@19.2.0) + '@mantine/utils': + specifier: ^6.0.22 + version: 6.0.22(react@19.2.0) + ai: + specifier: ^5.0.102 + version: 5.0.102(zod@4.1.12) + react: + specifier: ^19.2.0 + version: 19.2.0 + react-dom: + specifier: ^19.2.0 + version: 19.2.0(react@19.2.0) + devDependencies: + '@types/react': + specifier: ^19.2.2 + version: 19.2.2 + '@types/react-dom': + specifier: ^19.2.1 + version: 19.2.2(@types/react@19.2.2) + '@vitejs/plugin-react': + specifier: ^4.7.0 + version: 4.7.0(vite@5.4.20(@types/node@24.8.1)(lightningcss@1.30.1)(terser@5.44.1)) + vite: + specifier: ^5.4.20 + version: 5.4.20(@types/node@24.8.1)(lightningcss@1.30.1)(terser@5.44.1) + examples/vanilla-js/react-vanilla-custom-blocks: dependencies: '@blocknote/ariakit':