diff --git a/packages/react/src/TableHandles/components/TableHandleMenu/DefaultButtons/AddButton.tsx b/packages/react/src/TableHandles/components/TableHandleMenu/DefaultButtons/AddButton.tsx index 46d587e0cb..67c008b427 100644 --- a/packages/react/src/TableHandles/components/TableHandleMenu/DefaultButtons/AddButton.tsx +++ b/packages/react/src/TableHandles/components/TableHandleMenu/DefaultButtons/AddButton.tsx @@ -24,6 +24,7 @@ export const AddRowButton = < props.editor.updateBlock(props.block, { type: "table", content: { + type: "tableContent", rows, }, } as PartialBlock); diff --git a/packages/website/docs/docs/block-types.md b/packages/website/docs/docs/block-types.md index 1f20be8026..eaf86cbb97 100644 --- a/packages/website/docs/docs/block-types.md +++ b/packages/website/docs/docs/block-types.md @@ -124,6 +124,24 @@ type ImageBlock = { `width:` The image width in pixels. +### Table + +**Appearance** + +image + +**Type & Props** + +```typescript +type TableBlock = { + id: string; + type: "table"; + props: DefaultProps; + content: TableContent[]; + children: Block[]; +}; +``` + ## Default Block Properties While each type of block can have its own set of properties, there are some properties that all built-in block types have by default, which you can find in the definition for `DefaultProps`: @@ -144,78 +162,93 @@ type DefaultProps = { ## Custom Block Types -In addition to the default block types that BlockNote offers, you can also make your own custom blocks. Take a look at the demo below, in which we add a custom block containing an image and caption to a BlockNote editor, as well as a custom [Slash Menu Item](/docs/slash-menu#custom-items) to insert it. +In addition to the default block types that BlockNote offers, you can also make your own custom blocks. Take a look at the demo below, in which we add a custom block containing a paragraph with a different font to a BlockNote editor, as well as a custom [Slash Menu Item](/docs/slash-menu#custom-items) to insert it. ::: sandbox {template=react-ts} ```typescript-vue /App.tsx import { - BlockNoteEditor, - BlockSchema, - DefaultBlockSchema, defaultBlockSchema, + defaultBlockSpecs, defaultProps, } from "@blocknote/core"; import { BlockNoteView, useBlockNote, createReactBlockSpec, - InlineContent, ReactSlashMenuItem, getDefaultReactSlashMenuItems, } from "@blocknote/react"; import "@blocknote/core/style.css"; -import { RiImage2Fill } from "react-icons/ri"; +import { RiText } from "react-icons/ri"; export default function App() { - // Creates a custom image block. - const ImageBlock = createReactBlockSpec({ - type: "image", - propSchema: { - ...defaultProps, - src: { - default: "https://via.placeholder.com/1000", - }, - alt: { - default: "image", + // Creates a paragraph block with custom font. + const FontParagraphBlock = createReactBlockSpec( + { + type: "fontParagraph", + propSchema: { + ...defaultProps, + font: { + default: "Comic Sans MS", + }, }, + content: "inline", }, - content: "inline", - render: ({ block }) => ( -
- {block.props.alt} - -
- ), - }); + { + render: ({ block, contentRef }) => { + const style = { + fontFamily: block.props.font + }; + + return ( +

+ ); + }, + toExternalHTML: ({ contentRef }) =>

, + parse: (element) => { + const font = element.style.fontFamily; + + if (font === "") { + return; + } + + return { + font: font || undefined, + }; + }, + } + ); - // The custom schema, which includes the default blocks and the custom image - // block. - const customSchema = { + // Our block schema, which contains the configs for blocks that we want our + // editor to use. + const blockSchema = { // Adds all default blocks. ...defaultBlockSchema, - // Adds the custom image block. - image: ImageBlock, - } satisfies BlockSchema; + // Adds the font paragraph. + fontParagraph: FontParagraphBlock.config, + }; + // Our block specs, which contain the configs and implementations for blocks + // that we want our editor to use. + const blockSpecs = { + // Adds all default blocks. + ...defaultBlockSpecs, + // Adds the font paragraph. + fontParagraph: FontParagraphBlock, + }; - // Creates a slash menu item for inserting an image block. - const insertImage: ReactSlashMenuItem = { - name: "Insert Image", + // Creates a slash menu item for inserting a font paragraph block. + const insertFontParagraph: ReactSlashMenuItem = { + name: "Insert Font Paragraph", execute: (editor) => { - const src: string | null = prompt("Enter image URL"); - const alt: string | null = prompt("Enter image alt text"); + const font = prompt("Enter font name"); editor.insertBlocks( [ { - type: "image", + type: "fontParagraph", props: { - src: src || "https://via.placeholder.com/1000", - alt: alt || "image", + font: font || undefined, }, }, ], @@ -223,19 +256,18 @@ export default function App() { "after" ); }, - aliases: ["image", "img", "picture", "media"], - group: "Media", - icon: , - hint: "Insert an image", + aliases: ["p", "paragraph", "font"], + group: "Other", + icon: , }; // Creates a new editor instance. const editor = useBlockNote({ // Tells BlockNote which blocks to use. - blockSchema: customSchema, + blockSpecs: blockSpecs, slashMenuItems: [ - ...getDefaultReactSlashMenuItems(customSchema), - insertImage, + ...getDefaultReactSlashMenuItems(blockSchema), + insertFontParagraph, ], }); @@ -270,105 +302,143 @@ We'd love to hear your feedback on GitHub or in our Discord community! To define a custom block type, we use the `createReactBlockSpec` function, for which you can see the definition below: ```typescript -type PropSchema = Record< +type PropSchema< + PrimitiveType extends "boolean" | "number" | "string" +> = Record< string, { - default: string; - values?: string[]; + default: PrimitiveType; + values?: PrimitiveType[]; }; > -function createReactBlockSpec(config: { - type: string; - propSchema: PropSchema; - containsInlineContent: boolean; - render: (props: { - block: Block, - editor: BlockNoteEditor - }) => JSX.Element; -}): BlockType; +function createReactBlockSpec( + blockConfig: { + type: string; + propSchema: PropSchema<"boolean" | "number" | "string">; + content: "inline" | "none" + }, + blockImplementation: { + render: React.FC<{ + block: Block; + editor: BlockNoteEditor; + contentRef: (node: HTMLElement | null) => void; + }>, + toExternalHTML?: React.FC<{ + block: Block; + editor: BlockNoteEditor; + contentRef: (node: HTMLElement | null) => void; + }>, + parse?: ( + element: HTMLElement + ) => PartialBlock["props"] | undefined; + } +): BlockType; ``` Let's look at our custom image block from the demo, and go over each field in-depth to explain how it works: ```typescript jsx -const ImageBlock = createReactBlockSpec({ - type: "image", - propSchema: { - src: { - default: "https://via.placeholder.com/1000", +const FontparagraphBlock = createReactBlockSpec( + { + type: "fontParagraph", + propSchema: { + ...defaultProps, + font: { + default: "Comic Sans MS", + }, }, + content: "inline", }, - content: "inline", - render: ({ block }) => ( -

- {"Image"} - -
- ), -}); + { + render: ({ block, contentRef }) => { + const style = { + fontFamily: block.props.font + }; + + return ( +

+ ); + }, + parse: (element) => { + const font = element.style.fontFamily; + return { + font: font || undefined, + }; + }, + } +); ``` -#### `type` +You can see that `createReactBlockSpec` takes two object arguments: + +#### `blockConfig` + +This defines the block's type, properties, and content type. It allows BlockNote to know how to handle manipulating the block internally and provide typing. + +**`type`** Defines the name of the block, in this case, `image`. -#### `propSchema` +**`content`** + +As we saw in [Block Objects](/docs/blocks#block-objects), blocks can contain editable rich text which is represented as [Inline Content](/docs/inline-content). The `content` field allows your custom block to contain an editable rich-text field. Since we want to be able to type in our paragraph, we set it to `"inline"`. -This is an object which defines the props that the block should have. In this case, we want the block to have a `src` prop for the URL of the image, so we add a `src` key. We also want basic styling options for the image block, so we also add the [Default Block Properties](/docs/block-types#default-block-properties) using `defaultProps`. The value of each key is an object with a mandatory `default` field and an optional `values` field: +**`propSchema`** -`default:` Stores the prop's default value, so we use a placeholder image URL for `src` if no URL is provided. +This is an object which defines the props that the block should have. In this case, we want the block to have a `font` prop for the font that we want the paragraph to use, so we add a `font` key. We also want basic styling options, so we add the [Default Block Properties](/docs/block-types#default-block-properties) using `defaultProps`. The value of each key is an object with a mandatory `default` field and an optional `values` field: -`values:` Stores an array of strings that the prop can take. If `values` is not defined, BlockNote assumes the prop can be any string, which makes sense for `src`, since it can be any image URL. +`default:` Stores the prop's default value, in this case the Comic Sans MS font. -#### `containsInlineContent` +`values:` Stores an array of strings that the prop can take. If `values` is not defined, BlockNote assumes the prop can be any string, which makes sense for `font`, since we don't want to list every possible font name. -As we saw in [Block Objects](/docs/blocks#block-objects), blocks can contain editable rich text which is represented as [Inline Content](/docs/inline-content). The `containsInlineContent` field allows your custom block to contain an editable rich-text field. For the custom image block, we use an inline content field to create our caption, so it's set to `true`. +#### `blockImplementation` -#### `render` +This defines how the block should be rendered in the editor, and how it should be parsed from and converted to HTML. -This is a React component which defines how your custom block should be rendered in the editor, and takes two props: +**`render`** -`block:` The block that should be rendered. +This is a React component which defines how your custom block should be rendered in the editor, and takes three props: + +`block:` The block that should be rendered. This will always have the same type, props, and content as defined in the block's config. `editor:` The BlockNote editor instance that the block is in. -For our custom image block, we use a parent `div` which contains the image and caption. Since `block` will always be an `image` block, we also know it contains a `src` prop, and can pass it to the child `img` element. +`contentRef:` A React `ref` that marks which element in your block is editable, This is only useful if your block config contains `content: "inline"`. + +**`toExternalHTML`** + +This is identical in definition as `render`, but is used whenever the block is being exported to HTML for use outside BlockNote, namely when copying it to the clipboard. If it's not defined, BlockNote will just use `render` for the HTML conversion. -But what's this `InlineContent` component? Since we set `containsInlineContent` to `true`, it means we want to include an editable rich-text field somewhere in the image block. You should use the `InlineContent` component to represent this field in your `render` component. Since we're using it to create our caption, we add it below the `img` element. +**`parse`** -In the DOM, the `InlineContent` component is rendered as a `div` by default, but you can make it use a different element by passing `as={"elementTag"}` as a prop. For example, `as={"p"}` will make the `InlineContent` component get rendered to a paragraph. +This is a function that allows you to define which HTML elements should be parsed into your block when importing HTML from outside BlockNote, namely when pasting it from the clipboard. If the element should be parsed into your custom block, you should return the props that the block should be given. Otherwise, return `undefined`. -While the `InlineContent` component can be put anywhere inside the component you pass to `render`, you should make sure to only have one. If `containsInlineContent` is set to false, `render` shouldn't contain any. +`element`: The HTML element that's being parsed. ### Adding Custom Blocks to the Editor -Now, we need to tell BlockNote to use our custom image block by passing it to the editor in the `blockSchema` option. Let's again look at the image block from the demo as an example: +Now, we need to tell BlockNote to use our font paragraph block by passing it to the editor in the `blockSpecs` option. Let's again look at the image block from the demo as an example: ```typescript jsx +// Our block specs, which contain the configs and implementations for blocks +// that we want our editor to use. +const blockSpecs = { + // Adds all default blocks. + ...defaultBlockSpecs, + // Adds the font paragraph. + fontParagraph: FontParagraphBlock, +}; + +... + // Creates a new editor instance. const editor = useBlockNote({ // Tells BlockNote which blocks to use. - blockSchema: { - // Adds all default blocks. - ...defaultBlockSchema, - // Adds the custom image block. - image: ImageBlock, - }, + blockSpecs: blockSpecs, }); ``` -Since we still want the editor to use the [Built-In Block Types](/docs/block-types#built-in-block-types), we add `defaultBlockSchema` to our custom block schema. The key which we use for the custom image block is the same string we use for its type. Make sure that this is always the case for your own custom blocks. +Since we still want the editor to use the [Built-In Block Types](/docs/block-types#built-in-block-types), we add `defaultBlockSpecs` too. The key which we use for the font paragraph block should also be the same string we use for its type. Make sure that this is always the case for your own custom blocks. And we're done! You now know how to create custom blocks and add them to the editor. Head to [Manipulating Blocks](/docs/manipulating-blocks) to see what you can do with them in the editor. diff --git a/packages/website/docs/docs/blocks.md b/packages/website/docs/docs/blocks.md index f22d6d69eb..81e7c99069 100644 --- a/packages/website/docs/docs/blocks.md +++ b/packages/website/docs/docs/blocks.md @@ -65,7 +65,7 @@ type Block = { `props:` The block's properties, which are stored in a set of key/value pairs and specify how the block looks and behaves. Different block types have different props - see [Block Types & Properties](/docs/block-types) for more. -`content:` The block's rich text content, represented as an array of `InlineContent` objects. This does not include content from any nested blocks. For more information on `InlineContent` objects, visit [Inline Content](/docs/inline-content). +`content:` The block's rich text content, represented as an array of `InlineContent` objects. This does not include content from any nested blocks. [Table](/docs/block-types#table) blocks are slightly different, as they contain `TableContent`, where each table cell is represented as an array of `InlineContent` objects. For more information on `InlineContent` and `TableContent` objects, visit [Inline Content](/docs/inline-content). `children:` Any blocks nested inside the block. The nested blocks are also represented using `Block` objects. diff --git a/packages/website/docs/docs/inline-content.md b/packages/website/docs/docs/inline-content.md index 76948bee3a..212aba48f5 100644 --- a/packages/website/docs/docs/inline-content.md +++ b/packages/website/docs/docs/inline-content.md @@ -7,9 +7,11 @@ path: /docs/inline-content # Inline Content +## Inline Content Types + An array of `InlineContent` objects is used to describe the rich text content inside a block. Inline content can either be styled text or a link, and we'll go over both these in the upcoming sections. -## Styled Text +### Styled Text Styled text is a type of `InlineContent` used to display pieces of text with styles, and is defined using a `StyledText` object: @@ -25,7 +27,7 @@ type StyledText = { `styles:` The styles that are applied to the text. -### Styles Object +**Styles Object** `StyledText` supports a variety of styles, including bold, underline, and text color, which are represented using a `Styles` object: @@ -40,7 +42,7 @@ type Styles = Partial<{ }>; ``` -## Links +### Links Links are a type of `InlineContent` used to create clickable hyperlinks that go to some URL, and are made up of `StyledText`. They're defined using `Link` objects: @@ -56,6 +58,20 @@ type Link = { `href:` The URL that opens when clicking the link. +## Table Content + +While most blocks use an array of `InlineContent` objects to describe their content, tables are slightly different. They use a single `TableContent` object, which allows each table cell to be represented as an array of `InlineContent` objects instead: + + +```typescript +type TableContent = { + type: "tableContent"; + rows: { + cells: InlineContent[][]; + }[]; +}; +``` + ## Editor Functions While `InlineContent` objects are used to describe a block's content, they can be cumbersome to work with directly. Therefore, BlockNote exposes functions which make it easier to edit block contents. diff --git a/packages/website/docs/public/img/screenshots/table_type.png b/packages/website/docs/public/img/screenshots/table_type.png new file mode 100644 index 0000000000..11921f6a82 Binary files /dev/null and b/packages/website/docs/public/img/screenshots/table_type.png differ diff --git a/packages/website/docs/public/img/screenshots/table_type_dark.png b/packages/website/docs/public/img/screenshots/table_type_dark.png new file mode 100644 index 0000000000..88b1c952f7 Binary files /dev/null and b/packages/website/docs/public/img/screenshots/table_type_dark.png differ