diff --git a/.vscode/settings.json b/.vscode/settings.json index 7469bd1c70..7c24081186 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,5 +18,8 @@ "search.defaultViewMode": "tree", "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[mdx]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" } } diff --git a/examples/01-basic/02-block-objects/src/App.tsx b/examples/01-basic/02-block-objects/src/App.tsx index b33a9561e7..e02e9c95d6 100644 --- a/examples/01-basic/02-block-objects/src/App.tsx +++ b/examples/01-basic/02-block-objects/src/App.tsx @@ -48,7 +48,7 @@ export default function App() {
Document JSON:
-          {JSON.stringify(blocks, null, 2)}
+          {JSON.stringify(editor.document, null, 2)}
         
diff --git a/examples/05-interoperability/08-converting-blocks-to-react-email/App.tsx b/examples/05-interoperability/08-converting-blocks-to-react-email/src/App.tsx similarity index 100% rename from examples/05-interoperability/08-converting-blocks-to-react-email/App.tsx rename to examples/05-interoperability/08-converting-blocks-to-react-email/src/App.tsx diff --git a/examples/05-interoperability/08-converting-blocks-to-react-email/styles.css b/examples/05-interoperability/08-converting-blocks-to-react-email/src/styles.css similarity index 100% rename from examples/05-interoperability/08-converting-blocks-to-react-email/styles.css rename to examples/05-interoperability/08-converting-blocks-to-react-email/src/styles.css diff --git a/examples/06-custom-schema/06-toggleable-blocks/App.tsx b/examples/06-custom-schema/06-toggleable-blocks/src/App.tsx similarity index 97% rename from examples/06-custom-schema/06-toggleable-blocks/App.tsx rename to examples/06-custom-schema/06-toggleable-blocks/src/App.tsx index c99f4ce9ec..9ed4b95202 100644 --- a/examples/06-custom-schema/06-toggleable-blocks/App.tsx +++ b/examples/06-custom-schema/06-toggleable-blocks/src/App.tsx @@ -4,7 +4,7 @@ import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; import { useCreateBlockNote } from "@blocknote/react"; -import { ToggleBlock } from "./Toggle.js"; +import { ToggleBlock } from "./Toggle"; // Our schema with block specs, which contain the configs and implementations for // blocks that we want our editor to use. diff --git a/examples/06-custom-schema/06-toggleable-blocks/Toggle.tsx b/examples/06-custom-schema/06-toggleable-blocks/src/Toggle.tsx similarity index 100% rename from examples/06-custom-schema/06-toggleable-blocks/Toggle.tsx rename to examples/06-custom-schema/06-toggleable-blocks/src/Toggle.tsx diff --git a/fumadocs/app/examples/layout.tsx b/fumadocs/app/examples/layout.tsx index c9d7a5a1e1..91ad590440 100644 --- a/fumadocs/app/examples/layout.tsx +++ b/fumadocs/app/examples/layout.tsx @@ -13,7 +13,11 @@ export default function Layout({ children }: { children: ReactNode }) { for (const page of category.children) { if (page.type === "page" && page.$ref?.file) { const [exampleGroupName, exampleName] = page.$ref.file.split("/"); - const exampleData = getExampleData(exampleGroupName, exampleName); + + const exampleData = getExampleData( + exampleGroupName, + exampleName.split(".mdx")[0], + ); page.name = ( diff --git a/fumadocs/app/global.css b/fumadocs/app/global.css index 49609ad1dc..2a4db71c34 100644 --- a/fumadocs/app/global.css +++ b/fumadocs/app/global.css @@ -1,6 +1,7 @@ @import "tailwindcss"; @import "fumadocs-ui/css/vitepress.css"; @import "fumadocs-ui/css/preset.css"; +@import "fumadocs-twoslash/twoslash.css"; @source "."; @source "../components"; diff --git a/fumadocs/app/pricing/tiers.tsx b/fumadocs/app/pricing/tiers.tsx index 5bfbc5ba1e..405f4f1950 100644 --- a/fumadocs/app/pricing/tiers.tsx +++ b/fumadocs/app/pricing/tiers.tsx @@ -1,5 +1,5 @@ "use client"; -import { useSession } from "@/util/auth-client"; +import { authClient, useSession } from "@/util/auth-client"; import { CheckIcon } from "@heroicons/react/20/solid"; import { track } from "@vercel/analytics"; import classNames from "classnames"; @@ -55,15 +55,12 @@ function TierDescription({ tier }: { tier: Tier }) { function TierCTAButton({ tier }: { tier: Tier }) { const { data: session } = useSession(); - let href = "/signup"; let text = "Sign up"; if (session) { if (session.planType === "free") { - href = `/api/auth/checkout/${tier.id}`; text = "Buy now"; } else { - href = "/api/auth/portal"; text = session.planType === tier.id ? "Manage subscription" @@ -72,17 +69,32 @@ function TierCTAButton({ tier }: { tier: Tier }) { } return ( { + if (!session) { + return; + } + + if (session.planType === "free") { + track("Signup", { tier: tier.id }); + e.preventDefault(); + e.stopPropagation(); + await authClient.checkout({ + slug: tier.id, + }); + } else { + e.preventDefault(); + e.stopPropagation(); + await authClient.customer.portal(); + } + }} + href={tier.href ?? (session ? undefined : "/signup")} aria-describedby={tier.id} className={classNames( tier.mostPopular ? "text-fd-foreground bg-indigo-600 shadow-sm hover:bg-indigo-500" : "text-indigo-600 ring-1 ring-inset ring-indigo-600 hover:text-indigo-500 hover:ring-indigo-500", - "mt-8 block rounded-md px-3 py-2 text-center text-sm font-semibold leading-6 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600", + "mt-8 block cursor-pointer rounded-md px-3 py-2 text-center text-sm font-semibold leading-6 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600", )} - onClick={() => { - track("Signup", { tier: tier.id }); - }} > {tier.id === "enterprise" ? "Get in touch" : text} diff --git a/fumadocs/auth.ts b/fumadocs/auth.ts index 4766c4cc67..54a03e9f28 100644 --- a/fumadocs/auth.ts +++ b/fumadocs/auth.ts @@ -1,4 +1,4 @@ -import { polar } from "@polar-sh/better-auth"; +import { polar, checkout, portal, webhooks } from "@polar-sh/better-auth"; import { Polar } from "@polar-sh/sdk"; import * as Sentry from "@sentry/nextjs"; import { betterAuth } from "better-auth"; @@ -199,61 +199,56 @@ export const auth = betterAuth({ client: polarClient, // Enable automatic Polar Customer creation on signup createCustomerOnSignUp: true, - // http://localhost:3000/api/auth/portal - enableCustomerPortal: true, - // Configure checkout - checkout: { - enabled: true, - products: [ - { - productId: PRODUCTS.business.id, // ID of Product from Polar Dashboard - slug: PRODUCTS.business.slug, // Custom slug for easy reference in Checkout URL, e.g. /checkout/pro - // http://localhost:3000/api/auth/checkout/business - }, - { - productId: PRODUCTS.starter.id, - slug: PRODUCTS.starter.slug, - // http://localhost:3000/api/auth/checkout/starter - }, - ], - successUrl: "/pages/thanks", - }, - // Incoming Webhooks handler will be installed at /polar/webhooks - webhooks: { - // webhooks have to be publicly accessible - // ngrok http http://localhost:3000 - secret: process.env.POLAR_WEBHOOK_SECRET as string, - async onPayload(payload) { - switch (payload.type) { - case "subscription.active": - case "subscription.canceled": - case "subscription.updated": - case "subscription.revoked": - case "subscription.created": - case "subscription.uncanceled": { - const authContext = await auth.$context; - const userId = payload.data.customer.externalId; - if (!userId) { - return; - } - if (payload.data.status === "active") { - const productId = payload.data.product.id; - const planType = Object.values(PRODUCTS).find( - (p) => p.id === productId, - )?.slug; - await authContext.internalAdapter.updateUser(userId, { - planType, - }); - } else { - // No active subscription, so we need to remove the plan type - await authContext.internalAdapter.updateUser(userId, { - planType: null, - }); + use: [ + checkout({ + products: [ + { + productId: PRODUCTS.business.id, // ID of Product from Polar Dashboard + slug: PRODUCTS.business.slug, // Custom slug for easy reference in Checkout URL, e.g. /checkout/pro + }, + { + productId: PRODUCTS.starter.id, + slug: PRODUCTS.starter.slug, + }, + ], + successUrl: "/thanks", + authenticatedUsersOnly: true, + }), + portal(), + webhooks({ + secret: process.env.POLAR_WEBHOOK_SECRET as string, + async onPayload(payload) { + switch (payload.type) { + case "subscription.active": + case "subscription.canceled": + case "subscription.updated": + case "subscription.revoked": + case "subscription.created": + case "subscription.uncanceled": { + const authContext = await auth.$context; + const userId = payload.data.customer.externalId; + if (!userId) { + return; + } + if (payload.data.status === "active") { + const productId = payload.data.product.id; + const planType = Object.values(PRODUCTS).find( + (p) => p.id === productId, + )?.slug; + await authContext.internalAdapter.updateUser(userId, { + planType, + }); + } else { + // No active subscription, so we need to remove the plan type + await authContext.internalAdapter.updateUser(userId, { + planType: null, + }); + } } } - } - }, - }, + }, + }), + ], }), ], onAPIError: { diff --git a/fumadocs/components/DocPage.tsx b/fumadocs/components/DocPage.tsx index 2c1562ab88..f5bd594bd7 100644 --- a/fumadocs/components/DocPage.tsx +++ b/fumadocs/components/DocPage.tsx @@ -1,4 +1,3 @@ -import ThemedImage from "@/components/ThemedImage"; import { getMDXComponents } from "@/util/mdx-components"; import { getPageTreePeers } from "fumadocs-core/server"; import { LoaderOutput } from "fumadocs-core/source"; @@ -16,7 +15,10 @@ export function CardTable({ }) { return ( - {getPageTreePeers(source.pageTree, `/docs/${path}`).map((peer) => ( + {getPageTreePeers( + source.pageTree, + `docs/${path.startsWith("/") ? path.slice(1) : path}`, + ).map((peer) => ( {peer.description} @@ -63,11 +65,11 @@ export async function DocPage(props: { ( - + CardTable: (cardProps: any) => ( + ), - ThemedImage, })} /> diff --git a/fumadocs/content/docs/advanced/meta.json b/fumadocs/content/docs/advanced/meta.json deleted file mode 100644 index fd81d02bb4..0000000000 --- a/fumadocs/content/docs/advanced/meta.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "title": "Advanced", - "pages": [ - "nextjs", - "ariakit", - "shadcn", - "grid-suggestion-menus", - "code-blocks", - "paste-handling", - "tables", - "custom-schemas", - "vanilla-js", - "..." - ] -} diff --git a/fumadocs/content/docs/collaboration/index.mdx b/fumadocs/content/docs/collaboration/index.mdx deleted file mode 100644 index a10e7d34e2..0000000000 --- a/fumadocs/content/docs/collaboration/index.mdx +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Collaboration -description: Learn how to create multiplayer experiences with BlockNote ---- - -BlockNote supports multi-user collaborative document editing. - - diff --git a/fumadocs/content/docs/collaboration/meta.json b/fumadocs/content/docs/collaboration/meta.json deleted file mode 100644 index ed0a0329eb..0000000000 --- a/fumadocs/content/docs/collaboration/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Collaboration", - "pages": ["real-time-collaboration", "comments"] -} diff --git a/fumadocs/content/docs/editor-api/converting-blocks.mdx b/fumadocs/content/docs/editor-api/converting-blocks.mdx deleted file mode 100644 index dcb9617287..0000000000 --- a/fumadocs/content/docs/editor-api/converting-blocks.mdx +++ /dev/null @@ -1,124 +0,0 @@ ---- -title: Markdown & HTML -description: It's possible to export or import Blocks to and from Markdown and HTML. -imageTitle: Markdown & HTML -path: /docs/converting-blocks ---- - -It's possible to export or import Blocks to and from Markdown and HTML. - - - The functions to import/export to and from Markdown/HTML are considered "lossy"; some information might be dropped when you export Blocks to those formats. - - To serialize Blocks to a non-lossy format (for example, to store the contents of the editor in your backend), simply export the built-in Block format using `JSON.stringify(editor.document)`. - - - -## Markdown - -BlockNote can import / export Blocks to and from Markdown. Note that this is also considered "lossy", as not all structures can be entirely represented in Markdown. - -### Converting Blocks to Markdown - -`blocksToMarkdownLossy` converts `Block` objects to a Markdown string: - -```typescript -async blocksToMarkdownLossy(blocks?: Block[]): Promise; - -// Usage -const markdownFromBlocks = await editor.blocksToMarkdownLossy(blocks); -``` - -`blocks:` The blocks to convert. If not provided, the entire document (all top-level blocks) is used. - -`returns:` The blocks, serialized as a Markdown string. - -The output is simplified as Markdown does not support all features of BlockNote (e.g.: children of blocks which aren't list items are un-nested and certain styles are removed). - -**Demo** - - - -### Parsing Markdown to Blocks - -Use `tryParseMarkdownToBlocks` to try parsing a Markdown string into `Block` objects: - -```typescript -async tryParseMarkdownToBlocks(markdown: string): Promise; - -// Usage -const blocksFromMarkdown = await editor.tryParseMarkdownToBlocks(markdown); -``` - -`returns:` The blocks parsed from the Markdown string. - -Tries to create `Block` and `InlineContent` objects based on Markdown syntax, though not all symbols are recognized. If BlockNote doesn't recognize a symbol, it will parse it as text. - -**Demo** - - - -## Export HTML (for static rendering) - -Use `blocksToFullHTML` to export the entire document with all structure, styles and formatting. -The exported HTML is the same as BlockNote would use to render the editor, and includes all structure for nested blocks. - -For example, you an use this for static rendering documents that have been created in the editor. -Make sure to include the same stylesheets when you want to render the output HTML ([see example](/examples/backend/rendering-static-documents)). - -```typescript -async blocksToFullHTML(blocks?: Block[]): Promise; - -// Usage -const HTMLFromBlocks = await editor.blocksToFullHTML(blocks); -``` - -`blocks:` The blocks to convert. If not provided, the entire document (all top-level blocks) is used. - -`returns:` The blocks, exported to an HTML string. - -## HTML (for interoperability) - -The editor exposes functions to convert Blocks to and from HTML for interoperability with other applications. - -Converting Blocks to HTML this way will lose some information such as the nesting of nodes in order to export a simple HTML structure. - -### Converting Blocks to HTML - -Use `blocksToHTMLLossy` to export `Block` objects to an HTML string: - -```typescript -async blocksToHTMLLossy(blocks?: Block[]): Promise; - -// Usage -const HTMLFromBlocks = await editor.blocksToHTMLLossy(blocks); -``` - -`blocks:` The blocks to convert. If not provided, the entire document (all top-level blocks) is used. - -`returns:` The blocks, exported to an HTML string. - -To better conform to HTML standards, children of blocks which aren't list items are un-nested in the output HTML. - -**Demo** - - - -### Parsing HTML to Blocks - -Use `tryParseHTMLToBlocks` to parse an HTML string to `Block` objects: - -```typescript -async tryParseHTMLToBlocks(html: string): Promise; - -// Usage -const blocksFromHTML = await editor.tryParseHTMLToBlocks(html); -``` - -`returns:` The blocks parsed from the HTML string. - -Tries to create `Block` objects out of any HTML block-level elements, and `InlineContent` objects from any HTML inline elements, though not all HTML tags are recognized. If BlockNote doesn't recognize an element's tag, it will parse it as a paragraph or plain text. - -**Demo** - - diff --git a/fumadocs/content/docs/editor-api/events.mdx b/fumadocs/content/docs/editor-api/events.mdx deleted file mode 100644 index 6da13181ef..0000000000 --- a/fumadocs/content/docs/editor-api/events.mdx +++ /dev/null @@ -1,91 +0,0 @@ ---- -title: Events -description: BlockNote emits events when certain actions occur. -imageTitle: Events -path: /docs/events ---- - -Explore the events emitted by the editor. - -## `onCreate` - -The `onCreate` callback is called when the editor is initialized. - -```typescript -editor.onCreate(() => { - console.log("Editor created"); -}); -``` - -## `onChange` - -The `onChange` callback is called when the editor content changes. - -```typescript -editor.onChange((editor, { getChanges }) => { - console.log("Editor updated"); - const changes = getChanges(); - console.log(changes); -}); -``` - -You can see what specific changes occurred in the editor by calling `getChanges()` in the callback. This function returns an array of block changes which looks like: - -```typescript -/** - * The changes that occurred in the editor. - */ -type BlocksChanged = Array< - | { - // The affected block - block: Block; - // The source of the change - source: BlockChangeSource; - type: "insert" | "delete"; - // Insert and delete changes don't have a previous block - prevBlock: undefined; - } - | { - // The affected block - block: Block; - // The source of the change - source: BlockChangeSource; - type: "update"; - // The block before the update - prevBlock: Block; - } -)>; - -/** - * This attributes the changes to a specific source. - */ -type BlockChangeSource = { - /** - * The type of change source: - * - "local": Triggered by local user (default) - * - "paste": From paste operation - * - "drop": From drop operation - * - "undo"/"redo"/"undo-redo": From undo/redo operations - * - "yjs-remote": From remote user - */ - type: - | "local" - | "paste" - | "drop" - | "undo" - | "redo" - | "undo-redo" - | "yjs-remote"; -}; -``` - - -## `onSelectionChange` - -The `onSelectionChange` callback is called when the editor selection changes. - -```typescript -editor.onSelectionChange(() => { - console.log("Editor selection changed"); -}); -``` \ No newline at end of file diff --git a/fumadocs/content/docs/editor-api/index.mdx b/fumadocs/content/docs/editor-api/index.mdx deleted file mode 100644 index 1b95af7a33..0000000000 --- a/fumadocs/content/docs/editor-api/index.mdx +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Editor API -description: BlockNote exposes an API to interact with the editor and its contents from code. These methods are directly available on the `BlockNoteEditor` instance you created when instantiating your editor. -imageTitle: Editor API -path: /docs/editor-api ---- - -Explore how to interact with the editor and its contents from code. - - \ No newline at end of file diff --git a/fumadocs/content/docs/editor-api/meta.json b/fumadocs/content/docs/editor-api/meta.json deleted file mode 100644 index 9f35cb9934..0000000000 --- a/fumadocs/content/docs/editor-api/meta.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "title": "Editor API", - "pages": [ - "manipulating-blocks", - "manipulating-inline-content", - "cursor-selections", - "converting-blocks", - "events", - "server-processing", - "export-to-pdf", - "export-to-docx", - "export-to-odt", - "..." - ] -} diff --git a/fumadocs/content/docs/editor-basics/default-schema.mdx b/fumadocs/content/docs/editor-basics/default-schema.mdx deleted file mode 100644 index 9e9ac44cfd..0000000000 --- a/fumadocs/content/docs/editor-basics/default-schema.mdx +++ /dev/null @@ -1,179 +0,0 @@ ---- -title: Default Content Types -description: BlockNote supports a variety on built-in block and inline content types that are included in the editor by default. -imageTitle: Default Content Types ---- - -BlockNote supports a number of built-in blocks, inline content types, and styles that are included in the editor by default. This is called the Default Schema. To create your own content types, see [Custom Schemas](/docs/custom-schemas). - -The demo below showcases each of BlockNote's built-in block and inline content types: - - - -## Default Blocks - -BlockNote's built-in blocks range from basic paragraphs to tables and images. Let's look more in-depth at the default blocks and the properties they support: - -### Paragraph - -```typescript -type ParagraphBlock = { - id: string; - type: "paragraph"; - props: DefaultProps; - content: InlineContent[]; - children: Block[]; -}; -``` - -### Heading - -```typescript -type HeadingBlock = { - id: string; - type: "heading"; - props: { - level: 1 | 2 | 3 = 1; - } & DefaultProps; - content: InlineContent[]; - children: Block[]; -}; -``` - -`level:` The heading level, representing a title (`level: 1`), heading (`level: 2`), and subheading (`level: 3`). - -### Quote - -```typescript -type QuoteBlock = { - id: string; - type: "quote"; - props: DefaultProps; - content: InlineContent[]; - children: Block[]; -}; -``` - -### Bullet List Item - -```typescript -type BulletListItemBlock = { - id: string; - type: "bulletListItem"; - props: DefaultProps; - content: InlineContent[]; - children: Block[]; -}; -``` - -### Numbered List Item - -```typescript -type NumberedListItemBlock = { - id: string; - type: "numberedListItem"; - props: DefaultProps; - content: InlineContent[]; - children: Block[]; -}; -``` - -### Image - -```typescript -type ImageBlock = { - id: string; - type: "image"; - props: { - url: string = ""; - caption: string = ""; - previewWidth: number = 512; - } & DefaultProps; - content: undefined; - children: Block[]; -}; -``` - -`url:` The image URL. - -`caption:` The image caption. - -`previewWidth:` The image previewWidth in pixels. - -### Table - -```typescript -type TableBlock = { - id: string; - type: "table"; - props: DefaultProps; - content: TableContent; - children: Block[]; -}; -``` - -## Default Block Properties - -There are some default block props that BlockNote uses for the built-in blocks: - -```typescript -type DefaultProps = { - backgroundColor: string = "default"; - textColor: string = "default"; - textAlignment: "left" | "center" | "right" | "justify" = "left"; -}; -``` - -`backgroundColor:` The background color of the block, which also applies to nested blocks. - -`textColor:` The text color of the block, which also applies to nested blocks. - -`textAlignment:` The text alignment of the block. - -## Default Inline Content - -By default, `InlineContent` (the content of text blocks like paragraphs) in BlockNote can either be a `StyledText` or a `Link` object. Here's an overview of all default inline content and the properties they support: - -### Styled Text - -`StyledText` is a type of `InlineContent` used to display pieces of text with styles: - -```typescript -type StyledText = { - type: "text"; - text: string; - styles: Styles; -}; -``` - -### Link - -`Link` objects represent links to a URL: - -```typescript -type Link = { - type: "link"; - content: StyledText[]; - href: string; -}; -``` - -## Default Styles - -The default text formatting options in BlockNote are represented by the `Styles` in the default schema: - -```typescript -type Styles = { - bold: boolean; - italic: boolean; - underline: boolean; - strike: boolean; - textColor: string; - backgroundColor: string; -}; -``` - -## Creating New Block or Inline Content Types - -You can also extend your editor and create your own Blocks, Inline Content or Styles using React. -Skip to [Custom Schemas (advanced)](/docs/custom-schemas) to learn how to do this. diff --git a/fumadocs/content/docs/editor-basics/index.mdx b/fumadocs/content/docs/editor-basics/index.mdx deleted file mode 100644 index af9f08351f..0000000000 --- a/fumadocs/content/docs/editor-basics/index.mdx +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Editor Basics -description: Basic concepts and methods to setup your editor -imageTitle: Editor Basics ---- - -In this section, we first explore the [methods to setup your editor](/docs/editor-basics/setup). -Then, we'll dive into the structure of documents, Blocks and rich text content in BlockNote ([document structure](/docs/editor-basics/document-structure)). -We'll also go over the blocks and content types that are part of BlockNote's [default built-in schema](/docs/editor-basics/default-schema). - - diff --git a/fumadocs/content/docs/editor-basics/meta.json b/fumadocs/content/docs/editor-basics/meta.json deleted file mode 100644 index 822d877754..0000000000 --- a/fumadocs/content/docs/editor-basics/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Editor Basics", - "pages": ["setup", "document-structure", "default-schema", "..."] -} diff --git a/fumadocs/content/docs/editor-basics/setup.mdx b/fumadocs/content/docs/editor-basics/setup.mdx deleted file mode 100644 index 35e5da7cf9..0000000000 --- a/fumadocs/content/docs/editor-basics/setup.mdx +++ /dev/null @@ -1,187 +0,0 @@ ---- -title: Editor Setup -description: Learn how to setup your BlockNote editor using the `useCreateBlockNote` hook and the `BlockNoteView` component. -imageTitle: Editor Setup ---- - -You can customize your editor when you instantiate it. Let's take a closer looks at the basic methods and components to set up your BlockNote editor. - -## `useCreateBlockNote` hook - -Create a new `BlockNoteEditor` by calling the `useCreateBlockNote` hook. This instantiates a new editor and its required state. You can later interact with the editor using the Editor API and pass it to the `BlockNoteView` component. - -```ts -function useCreateBlockNote( - options?: BlockNoteEditorOptions, - deps?: React.DependencyList = [], -): BlockNoteEditor; - -type BlockNoteEditorOptions = { - animations?: boolean; - collaboration?: CollaborationOptions; - comments?: CommentsConfig; - defaultStyles?: boolean; - dictionary?: Dictionary; - disableExtensions?: string[]; - domAttributes?: Record; - dropCursor?: (opts: { - editor: BlockNoteEditor; - color?: string | false; - width?: number; - class?: string; - }) => Plugin; - initialContent?: PartialBlock[]; - pasteHandler?: (context: { - event: ClipboardEvent; - editor: BlockNoteEditor; - defaultPasteHandler: (context: { - pasteBehavior?: "prefer-markdown" | "prefer-html"; - }) => boolean | undefined; - }) => boolean | undefined; - resolveFileUrl: (url: string) => Promise; - schema?: BlockNoteSchema; - setIdAttribute?: boolean; - sideMenuDetection?: "viewport" | "editor"; - tabBehavior?: "prefer-navigate-ui" | "prefer-indent"; - tables?: TableFeatures; - trailingBlock?: boolean; - uploadFile?: (file: File) => Promise; -}; -``` - -The hook takes two optional parameters: - -**options:** An object containing options for the editor: - -`animations`: Whether changes to blocks (like indentation, creating lists, changing headings) should be animated or not. Defaults to `true`. - -`collaboration`: Options for enabling real-time collaboration. See [Real-time Collaboration](/docs/advanced/real-time-collaboration) for more info. - -`comments`: Configuration for the comments feature, requires a `threadStore`. See [Comments](/docs/collaboration/comments) for more. - -`defaultStyles`: Whether to use the default font and reset the styles of `

`, `

  • `, `

    `, etc. elements that are used in BlockNote. Defaults to true if undefined. - -`dictionary`: Provide strings for localization. See the [Localization / i18n example](/examples/basic/localization) and [Custom Placeholders](/examples/basic/custom-placeholder). - -`disableExtensions` (_advanced_): Disables TipTap extensions used in BlockNote by default, based on their names. - -`domAttributes:` An object containing HTML attributes that should be added to various DOM elements in the editor. See [Adding DOM Attributes](/docs/theming#adding-dom-attributes) for more. - -`dropCursor`: A replacement indicator to use when dragging and dropping blocks. Uses the [ProseMirror drop cursor](https://github.com/ProseMirror/prosemirror-dropcursor), or a modified version when [Column Blocks](/docs/editor-basics/document-structure#column-blocks) are enabled. - -`initialContent:` The content that should be in the editor when it's created, represented as an array of [Partial Blocks](/docs/manipulating-blocks#partial-blocks). - -`pasteHandler`: A function that can be used to override the default paste behavior. See [Paste Handling](/docs/advanced/paste-handling) for more. - -`resolveFileUrl:` Function to resolve file URLs for display/download. Useful for creating authenticated URLs or implementing custom protocols. - -`resolveUsers`: Function to resolve user information for comments. See [Comments](/docs/collaboration/comments) for more. - -`schema`: The editor schema if you want to extend your editor with custom blocks, styles, or inline content [Custom Schemas](/docs/custom-schemas). - -`setIdAttribute`: Whether to render an `id` HTML attribute on blocks as well as a `data-id` attribute. Defaults to `false`. - -`sideMenuDetection`: Determines whether the mouse cursor position is locked to the editor bounding box for showing the [Block Side Menu](/docs/ui-components/side-menu) and block drag & drop. When set to `viewport`, the Side Menu will be shown next to the nearest block to the cursor, regardless of where it is in the viewport. Dropping blocks will also be locked to the editor bounding box. Otherwise, the Side Menu will only be shown when the cursor is within the editor bounds, and blocks can only be dropped when hovering the editor. In order to use multiple editors, must be set to `editor`. Defaults to `viewport`. - -`tabBehavior`: Determines whether pressing the tab key should navigate toolbars for keyboard accessibility. When set to `"prefer-navigate-ui`, the user can navigate toolbars using Tab. Pressing Escape re-focuses the editor, and Tab now indents blocks. `"prefer-indent"` causes Tab to always indent blocks. Defaults to `prefer-navigate-ui`. - -`tables`: Configuration for table features. Allowing you to enable more advanced table features like `splitCells`, `cellBackgroundColor`, `cellTextColor`, and `headers`. - -`trailingBlock`: An option which user can pass with `false` value to disable the automatic creation of a trailing new block on the next line when the user types or edits any block. Defaults to `true` if undefined. - -`uploadFile`: A function which handles file uploads and eventually returns the URL to the uploaded file. Used for [Image blocks](/docs/editor-basics/default-schema#image). - -**deps:** Dependency array that's internally passed to `useMemo`. A new editor will only be created when this array changes. - - - Manually creating the editor (`BlockNoteEditor.create`) -

    - The `useCreateBlockNote` hook is actually a simple `useMemo` wrapper around - the `BlockNoteEditor.create` method. You can use this method directly if you - want to control the editor lifecycle manually. For example, we do this in - the [Saving & Loading example](/examples/backend/saving-loading) to delay - the editor creation until some content has been fetched from an external - data source. -

    -
    - -## Rendering the Editor with `` - -Use the `` component to render the `BlockNoteEditor` instance you just created: - -```tsx -const editor = useCreateBlockNote(); - -return ; -``` - -### Props - -There are a number of additional props you can pass to `BlockNoteView`. You can find the full list of these below: - -```typescript -export type BlockNoteViewProps = { - editor: BlockNoteEditor; - editable?: boolean; - onSelectionChange?: () => void; - onChange?: () => void; - theme?: - | "light" - | "dark" - | Theme - | { - light: Theme; - dark: Theme; - }; - formattingToolbar?: boolean; - linkToolbar?: boolean; - sideMenu?: boolean; - slashMenu?: boolean; - emojiPicker?: boolean; - filePanel?: boolean; - tableHandles?: boolean; - comments?: boolean; - children?: -} & HTMLAttributes; -``` - -`editor`: The `BlockNoteEditor` instance to render. - -`editable`: Whether the editor should be editable. - -`onSelectionChange`: Callback fired when the editor selection changes. - -`onChange`: Callback fired when the editor content (document) changes. - -`theme`: The editor's theme, see [Themes](/docs/styling-theming/themes) for more about this. - -`formattingToolbar`: Whether the [Formatting Toolbar](/docs/ui-components/formatting-toolbar) should be enabled. - -`linkToolbar`: Whether the Link Toolbar should be enabled. - -`sideMenu`: Whether the [Block Side Menu](/docs/ui-components/side-menu) should be enabled. - -`slashMenu`: Whether the [Slash Menu](/docs/ui-components/suggestion-menus#slash-menu) should be enabled. - -`emojiPicker`: Whether the [Emoji Picker](/docs/advanced/grid-suggestion-menus#emoji-picker) should be enabled. - -`filePanel`: Whether the File Toolbar should be enabled. - -`tableHandles`: Whether the Table Handles should be enabled. - -`comments`: Whether the default comments UI feature should be enabled. - -`children`: Pass child elements to the `BlockNoteView` to create or customize toolbars, menus, or other UI components. See [UI Components](/docs/ui-components) for more. - -Additional props passed are forwarded to the HTML `div` element BlockNote renders internally. - - - Uncontrolled component -

    - Note that the `BlockNoteView` component is an [uncontrolled component](https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components). - This means you don't pass in the editor content directly as a prop. You can use the `initialContent` option in the `useCreateBlockNote` hook to set the initial content of the editor (similar to the `defaultValue` prop in a regular React `