Skip to content

Commit 79eaf77

Browse files
Documentation Demos (#120)
* Added new text cursor fields & functions docs * Made website and JS docs have same information + minor changes/fixes * More minor changes & fixes * More minor changes & fixes * Added block structure diagram * remove margin from editor (this is now built-in) * Added PR feedback and cleaned up links * Added most live examples * Cleaned up event listeners * Made `BNUpdateBlock` also apply props to `blockContainer` node * Added text cursor demo and fixed listeners for others * Fixed build error * Fixed build error --------- Co-authored-by: yousefed <[email protected]>
1 parent d4cd37a commit 79eaf77

File tree

10 files changed

+280
-58
lines changed

10 files changed

+280
-58
lines changed

examples/editor/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ type WindowWithProseMirror = Window & typeof globalThis & { ProseMirror: any };
77

88
function App() {
99
const editor = useBlockNote({
10-
onUpdate: (editor) => {
10+
onEditorContentChange: (editor) => {
1111
console.log(editor.topLevelBlocks);
1212
},
1313
editorDOMAttributes: {

examples/vanilla/src/main.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const editor = new BlockNoteEditor({
1717
// Create an example menu for when a block is hovered
1818
blockSideMenuFactory,
1919
},
20-
onUpdate: () => {
20+
onEditorContentChange: () => {
2121
console.log(editor.topLevelBlocks);
2222
},
2323
editorDOMAttributes: {

packages/core/src/BlockNoteEditor.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ export type BlockNoteEditorOptions = {
3838
slashCommands: BaseSlashMenuItem[];
3939
parentElement: HTMLElement;
4040
editorDOMAttributes: Record<string, string>;
41-
onUpdate: (editor: BlockNoteEditor) => void;
42-
onCreate: (editor: BlockNoteEditor) => void;
41+
onEditorCreate: (editor: BlockNoteEditor) => void;
42+
onEditorContentChange: (editor: BlockNoteEditor) => void;
43+
onTextCursorPositionChange: (editor: BlockNoteEditor) => void;
4344

4445
// tiptap options, undocumented
4546
_tiptapOptions: any;
@@ -73,11 +74,14 @@ export class BlockNoteEditor {
7374
const tiptapOptions: EditorOptions = {
7475
...blockNoteTipTapOptions,
7576
...options._tiptapOptions,
77+
onCreate: () => {
78+
options.onEditorCreate?.(this);
79+
},
7680
onUpdate: () => {
77-
options.onUpdate?.(this);
81+
options.onEditorContentChange?.(this);
7882
},
79-
onCreate: () => {
80-
options.onCreate?.(this);
83+
onSelectionUpdate: () => {
84+
options.onTextCursorPositionChange?.(this);
8185
},
8286
extensions:
8387
options.enableBlockNoteExtensions === false
@@ -291,14 +295,6 @@ export class BlockNoteEditor {
291295
replaceBlocks(blocksToRemove, blocksToInsert, this._tiptapEditor);
292296
}
293297

294-
/**
295-
* Executes a callback function whenever the editor's content changes.
296-
* @param callback The callback function to execute.
297-
*/
298-
public onContentChange(callback: () => void) {
299-
this._tiptapEditor.on("update", callback);
300-
}
301-
302298
/**
303299
* Serializes blocks into an HTML string. To better conform to HTML standards, children of blocks which aren't list
304300
* items are un-nested in the output HTML.

packages/core/src/extensions/Blocks/nodes/BlockContainer.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ export const BlockContainer = Node.create<IBlock>({
187187
);
188188
}
189189

190-
// Changes the block type and adds the provided props as node attributes. Also preserves all existing node
190+
// Changes the blockContent node type and adds the provided props as attributes. Also preserves all existing
191191
// attributes that are compatible with the new type.
192192
state.tr.setNodeMarkup(
193193
startPos,
@@ -199,6 +199,13 @@ export const BlockContainer = Node.create<IBlock>({
199199
...block.props,
200200
}
201201
);
202+
203+
// Adds all provided props as attributes to the parent blockContainer node too, and also preserves existing
204+
// attributes.
205+
state.tr.setNodeMarkup(startPos - 1, undefined, {
206+
...node.attrs,
207+
...block.props,
208+
});
202209
}
203210

204211
return true;

packages/website/docs/docs/blocks.md

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,32 +59,33 @@ type Block = {
5959

6060
Now that we know how blocks are represented in code, let's take a look at the live example below. We have a BlockNote editor, under which we display its contents using an array of `Block` objects. Feel free to play around and get a better feel for how blocks look in the editor, compared to how they're represented using `Block` objects.
6161

62-
**TODO** Live example.
63-
6462
::: sandbox {template=react-ts}
6563

6664
```typescript /App.tsx
6765
import { useState } from "react";
66+
import { BlockNoteEditor } from "@blocknote/core";
6867
import { BlockNoteView, useBlockNote } from "@blocknote/react";
6968
import "@blocknote/core/style.css";
70-
import "./styles.css";
7169

7270
export default function App() {
73-
const [editorAPI, setEditorAPI] = useState(null);
74-
const [blocks, setBlocks] = useState(null);
75-
71+
// Stores the editor's contents as an array of Block objects.
72+
const [blocks, setBlocks] = useState<Block[] | null>(null);
73+
7674
// Creates a new editor instance.
77-
const editor = useBlockNote({});
78-
79-
// Saves the editor's contents as Block objects.
80-
editor.onContentChange(() => setBlocks(editor.allBlocks));
81-
82-
// Renders the editor instance using a React component.
75+
const editor: BlockNoteEditor | null = useBlockNote({
76+
// Listens for when the editor's contents change.
77+
onEditorContentChange: (editor: BlockNoteEditor) =>
78+
// Converts the editor's contents to an array of Block objects.
79+
setBlocks(editor.topLevelBlocks)
80+
})
81+
82+
// Renders a BlockNote editor, and its contents as an array of Block objects
83+
// below.
8384
return (
84-
<>
85-
<BlockNoteView editor={editor} />
86-
<div>{blocks}</div>
87-
</>
85+
<div>
86+
<BlockNoteView editor={editor}/>
87+
<pre>{JSON.stringify(blocks, null, 2)}</pre>
88+
</div>
8889
);
8990
}
9091
```

packages/website/docs/docs/converting-blocks.md

Lines changed: 175 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,46 @@ const HTMLFromBlocks = editor.blocksToHTML(blocks);
3232

3333
To better conform to HTML standards, children of blocks which aren't list items are un-nested in the output HTML.
3434

35-
**Example**
35+
**Demo**
36+
37+
::: sandbox {template=react-ts}
38+
39+
```typescript /App.tsx
40+
import { useState } from "react";
41+
import { BlockNoteEditor } from "@blocknote/core";
42+
import { BlockNoteView, useBlockNote } from "@blocknote/react";
43+
import "@blocknote/core/style.css";
44+
45+
export default function App() {
46+
// Stores the editor's contents as HTML.
47+
const [html, setHTML] = useState<string | null>(null);
48+
49+
// Creates a new editor instance.
50+
const editor = useBlockNote({
51+
// Listens for when the editor's contents change.
52+
onEditorContentChange: (editor: BlockNoteEditor) => {
53+
// Converts the editor's contents from Block objects to HTML and saves
54+
// them.
55+
const saveBlocksAsHTML = async () => {
56+
const html = await editor.blocksToHTML(editor.topLevelBlocks);
57+
setHTML(html);
58+
};
59+
saveBlocksAsHTML();
60+
}
61+
});
62+
63+
// Renders a BlockNote editor, and its contents as HTML below.
64+
return (
65+
<div>
66+
<BlockNoteView editor={editor} />
67+
<pre style={{ whiteSpace: "pre-wrap" }}>{html}</pre>
68+
</div>
69+
);
70+
}
71+
```
3672

37-
TODO
73+
74+
:::
3875

3976
### Converting HTML to Blocks
4077

@@ -56,9 +93,56 @@ const blocksFromHTML = editor.HTMLToBlocks(html);
5693

5794
Tries to create `Block` objects out of any HTML block-level elements, and `InlineNode` 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.
5895

59-
**Example**
96+
**Demo**
97+
98+
::: sandbox {template=react-ts}
99+
100+
```typescript /App.tsx
101+
import { useEffect, useState } from "react";
102+
import { BlockNoteEditor } from "@blocknote/core";
103+
import { BlockNoteView, useBlockNote } from "@blocknote/react";
104+
import "@blocknote/core/style.css";
105+
106+
export default function App() {
107+
// Creates a new editor instance.
108+
const editor: BlockNoteEditor | null = useBlockNote({
109+
// Makes the editor non-editable.
110+
_tiptapOptions: {
111+
editable: false,
112+
},
113+
})
114+
115+
// Stores the current HTML content.
116+
const [html, setHTML] = useState<string>("");
117+
118+
useEffect(() => {
119+
if (editor) {
120+
// Whenever the current HTML content changes, converts it to an array of
121+
// Block objects and replaces the editor's content with them.
122+
const getBlocks = async () => {
123+
const blocks = await editor.HTMLToBlocks(html);
124+
editor.replaceBlocks(editor.topLevelBlocks, blocks);
125+
};
126+
getBlocks();
127+
}
128+
}, [editor, html]);
129+
130+
// Renders a text area for you to write/paste HTML in and a BlockNote editor
131+
// below, which displays the current HTML as blocks.
132+
return (
133+
<div>
134+
<textarea
135+
style={{ width: "100%", height: "100px" }}
136+
value={html}
137+
onChange={(event) => setHTML(event.target.value)}
138+
/>
139+
<BlockNoteView editor={editor} />
140+
</div>
141+
);
142+
}
143+
```
60144

61-
TODO
145+
:::
62146

63147
## Markdown
64148

@@ -84,9 +168,45 @@ const markdownFromBlocks = editor.blocksToMarkdown(blocks);
84168

85169
The output is simplified as Markdown does not support all features of BlockNote - children of blocks which aren't list items are un-nested and certain styles are removed.
86170

87-
**Example**
171+
**Demo**
172+
173+
::: sandbox {template=react-ts}
174+
175+
```typescript /App.tsx
176+
import { useState } from "react";
177+
import { BlockNoteEditor } from "@blocknote/core";
178+
import { BlockNoteView, useBlockNote } from "@blocknote/react";
179+
import "@blocknote/core/style.css";
180+
181+
export default function App() {
182+
// Stores the editor's contents as Markdown.
183+
const [markdown, setMarkdown] = useState<string | null>(null);
184+
185+
// Creates a new editor instance.
186+
const editor = useBlockNote({
187+
// Listens for when the editor's contents change.
188+
onEditorContentChange: (editor: BlockNoteEditor) => {
189+
// Converts the editor's contents from Block objects to Markdown and
190+
// saves them.
191+
const saveBlocksAsMarkdown = async () => {
192+
const markdown = await editor.blocksToMarkdown(editor.topLevelBlocks);
193+
setMarkdown(markdown);
194+
};
195+
saveBlocksAsMarkdown();
196+
}
197+
});
198+
199+
// Renders a BlockNote editor, and its contents as Markdown below.
200+
return (
201+
<div>
202+
<BlockNoteView editor={editor} />
203+
<pre style={{ whiteSpace: "pre-wrap" }}>{markdown}</pre>
204+
</div>
205+
);
206+
}
207+
```
88208

89-
TODO
209+
:::
90210

91211
### Converting Markdown to Blocks
92212

@@ -108,6 +228,53 @@ const blocksFromMarkdown = editor.markdownToBlocks(markdown);
108228

109229
Tries to create `Block` and `InlineNode` objects based on Markdown syntax, though not all symbols are recognized. If BlockNote doesn't recognize a symbol, it will parse it as text.
110230

111-
**Example**
231+
**Demo**
232+
233+
::: sandbox {template=react-ts}
234+
235+
```typescript /App.tsx
236+
import { useEffect, useState } from "react";
237+
import { BlockNoteEditor } from "@blocknote/core";
238+
import { BlockNoteView, useBlockNote } from "@blocknote/react";
239+
import "@blocknote/core/style.css";
240+
241+
export default function App() {
242+
// Creates a new editor instance.
243+
const editor: BlockNoteEditor | null = useBlockNote({
244+
// Makes the editor non-editable.
245+
_tiptapOptions: {
246+
editable: false,
247+
},
248+
})
249+
250+
// Stores the current Markdown content.
251+
const [markdown, setMarkdown] = useState<string>("");
252+
253+
useEffect(() => {
254+
if (editor) {
255+
// Whenever the current Markdown content changes, converts it to an array
256+
// of Block objects and replaces the editor's content with them.
257+
const getBlocks = async () => {
258+
const blocks = await editor.markdownToBlocks(markdown);
259+
editor.replaceBlocks(editor.topLevelBlocks, blocks);
260+
};
261+
getBlocks();
262+
}
263+
}, [editor, markdown]);
264+
265+
// Renders a text area for you to write/paste Markdown in and a BlockNote
266+
// editor below, which displays the current Markdown as blocks.
267+
return (
268+
<div>
269+
<textarea
270+
style={{ width: "100%", height: "100px" }}
271+
value={markdown}
272+
onChange={(event) => setMarkdown(event.target.value)}
273+
/>
274+
<BlockNoteView editor={editor} />
275+
</div>
276+
);
277+
}
278+
```
112279

113-
TODO
280+
:::

0 commit comments

Comments
 (0)