Skip to content

Commit 8b1849e

Browse files
docs: update custom block/inline content/style docs (#2012)
1 parent 10cdbfb commit 8b1849e

File tree

11 files changed

+288
-53
lines changed

11 files changed

+288
-53
lines changed

docs/content/docs/features/custom-schemas/custom-blocks.mdx

Lines changed: 80 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,22 @@ In addition to the default block types that BlockNote offers, you can also make
1212

1313
## Creating a Custom Block Type
1414

15-
Use the `createReactBlockSpec` function to create a custom block type. This function takes two arguments:
15+
Use the `createReactBlockSpec` function to create a custom block type. This function takes three arguments:
1616

1717
```typescript
1818
function createReactBlockSpec(
1919
blockConfig: CustomBlockConfig,
2020
blockImplementation: ReactCustomBlockImplementation,
21-
);
21+
extensions?: BlockNoteExtension[],
22+
): (options? BlockOptions) => BlockSpec;
2223
```
2324

24-
Let's look at our custom alert block from the demo, and go over each field to explain how it works:
25+
It returns a function that you can call to create an instance of your custom block, or a `BlockSpec`. This `BlockSpec` then gets passed into your [BlockNote schema](/docs/features/custom-schemas#creating-your-own-schema) to add the block to the editor. This function may also take arbitrary options, which you can find out more about [below](/docs/features/custom-schemas/custom-blocks#block-config-options).
26+
27+
Let's look at our custom alert block from the demo, and go over everything we pass to `createReactBlockSpec`:
2528

2629
```typescript
27-
const Alert = createReactBlockSpec(
30+
const createAlert = createReactBlockSpec(
2831
{
2932
type: "alert",
3033
propSchema: {
@@ -54,8 +57,6 @@ type BlockConfig = {
5457
type: string;
5558
content: "inline" | "none";
5659
readonly propSchema: PropSchema;
57-
isSelectable?: boolean;
58-
hardBreakShortcut?: "shift+enter" | "enter" | "none";
5960
};
6061
```
6162

@@ -106,12 +107,6 @@ If you do not want the prop to have a default value, you can define it as an obj
106107
Properties](/docs/features/blocks#default-block-properties)._
107108
</Callout>
108109

109-
`isSelectable?:` Can be set to false in order to make the block non-selectable, both using the mouse and keyboard. This also helps with being able to select non-editable content within the block. Should only be set to false when `content` is `none` and defaults to true.
110-
111-
`hardBreakShortcut?:` Defines which keyboard shortcut should be used to insert a hard break into the block's inline content. Defaults to `"shift+enter"`.
112-
113-
#### File Block Config
114-
115110
### Block Implementation (`ReactCustomBlockImplementation`)
116111

117112
The Block Implementation defines how the block should be rendered in the editor, and how it should be parsed from and converted to HTML.
@@ -129,6 +124,15 @@ type ReactCustomBlockImplementation = {
129124
contentRef?: (node: HTMLElement | null) => void;
130125
}>;
131126
parse?: (element: HTMLElement) => PartialBlock["props"] | undefined;
127+
runsBefore?: string[];
128+
meta?: {
129+
hardBreakShortcut?: "shift+enter" | "enter" | "none";
130+
selectable?: boolean;
131+
fileBlockAccept?: string[];
132+
code?: boolean;
133+
defining?: boolean;
134+
isolating?: boolean;
135+
};
132136
};
133137
```
134138

@@ -152,6 +156,69 @@ type ReactCustomBlockImplementation = {
152156

153157
- `element`: The HTML element that's being parsed.
154158

159+
`runsBefore?:` If this block has parsing or extensions that need to be given priority over any other blocks, you can pass their `type`s in an array here.
160+
161+
`meta?:` An object for setting various generic properties of the block.
162+
163+
- `hardBreakShortcut?:` Defines which keyboard shortcut should be used to insert a hard break into the block's inline content. Defaults to `"shift+enter"`.
164+
165+
- `selectable?:` Can be set to false in order to make the block non-selectable, both using the mouse and keyboard. This also helps with being able to select non-editable content within the block. Should only be set to false when `content` is `none` and defaults to true.
166+
167+
- `fileBlockAccept?:` For custom file blocks, this specifies which MIME types are accepted when uploading a file. All file blocks should specify this property, and should use a [`FileBlockWrapper`](https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/blocks/File/helpers/render/FileBlockWrapper.tsx)/[`ResizableFileBlockWrapper`](https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/blocks/File/helpers/render/ResizableFileBlockWrapper.tsx) component in their `render` functions (see next subsection).
168+
169+
- `code?:` Whether this block contains [code](https://prosemirror.net/docs/ref/#model.NodeSpec.code).
170+
171+
- `defining?:` Whether this block is [defining](https://prosemirror.net/docs/ref/#model.NodeSpec.defining).
172+
173+
- `isolating?:` Whether this block is [isolating](https://prosemirror.net/docs/ref/#model.NodeSpec.isolating).
174+
175+
### Block Extensions
176+
177+
While the example on this page doesn't use it, `createReactBlockSpec` takes a third, optional argument `extensions`. This is for adding editor `extensions` that are specific to the block, which you can find out more about [here](/docs/features/extensions).
178+
179+
Block extensions are typically things like e.g. adding keyboard shortcuts to change the current block type to a custom block. For a table of contents block, an extension could also add a ProseMirror plugin to scan for headings to put in the ToC.
180+
181+
### Block Config Options
182+
183+
In some cases, you may want to have a customizable block config. For example, you may want to be able to have a code block with syntax highlighting for either web or embedded code, or a heading block with a flexible number of heading levels. You can use the same API for this use case, with some minor changes:
184+
185+
```typescript
186+
// Arbitrary options that your block can take, e.g. number of heading levels or
187+
// available code syntax highlight languages.
188+
type CustomBlockConfigOptions = {
189+
...
190+
}
191+
192+
const createCustomBlock = createReactBlockSpec(
193+
createBlockConfig((options: CustomBlockConfigOptions) => ({
194+
type: "customBlock"
195+
propSchema: ...,
196+
content: ...,
197+
})),
198+
(options: CustomBlockConfigOptions) => ({
199+
render: ...,
200+
...
201+
})
202+
)
203+
204+
const options: CustomBlockConfigOptions = {
205+
...
206+
};
207+
208+
const schema = BlockNoteSchema.create().extend({
209+
blockSpecs: {
210+
// Creates an instance of the custom block and adds it to the schema.
211+
customBlock: createCustomBlock(options),
212+
},
213+
});
214+
```
215+
216+
You can see that instead of passing plain objects for the config and implementation, we instead pass functions. These take the block options as an argument, and return the config and implementation objects respectively. Additionally, the function for creating the config is wrapped in a `createBlockConfig` function.
217+
218+
Also notice that for the example on this page, we create a new Alert block instance by simply calling `createAlert()` with no arguments. When a custom block takes options though, you can pass them in when creating an instance, as shown above.
219+
220+
To see a full example of block options being used, check out the [built-in heading block](https://github.com/TypeCellOS/BlockNote/blob/main/packages/core/src/blocks/Heading/block.ts).
221+
155222
## Adding Custom Blocks to the Editor
156223

157224
Finally, create a BlockNoteSchema using the definition of your custom blocks:
@@ -163,7 +230,7 @@ const schema = BlockNoteSchema.create({
163230
...defaultBlockSpecs,
164231

165232
// Add your own custom blocks:
166-
alert: Alert,
233+
alert: createAlert(),
167234
},
168235
});
169236
```

docs/content/docs/features/custom-schemas/custom-inline-content.mdx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ Use the `createReactInlineContentSpec` function to create a custom inline conten
1818
function createReactInlineContentSpec(
1919
blockConfig: CustomInlineContentConfig,
2020
blockImplementation: ReactInlineContentImplementation,
21-
);
21+
): InlineContentSpec;
2222
```
2323

24-
Let's look at our custom mentions tag from the demo, and go over each field to explain how it works:
24+
It returns an instance of your custom inline content, or an `InlineContentSpec`. This `InlineContentSpec` then gets passed into your [BlockNote schema](/docs/features/custom-schemas#creating-your-own-schema) to add the inline content to the editor.
25+
26+
Let's look at our custom mentions tag from the demo, and go over everything we pass to `createReactInlineContentSpec`:
2527

2628
```typescript
2729
const Mention = createReactInlineContentSpec(
@@ -59,8 +61,7 @@ type CustomInlineContentConfig = {
5961
`content:` `styled` if your custom inline content should contain [`StyledText`](/docs/foundations/document-structure#inline-content-objects), `none` if not.
6062

6163
<Callout type="info">
62-
_In the mentions demo, we want each mention to be a single, non-editable
63-
element, so we set `content` to `"none"`._
64+
_In the mentions demo, we want each mention to be a single, non-editable element, so we set `content` to `"none"`._
6465
</Callout>
6566

6667
`propSchema:` The `PropSchema` specifies the props that the inline content supports. Inline content props (properties) are data stored with your inline content in the document, and can be used to customize its appearance or behavior.
@@ -95,8 +96,7 @@ If you do not want the prop to have a default value, you can define it as an obj
9596
- `values?:` Specifies an array of values that the prop can take, for example, to limit the value to a list of pre-defined strings. If `values` is not defined, BlockNote assumes the prop can be any value of `PrimitiveType`.
9697

9798
<Callout type="info">
98-
_In the mentions demo, we add a `user` prop for the user that's being
99-
mentioned._
99+
_In the mentions demo, we add a `user` prop for the user that's being mentioned._
100100
</Callout>
101101

102102
### Inline Content Implementation (`ReactCustomInlineContentImplementation`)
@@ -110,9 +110,18 @@ type ReactCustomInlineContentImplementation = {
110110
};
111111
render: React.FC<{
112112
inlineContent: InlineContent;
113+
editor: BlockNoteEditor;
114+
contentRef?: (node: HTMLElement | null) => void;
115+
}>;
116+
toExternalHTML?: React.FC<{
117+
inlineContent: InlineContent;
118+
editor: BlockNoteEditor;
113119
contentRef?: (node: HTMLElement | null) => void;
114-
draggable?: boolean;
115120
}>;
121+
parse?: (element: HTMLElement) => PartialInlineContent["props"] | undefined;
122+
meta?: {
123+
draggable?: boolean;
124+
};
116125
};
117126
```
118127

@@ -124,11 +133,20 @@ type ReactCustomInlineContentImplementation = {
124133

125134
- `draggable:` Specifies whether the inline content can be dragged within the editor. If set to `true`, the inline content will be draggable. Defaults to `false` if not specified. If this is true, you should add `data-drag-handle` to the DOM element that should function as the drag handle.
126135

136+
`toExternalHTML?:` This component is used whenever the inline content is being exported to HTML for use outside BlockNote, for example when copying it to the clipboard. If it's not defined, BlockNote will just use `render` for the HTML conversion. Takes the same props as `render`.
137+
138+
<Callout type="info">
139+
_Note that your component passed to `toExternalHTML` is rendered and serialized in a separate React root, which means you can't use hooks that rely on React Contexts._
140+
</Callout>
141+
142+
`parse?:` The `parse` function defines how to parse HTML content into your inline content, for example when pasting contents from the clipboard. If the element should be parsed into your custom inline content, you return the props that the block should be given. Otherwise, return `undefined`. Takes a single argument:
143+
144+
- `element`: The HTML element that's being parsed.
145+
127146
`meta?.draggable?:` Whether the inline content should be draggable.
128147

129148
<Callout type="info">
130-
_Note that since inline content is, by definition, inline, your component
131-
should also return an HTML inline element._
149+
_Note that since inline content is, by definition, inline, your component should also return an HTML inline element._
132150
</Callout>
133151

134152
## Adding Custom Inline Content to the Editor

docs/content/docs/features/custom-schemas/custom-styles.mdx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ Use the `createReactStyleSpec` function to create a custom style type. This func
1818
function createReactStyleSpec(
1919
styleConfig: CustomStyleConfig,
2020
styleImplementation: ReactStyleImplementation,
21-
);
21+
): StyleSpec;
2222
```
2323

24-
Let's look at our custom font style from the demo, and go over each field to explain how it works:
24+
It returns an instance of your custom inline content, or a `StyleSpec`. This `StyleSpec` then gets passed into your [BlockNote schema](/docs/features/custom-schemas#creating-your-own-schema) to add the style to the editor.
25+
26+
Let's look at our custom font style from the demo, and go over everything we pass to `createReactStyleSpec`:
2527

2628
```typescript
2729
export const Font = createReactStyleSpec(
@@ -67,6 +69,11 @@ type ReactCustomStyleImplementation = {
6769
value?: string;
6870
contentRef: (node: HTMLElement | null) => void;
6971
}>;
72+
toExternalHTML?: React.FC<{
73+
value?: string;
74+
contentRef: (node: HTMLElement | null) => void;
75+
}>;
76+
parse?: (element: HTMLElement) => string | true | undefined;
7077
};
7178
```
7279

@@ -76,12 +83,18 @@ type ReactCustomStyleImplementation = {
7683

7784
- `contentRef:` A React `ref` to mark the editable element.
7885

86+
`toExternalHTML?:` This component is used whenever the style is being exported to HTML for use outside BlockNote, for example when copying it to the clipboard. If it's not defined, BlockNote will just use `render` for the HTML conversion. Takes the same props as `render`.
87+
7988
<Callout type="info">
80-
_Note that in contrast to Custom Blocks and Inline Content, the `render`
81-
function of Custom Styles cannot access React Context or other state. They
82-
should be plain React functions analogous to the example._
89+
_Note that your component passed to `toExternalHTML` is rendered and
90+
serialized in a separate React root, which means you can't use hooks that rely
91+
on React Contexts._
8392
</Callout>
8493

94+
`parse?:` The `parse` function defines how to parse HTML content into your style, for example when pasting contents from the clipboard. If the element should be parsed into your custom style, you return a `string` or `true`. If the `propSchema` is `"string"`, you should likewise return a string value, or `true` otherwise. Returning `undefined` will not parse the style from the HTML element. Takes a single argument:
95+
96+
- `element`: The HTML element that's being parsed.
97+
8598
## Adding Custom Style to the Editor
8699

87100
Finally, create a BlockNoteSchema using the definition of your custom style:

docs/content/docs/features/custom-schemas/index.mdx

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,35 +28,72 @@ Text Styles are properties that can be applied to a piece of text, such as bold,
2828

2929
## Creating your own schema
3030

31-
Once you have defined your custom blocks (see the links above), inline content or styles, you can create a schema and pass this to the initialization of the editor.
31+
Once you have defined your custom blocks (see the links above), inline content or styles, you can create a schema and pass this to the initialization of the editor. There are two ways to create a new schema.
32+
33+
### Extending an existing schema
34+
35+
You can call `BlockNoteSchema.extend` to add custom blocks, inline content, or styles to an existing schema. While this works for any existing schema, it's most common to use this to extend the default schema.
36+
37+
```typescript
38+
// Creates an instance of the default schema when nothing is passed to
39+
// `BlockNoteSchema.create`.
40+
const schema = BlockNoteSchema.create()
41+
// Adds custom blocks, inline content, or styles to the default schema.
42+
.extend({
43+
blockSpecs: {
44+
// Add your own custom blocks:
45+
customBlock: CustomBlock,
46+
...
47+
},
48+
inlineContentSpecs: {
49+
// Add your own custom inline content:
50+
customInlineContent: CustomInlineContent,
51+
...
52+
},
53+
styleSpecs: {
54+
// Add your own custom styles:
55+
customStyle: CustomStyle,
56+
...
57+
},
58+
});
59+
```
60+
61+
### Creating a schema from scratch
62+
63+
Passing custom blocks, inline content, or styles directly into `BlockNoteSchema.create` will produce a new schema with only the things you pass. This can be useful if you only need a few basic things from the default schema, and intend to implement everything else yourself.
3264

3365
```typescript
3466
const schema = BlockNoteSchema.create({
3567
blockSpecs: {
36-
// enable the default blocks if desired
37-
...defaultBlockSpecs,
68+
// Add only the default paragraph block:
69+
paragraph: defaultBlockSpecs.paragraph,
3870

3971
// Add your own custom blocks:
40-
// customBlock: CustomBlock,
72+
customBlock: CustomBlock,
73+
...
4174
},
4275
inlineContentSpecs: {
43-
// enable the default inline content if desired
44-
...defaultInlineContentSpecs,
76+
// Add only the default text inline content:
77+
text: defaultInlineContentSpecs.text,
4578

4679
// Add your own custom inline content:
47-
// customInlineContent: CustomInlineContent,
80+
customInlineContent: CustomInlineContent,
81+
...
4882
},
4983
styleSpecs: {
50-
// enable the default styles if desired
51-
...defaultStyleSpecs,
84+
// Add only the default bold style:
85+
bold: defaultStyleSpecs.bold,
5286

5387
// Add your own custom styles:
54-
// customStyle: CustomStyle
88+
customStyle: CustomStyle,
89+
...
5590
},
5691
});
5792
```
5893

59-
You can then pass this to the instantiation of your BlockNoteEditor (`BlockNoteEditor.create` or `useCreateBlockNote`):
94+
## Using your own schema
95+
96+
Once you've created an instance of your schema using `BlockNoteSchema.create` or `BlockNoteSchema.extend`, you can pass it to the `schema` option of your BlockNoteEditor (`BlockNoteEditor.create` or `useCreateBlockNote`):
6097

6198
```typescript
6299
const editor = useCreateBlockNote({

0 commit comments

Comments
 (0)