Skip to content

Commit 72ea879

Browse files
authored
Update docs (#38)
* update docs * update docs * add doc * disable cla * small readme fixes
1 parent 8a315dd commit 72ea879

36 files changed

+639
-257
lines changed
File renamed without changes.

LICENSE

+373
Large diffs are not rendered by default.

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ blocknote
7373
└── tests - Playwright end to end tests
7474
```
7575

76+
An introduction into the BlockNote Prosemirror schema can be found in [packages/core/ARCHITECTURE.md](https://github.com/YousefED/BlockNote/blob/main/packages/core/ARCHITECTURE.md).
77+
7678
## Running
7779

7880
To run the project, open the command line in the project's root directory and enter the following commands:

packages/core/ARCHITECTURE.md

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Node structure
2+
3+
We use a Prosemirror document structure where every element is a `block` with 1 `content` element and one optional group of children (`blockgroup`).
4+
5+
- A `block` can only appear in a `blockgroup` (which is also the type of the root node)
6+
- Every `block` element can have attributes (e.g.: is it a heading or a list item)
7+
- Every `block` element can contain a `blockgroup` as second child. In this case the `blockgroup` is considered nested (indented in the UX)
8+
9+
This architecture is different from the "default" Prosemirror / Tiptap implementation which would use more semantic HTML node types (`p`, `li`, etc.). We have designed this block structure instead to more easily:
10+
11+
- support indentation of any node (without complex wrapping logic)
12+
- supporting animations (nodes stay the same type, only attrs are changed)
13+
14+
## Example
15+
16+
```xml
17+
<blockgroup>
18+
<block>
19+
<content>Parent element 1</content>
20+
<blockgroup>
21+
<block>
22+
<content>Nested / child / indented item</content>
23+
</block>
24+
</blockgroup>
25+
</block>
26+
<block>
27+
<content>Parent element 2</content>
28+
<blockgroup>
29+
<block>...</block>
30+
<block>...</block>
31+
</blockgroup>
32+
</block>
33+
<block>
34+
<content>Element 3 without children</content>
35+
</block>
36+
</blockgroup>
37+
```

packages/core/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@blocknote/core",
33
"homepage": "https://github.com/yousefed/blocknote",
44
"private": false,
5-
"license": "MIT",
5+
"license": "MPL-2.0",
66
"version": "0.1.0-alpha.3",
77
"files": [
88
"dist",

packages/core/src/BlockNoteExtensions.ts

+10-17
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { Extensions, extensions } from "@tiptap/core";
22

3+
import { Node } from "@tiptap/core";
34
import Bold from "@tiptap/extension-bold";
45
import Code from "@tiptap/extension-code";
56
import DropCursor from "@tiptap/extension-dropcursor";
67
import GapCursor from "@tiptap/extension-gapcursor";
78
import HardBreak from "@tiptap/extension-hard-break";
8-
import Italic from "@tiptap/extension-italic";
9-
import Underline from "@tiptap/extension-underline";
10-
// import Placeholder from "@tiptap/extension-placeholder";
11-
import { Node } from "@tiptap/core";
129
import { History } from "@tiptap/extension-history";
10+
import Italic from "@tiptap/extension-italic";
1311
import Strike from "@tiptap/extension-strike";
1412
import Text from "@tiptap/extension-text";
13+
import Underline from "@tiptap/extension-underline";
1514
import { blocks } from "./extensions/Blocks";
1615
import blockStyles from "./extensions/Blocks/nodes/Block.module.css";
1716
import { BubbleMenuExtension } from "./extensions/BubbleMenu/BubbleMenuExtension";
@@ -22,12 +21,16 @@ import { Placeholder } from "./extensions/Placeholder/PlaceholderExtension";
2221
import SlashMenuExtension from "./extensions/SlashMenu";
2322
import { TrailingNode } from "./extensions/TrailingNode/TrailingNodeExtension";
2423
import UniqueID from "./extensions/UniqueID/UniqueID";
24+
2525
export const Document = Node.create({
2626
name: "doc",
2727
topNode: true,
2828
content: "block+",
2929
});
3030

31+
/**
32+
* Get all the Tiptap extensions BlockNote is configured with by default
33+
*/
3134
export const getBlockNoteExtensions = () => {
3235
const ret: Extensions = [
3336
extensions.ClipboardTextSerializer,
@@ -48,7 +51,7 @@ export const getBlockNoteExtensions = () => {
4851
showOnlyCurrent: false,
4952
}),
5053
UniqueID.configure({
51-
types: ["tcblock"],
54+
types: ["block"],
5255
}),
5356
HardBreak,
5457
// Comments,
@@ -64,26 +67,16 @@ export const getBlockNoteExtensions = () => {
6467
Underline,
6568
HyperlinkMark,
6669
FixedParagraph,
70+
6771
// custom blocks:
6872
...blocks,
6973
DraggableBlocksExtension,
7074
DropCursor.configure({ width: 5, color: "#ddeeff" }),
7175
BubbleMenuExtension,
7276
History,
73-
SlashMenuExtension,
7477
// This needs to be at the bottom of this list, because Key events (such as enter, when selecting a /command),
7578
// should be handled before Enter handlers in other components like splitListItem
76-
// SlashCommandExtension.configure({
77-
// // Extra commands can be registered here
78-
// commands: {},
79-
// }),
80-
// MentionsExtension.configure({
81-
// providers: {
82-
// people: (query) => {
83-
// return PEOPLE.filter((mention) => mention.match(query));
84-
// },
85-
// },
86-
// }),
79+
SlashMenuExtension,
8780
TrailingNode,
8881
];
8982
return ret;

packages/core/src/EditorContent.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export { EditorContent } from "@tiptap/react";
1+
// BlockNote uses a similar pattern as Tiptap, so for now we can just export that
2+
export { EditorContent } from "@tiptap/react";

packages/core/src/extensions/Blocks/OrderedListPlugin.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ export const OrderedListPlugin = () => {
1111
let count = 1;
1212
let skip = 0;
1313
newState.doc.descendants((node, pos) => {
14-
if (node.type.name === "tcblock" && !node.attrs.listType) {
14+
if (node.type.name === "block" && !node.attrs.listType) {
1515
count = 1;
1616
}
1717
if (
1818
skip === 0 &&
19-
node.type.name === "tcblock" &&
19+
node.type.name === "block" &&
2020
node.attrs.listType === "oli"
2121
) {
2222
skip = node.content.childCount;
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import { findParentNode } from "@tiptap/core";
22

3-
export const findBlock = findParentNode((node) => node.type.name === "tcblock");
3+
export const findBlock = findParentNode((node) => node.type.name === "block");

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

+12-13
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ declare module "@tiptap/core" {
4343
* The main "Block node" documents consist of
4444
*/
4545
export const Block = Node.create<IBlock>({
46-
name: "tcblock",
46+
name: "block",
4747
group: "block",
4848
addOptions() {
4949
return {
@@ -52,7 +52,7 @@ export const Block = Node.create<IBlock>({
5252
},
5353

5454
// A block always contains content, and optionally a blockGroup which contains nested blocks
55-
content: "tccontent blockgroup?",
55+
content: "content blockgroup?",
5656

5757
defining: true,
5858

@@ -172,7 +172,7 @@ export const Block = Node.create<IBlock>({
172172
const nodePos = tr.selection.$anchor.posAtIndex(0, -1) - 1;
173173

174174
// const node2 = tr.doc.nodeAt(nodePos);
175-
if (node.type.name === "tcblock" && node.attrs["listType"]) {
175+
if (node.type.name === "block" && node.attrs["listType"]) {
176176
if (dispatch) {
177177
tr.setNodeMarkup(nodePos, undefined, {
178178
...node.attrs,
@@ -203,8 +203,7 @@ export const Block = Node.create<IBlock>({
203203

204204
// Create new block after current block
205205
const endOfBlock = currentBlock.pos + currentBlock.node.nodeSize;
206-
let newBlock =
207-
state.schema.nodes["tcblock"].createAndFill(attributes)!;
206+
let newBlock = state.schema.nodes["block"].createAndFill(attributes)!;
208207
if (dispatch) {
209208
tr.insert(endOfBlock, newBlock);
210209
tr.setSelection(new TextSelection(tr.doc.resolve(endOfBlock + 1)));
@@ -218,7 +217,7 @@ export const Block = Node.create<IBlock>({
218217
const nodePos = tr.selection.$anchor.posAtIndex(0, -1) - 1;
219218

220219
// const node2 = tr.doc.nodeAt(nodePos);
221-
if (node.type.name === "tcblock") {
220+
if (node.type.name === "block") {
222221
if (dispatch) {
223222
tr.setNodeMarkup(nodePos, undefined, {
224223
...node.attrs,
@@ -268,11 +267,11 @@ export const Block = Node.create<IBlock>({
268267
commands.command(({ tr }) => {
269268
const isAtStartOfNode = tr.selection.$anchor.parentOffset === 0;
270269
const node = tr.selection.$anchor.node(-1);
271-
if (isAtStartOfNode && node.type.name === "tcblock") {
270+
if (isAtStartOfNode && node.type.name === "block") {
272271
// we're at the start of the block, so we're trying to "backspace" the bullet or indentation
273272
return commands.first([
274273
() => commands.unsetList(), // first try to remove the "list" property
275-
() => commands.liftListItem("tcblock"), // then try to remove a level of indentation
274+
() => commands.liftListItem("block"), // then try to remove a level of indentation
276275
]);
277276
}
278277
return false;
@@ -292,7 +291,7 @@ export const Block = Node.create<IBlock>({
292291
const isAtStartOfNode = tr.selection.$anchor.parentOffset === 0;
293292
const anchor = tr.selection.$anchor;
294293
const node = anchor.node(-1);
295-
if (isAtStartOfNode && node.type.name === "tcblock") {
294+
if (isAtStartOfNode && node.type.name === "block") {
296295
if (node.childCount === 2) {
297296
// BlockB has children. We want to go from this:
298297
//
@@ -337,7 +336,7 @@ export const Block = Node.create<IBlock>({
337336
const handleEnter = () =>
338337
this.editor.commands.first(({ commands }) => [
339338
// Try to split the current block into 2 items:
340-
() => commands.splitListItem("tcblock"),
339+
() => commands.splitListItem("block"),
341340
// Otherwise, maybe we are in an empty list item. "Enter" should remove the list bullet
342341
({ tr, dispatch }) => {
343342
const $from = tr.selection.$from;
@@ -348,7 +347,7 @@ export const Block = Node.create<IBlock>({
348347
const node = tr.selection.$anchor.node(-1);
349348
const nodePos = tr.selection.$anchor.posAtIndex(0, -1) - 1;
350349

351-
if (node.type.name === "tcblock" && node.attrs["listType"]) {
350+
if (node.type.name === "block" && node.attrs["listType"]) {
352351
if (dispatch) {
353352
tr.setNodeMarkup(nodePos, undefined, {
354353
...node.attrs,
@@ -373,9 +372,9 @@ export const Block = Node.create<IBlock>({
373372
return {
374373
Backspace: handleBackspace,
375374
Enter: handleEnter,
376-
Tab: () => this.editor.commands.sinkListItem("tcblock"),
375+
Tab: () => this.editor.commands.sinkListItem("block"),
377376
"Shift-Tab": () => {
378-
return this.editor.commands.liftListItem("tcblock");
377+
return this.editor.commands.liftListItem("block");
379378
},
380379
"Mod-Alt-0": () =>
381380
this.editor.chain().unsetList().unsetBlockHeading().run(),

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const BlockGroup = Node.create({
1010
};
1111
},
1212

13-
content: "tcblock+",
13+
content: "block+",
1414

1515
parseHTML() {
1616
return [{ tag: "div" }];

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export interface IBlock {
55
}
66

77
export const ContentBlock = Node.create<IBlock>({
8-
name: "tccontent",
8+
name: "content",
99

1010
addOptions() {
1111
return {

packages/core/src/extensions/Blocks/nodes/README.md

-26
This file was deleted.

packages/core/src/extensions/BubbleMenu/BubbleMenuExtension.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Extension } from "@tiptap/core";
22
import { PluginKey } from "prosemirror-state";
33
import ReactDOM from "react-dom";
4+
import rootStyles from "../../root.module.css";
45
import { createBubbleMenuPlugin } from "./BubbleMenuPlugin";
56
import { BubbleMenu } from "./component/BubbleMenu";
6-
import rootStyles from "../../root.module.css";
7+
78
/**
89
* The menu that is displayed when selecting a piece of text.
910
*/

packages/core/src/extensions/BubbleMenu/component/BubbleMenu.tsx

+10-14
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1+
import DropdownMenu, { DropdownItemGroup } from "@atlaskit/dropdown-menu";
12
import { Editor } from "@tiptap/core";
23
import {
34
RiBold,
45
RiH1,
56
RiH2,
67
RiH3,
8+
RiIndentDecrease,
9+
RiIndentIncrease,
710
RiItalic,
811
RiLink,
9-
RiStrikethrough,
10-
RiUnderline,
11-
RiIndentIncrease,
12-
RiIndentDecrease,
13-
RiText,
1412
RiListOrdered,
1513
RiListUnordered,
14+
RiStrikethrough,
15+
RiText,
16+
RiUnderline,
1617
} from "react-icons/ri";
1718
import { SimpleToolbarButton } from "../../../shared/components/toolbar/SimpleToolbarButton";
1819
import { Toolbar } from "../../../shared/components/toolbar/Toolbar";
1920
import { useEditorForceUpdate } from "../../../shared/hooks/useEditorForceUpdate";
2021
import { findBlock } from "../../Blocks/helpers/findBlock";
2122
import formatKeyboardShortcut from "../../helpers/formatKeyboardShortcut";
22-
import LinkToolbarButton from "./LinkToolbarButton";
23-
import DropdownMenu, { DropdownItemGroup } from "@atlaskit/dropdown-menu";
2423
import DropdownBlockItem from "./DropdownBlockItem";
24+
import LinkToolbarButton from "./LinkToolbarButton";
2525

2626
type ListType = "li" | "oli";
2727

@@ -167,19 +167,15 @@ export const BubbleMenu = (props: { editor: Editor }) => {
167167
icon={RiStrikethrough}
168168
/>
169169
<SimpleToolbarButton
170-
onClick={() =>
171-
props.editor.chain().focus().sinkListItem("tcblock").run()
172-
}
173-
isDisabled={!props.editor.can().sinkListItem("tcblock")}
170+
onClick={() => props.editor.chain().focus().sinkListItem("block").run()}
171+
isDisabled={!props.editor.can().sinkListItem("block")}
174172
mainTooltip="Indent"
175173
secondaryTooltip={formatKeyboardShortcut("Tab")}
176174
icon={RiIndentIncrease}
177175
/>
178176

179177
<SimpleToolbarButton
180-
onClick={() =>
181-
props.editor.chain().focus().liftListItem("tcblock").run()
182-
}
178+
onClick={() => props.editor.chain().focus().liftListItem("block").run()}
183179
isDisabled={
184180
!props.editor.can().command(({ state }) => {
185181
const block = findBlock(state.selection);

packages/core/src/extensions/DraggableBlocks/components/DragHandle.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,7 @@ export const DragHandle = (props: {
5959
if (currentBlock.node.firstChild?.textContent.length !== 0) {
6060
// Create new block after current block
6161
const endOfBlock = currentBlock.pos + currentBlock.node.nodeSize;
62-
let newBlock =
63-
props.view.state.schema.nodes["tccontent"].createAndFill()!;
62+
let newBlock = props.view.state.schema.nodes["content"].createAndFill()!;
6463
props.view.state.tr.insert(endOfBlock, newBlock);
6564
props.view.dispatch(props.view.state.tr.insert(endOfBlock, newBlock));
6665
props.view.dispatch(

0 commit comments

Comments
 (0)