@blocknote/core 0.1.0-alpha.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +373 -0
- package/README.md +3 -1
- package/dist/blocknote.js +276 -276
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +20 -20
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +4 -3
- package/src/BlockNoteExtensions.ts +10 -17
- package/src/EditorContent.tsx +2 -1
- package/src/extensions/Blocks/OrderedListPlugin.ts +2 -2
- package/src/extensions/Blocks/helpers/findBlock.ts +1 -1
- package/src/extensions/Blocks/nodes/Block.ts +12 -13
- package/src/extensions/Blocks/nodes/BlockGroup.ts +1 -1
- package/src/extensions/Blocks/nodes/Content.ts +1 -1
- package/src/extensions/BubbleMenu/BubbleMenuExtension.tsx +2 -1
- package/src/extensions/BubbleMenu/component/BubbleMenu.tsx +10 -14
- package/src/extensions/DraggableBlocks/components/DragHandle.tsx +1 -2
- package/src/extensions/TrailingNode/TrailingNodeExtension.ts +8 -5
- package/src/useEditor.ts +4 -0
- package/types/src/BlockNoteExtensions.d.ts +3 -0
- package/types/src/extensions/TrailingNode/TrailingNodeExtension.d.ts +3 -0
- package/types/src/useEditor.d.ts +3 -0
- package/src/extensions/Blocks/nodes/README.md +0 -26
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocknote/core",
|
|
3
|
+
"homepage": "https://github.com/yousefed/blocknote",
|
|
3
4
|
"private": false,
|
|
4
|
-
"license": "
|
|
5
|
-
"version": "0.1.0
|
|
5
|
+
"license": "MPL-2.0",
|
|
6
|
+
"version": "0.1.0",
|
|
6
7
|
"files": [
|
|
7
8
|
"dist",
|
|
8
9
|
"types",
|
|
@@ -105,5 +106,5 @@
|
|
|
105
106
|
"access": "public",
|
|
106
107
|
"registry": "https://registry.npmjs.org/"
|
|
107
108
|
},
|
|
108
|
-
"gitHead": "
|
|
109
|
+
"gitHead": "511cb65b707ca40289d838c71ac6edd29d882eef"
|
|
109
110
|
}
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { Extensions, extensions } from "@tiptap/core";
|
|
2
2
|
|
|
3
|
+
import { Node } from "@tiptap/core";
|
|
3
4
|
import Bold from "@tiptap/extension-bold";
|
|
4
5
|
import Code from "@tiptap/extension-code";
|
|
5
6
|
import DropCursor from "@tiptap/extension-dropcursor";
|
|
6
7
|
import GapCursor from "@tiptap/extension-gapcursor";
|
|
7
8
|
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";
|
|
12
9
|
import { History } from "@tiptap/extension-history";
|
|
10
|
+
import Italic from "@tiptap/extension-italic";
|
|
13
11
|
import Strike from "@tiptap/extension-strike";
|
|
14
12
|
import Text from "@tiptap/extension-text";
|
|
13
|
+
import Underline from "@tiptap/extension-underline";
|
|
15
14
|
import { blocks } from "./extensions/Blocks";
|
|
16
15
|
import blockStyles from "./extensions/Blocks/nodes/Block.module.css";
|
|
17
16
|
import { BubbleMenuExtension } from "./extensions/BubbleMenu/BubbleMenuExtension";
|
|
@@ -22,12 +21,16 @@ import { Placeholder } from "./extensions/Placeholder/PlaceholderExtension";
|
|
|
22
21
|
import SlashMenuExtension from "./extensions/SlashMenu";
|
|
23
22
|
import { TrailingNode } from "./extensions/TrailingNode/TrailingNodeExtension";
|
|
24
23
|
import UniqueID from "./extensions/UniqueID/UniqueID";
|
|
24
|
+
|
|
25
25
|
export const Document = Node.create({
|
|
26
26
|
name: "doc",
|
|
27
27
|
topNode: true,
|
|
28
28
|
content: "block+",
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Get all the Tiptap extensions BlockNote is configured with by default
|
|
33
|
+
*/
|
|
31
34
|
export const getBlockNoteExtensions = () => {
|
|
32
35
|
const ret: Extensions = [
|
|
33
36
|
extensions.ClipboardTextSerializer,
|
|
@@ -48,7 +51,7 @@ export const getBlockNoteExtensions = () => {
|
|
|
48
51
|
showOnlyCurrent: false,
|
|
49
52
|
}),
|
|
50
53
|
UniqueID.configure({
|
|
51
|
-
types: ["
|
|
54
|
+
types: ["block"],
|
|
52
55
|
}),
|
|
53
56
|
HardBreak,
|
|
54
57
|
// Comments,
|
|
@@ -64,26 +67,16 @@ export const getBlockNoteExtensions = () => {
|
|
|
64
67
|
Underline,
|
|
65
68
|
HyperlinkMark,
|
|
66
69
|
FixedParagraph,
|
|
70
|
+
|
|
67
71
|
// custom blocks:
|
|
68
72
|
...blocks,
|
|
69
73
|
DraggableBlocksExtension,
|
|
70
74
|
DropCursor.configure({ width: 5, color: "#ddeeff" }),
|
|
71
75
|
BubbleMenuExtension,
|
|
72
76
|
History,
|
|
73
|
-
SlashMenuExtension,
|
|
74
77
|
// This needs to be at the bottom of this list, because Key events (such as enter, when selecting a /command),
|
|
75
78
|
// should be handled before Enter handlers in other components like splitListItem
|
|
76
|
-
|
|
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,
|
|
87
80
|
TrailingNode,
|
|
88
81
|
];
|
|
89
82
|
return ret;
|
package/src/EditorContent.tsx
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
// BlockNote uses a similar pattern as Tiptap, so for now we can just export that
|
|
2
|
+
export { EditorContent } from "@tiptap/react";
|
|
@@ -11,12 +11,12 @@ export const OrderedListPlugin = () => {
|
|
|
11
11
|
let count = 1;
|
|
12
12
|
let skip = 0;
|
|
13
13
|
newState.doc.descendants((node, pos) => {
|
|
14
|
-
if (node.type.name === "
|
|
14
|
+
if (node.type.name === "block" && !node.attrs.listType) {
|
|
15
15
|
count = 1;
|
|
16
16
|
}
|
|
17
17
|
if (
|
|
18
18
|
skip === 0 &&
|
|
19
|
-
node.type.name === "
|
|
19
|
+
node.type.name === "block" &&
|
|
20
20
|
node.attrs.listType === "oli"
|
|
21
21
|
) {
|
|
22
22
|
skip = node.content.childCount;
|
|
@@ -43,7 +43,7 @@ declare module "@tiptap/core" {
|
|
|
43
43
|
* The main "Block node" documents consist of
|
|
44
44
|
*/
|
|
45
45
|
export const Block = Node.create<IBlock>({
|
|
46
|
-
name: "
|
|
46
|
+
name: "block",
|
|
47
47
|
group: "block",
|
|
48
48
|
addOptions() {
|
|
49
49
|
return {
|
|
@@ -52,7 +52,7 @@ export const Block = Node.create<IBlock>({
|
|
|
52
52
|
},
|
|
53
53
|
|
|
54
54
|
// A block always contains content, and optionally a blockGroup which contains nested blocks
|
|
55
|
-
content: "
|
|
55
|
+
content: "content blockgroup?",
|
|
56
56
|
|
|
57
57
|
defining: true,
|
|
58
58
|
|
|
@@ -172,7 +172,7 @@ export const Block = Node.create<IBlock>({
|
|
|
172
172
|
const nodePos = tr.selection.$anchor.posAtIndex(0, -1) - 1;
|
|
173
173
|
|
|
174
174
|
// const node2 = tr.doc.nodeAt(nodePos);
|
|
175
|
-
if (node.type.name === "
|
|
175
|
+
if (node.type.name === "block" && node.attrs["listType"]) {
|
|
176
176
|
if (dispatch) {
|
|
177
177
|
tr.setNodeMarkup(nodePos, undefined, {
|
|
178
178
|
...node.attrs,
|
|
@@ -203,8 +203,7 @@ export const Block = Node.create<IBlock>({
|
|
|
203
203
|
|
|
204
204
|
// Create new block after current block
|
|
205
205
|
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)!;
|
|
208
207
|
if (dispatch) {
|
|
209
208
|
tr.insert(endOfBlock, newBlock);
|
|
210
209
|
tr.setSelection(new TextSelection(tr.doc.resolve(endOfBlock + 1)));
|
|
@@ -218,7 +217,7 @@ export const Block = Node.create<IBlock>({
|
|
|
218
217
|
const nodePos = tr.selection.$anchor.posAtIndex(0, -1) - 1;
|
|
219
218
|
|
|
220
219
|
// const node2 = tr.doc.nodeAt(nodePos);
|
|
221
|
-
if (node.type.name === "
|
|
220
|
+
if (node.type.name === "block") {
|
|
222
221
|
if (dispatch) {
|
|
223
222
|
tr.setNodeMarkup(nodePos, undefined, {
|
|
224
223
|
...node.attrs,
|
|
@@ -268,11 +267,11 @@ export const Block = Node.create<IBlock>({
|
|
|
268
267
|
commands.command(({ tr }) => {
|
|
269
268
|
const isAtStartOfNode = tr.selection.$anchor.parentOffset === 0;
|
|
270
269
|
const node = tr.selection.$anchor.node(-1);
|
|
271
|
-
if (isAtStartOfNode && node.type.name === "
|
|
270
|
+
if (isAtStartOfNode && node.type.name === "block") {
|
|
272
271
|
// we're at the start of the block, so we're trying to "backspace" the bullet or indentation
|
|
273
272
|
return commands.first([
|
|
274
273
|
() => commands.unsetList(), // first try to remove the "list" property
|
|
275
|
-
() => commands.liftListItem("
|
|
274
|
+
() => commands.liftListItem("block"), // then try to remove a level of indentation
|
|
276
275
|
]);
|
|
277
276
|
}
|
|
278
277
|
return false;
|
|
@@ -292,7 +291,7 @@ export const Block = Node.create<IBlock>({
|
|
|
292
291
|
const isAtStartOfNode = tr.selection.$anchor.parentOffset === 0;
|
|
293
292
|
const anchor = tr.selection.$anchor;
|
|
294
293
|
const node = anchor.node(-1);
|
|
295
|
-
if (isAtStartOfNode && node.type.name === "
|
|
294
|
+
if (isAtStartOfNode && node.type.name === "block") {
|
|
296
295
|
if (node.childCount === 2) {
|
|
297
296
|
// BlockB has children. We want to go from this:
|
|
298
297
|
//
|
|
@@ -337,7 +336,7 @@ export const Block = Node.create<IBlock>({
|
|
|
337
336
|
const handleEnter = () =>
|
|
338
337
|
this.editor.commands.first(({ commands }) => [
|
|
339
338
|
// Try to split the current block into 2 items:
|
|
340
|
-
() => commands.splitListItem("
|
|
339
|
+
() => commands.splitListItem("block"),
|
|
341
340
|
// Otherwise, maybe we are in an empty list item. "Enter" should remove the list bullet
|
|
342
341
|
({ tr, dispatch }) => {
|
|
343
342
|
const $from = tr.selection.$from;
|
|
@@ -348,7 +347,7 @@ export const Block = Node.create<IBlock>({
|
|
|
348
347
|
const node = tr.selection.$anchor.node(-1);
|
|
349
348
|
const nodePos = tr.selection.$anchor.posAtIndex(0, -1) - 1;
|
|
350
349
|
|
|
351
|
-
if (node.type.name === "
|
|
350
|
+
if (node.type.name === "block" && node.attrs["listType"]) {
|
|
352
351
|
if (dispatch) {
|
|
353
352
|
tr.setNodeMarkup(nodePos, undefined, {
|
|
354
353
|
...node.attrs,
|
|
@@ -373,9 +372,9 @@ export const Block = Node.create<IBlock>({
|
|
|
373
372
|
return {
|
|
374
373
|
Backspace: handleBackspace,
|
|
375
374
|
Enter: handleEnter,
|
|
376
|
-
Tab: () => this.editor.commands.sinkListItem("
|
|
375
|
+
Tab: () => this.editor.commands.sinkListItem("block"),
|
|
377
376
|
"Shift-Tab": () => {
|
|
378
|
-
return this.editor.commands.liftListItem("
|
|
377
|
+
return this.editor.commands.liftListItem("block");
|
|
379
378
|
},
|
|
380
379
|
"Mod-Alt-0": () =>
|
|
381
380
|
this.editor.chain().unsetList().unsetBlockHeading().run(),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Extension } from "@tiptap/core";
|
|
2
2
|
import { PluginKey } from "prosemirror-state";
|
|
3
3
|
import ReactDOM from "react-dom";
|
|
4
|
+
import rootStyles from "../../root.module.css";
|
|
4
5
|
import { createBubbleMenuPlugin } from "./BubbleMenuPlugin";
|
|
5
6
|
import { BubbleMenu } from "./component/BubbleMenu";
|
|
6
|
-
|
|
7
|
+
|
|
7
8
|
/**
|
|
8
9
|
* The menu that is displayed when selecting a piece of text.
|
|
9
10
|
*/
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
+
import DropdownMenu, { DropdownItemGroup } from "@atlaskit/dropdown-menu";
|
|
1
2
|
import { Editor } from "@tiptap/core";
|
|
2
3
|
import {
|
|
3
4
|
RiBold,
|
|
4
5
|
RiH1,
|
|
5
6
|
RiH2,
|
|
6
7
|
RiH3,
|
|
8
|
+
RiIndentDecrease,
|
|
9
|
+
RiIndentIncrease,
|
|
7
10
|
RiItalic,
|
|
8
11
|
RiLink,
|
|
9
|
-
RiStrikethrough,
|
|
10
|
-
RiUnderline,
|
|
11
|
-
RiIndentIncrease,
|
|
12
|
-
RiIndentDecrease,
|
|
13
|
-
RiText,
|
|
14
12
|
RiListOrdered,
|
|
15
13
|
RiListUnordered,
|
|
14
|
+
RiStrikethrough,
|
|
15
|
+
RiText,
|
|
16
|
+
RiUnderline,
|
|
16
17
|
} from "react-icons/ri";
|
|
17
18
|
import { SimpleToolbarButton } from "../../../shared/components/toolbar/SimpleToolbarButton";
|
|
18
19
|
import { Toolbar } from "../../../shared/components/toolbar/Toolbar";
|
|
19
20
|
import { useEditorForceUpdate } from "../../../shared/hooks/useEditorForceUpdate";
|
|
20
21
|
import { findBlock } from "../../Blocks/helpers/findBlock";
|
|
21
22
|
import formatKeyboardShortcut from "../../helpers/formatKeyboardShortcut";
|
|
22
|
-
import LinkToolbarButton from "./LinkToolbarButton";
|
|
23
|
-
import DropdownMenu, { DropdownItemGroup } from "@atlaskit/dropdown-menu";
|
|
24
23
|
import DropdownBlockItem from "./DropdownBlockItem";
|
|
24
|
+
import LinkToolbarButton from "./LinkToolbarButton";
|
|
25
25
|
|
|
26
26
|
type ListType = "li" | "oli";
|
|
27
27
|
|
|
@@ -167,19 +167,15 @@ export const BubbleMenu = (props: { editor: Editor }) => {
|
|
|
167
167
|
icon={RiStrikethrough}
|
|
168
168
|
/>
|
|
169
169
|
<SimpleToolbarButton
|
|
170
|
-
onClick={() =>
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
isDisabled={!props.editor.can().sinkListItem("tcblock")}
|
|
170
|
+
onClick={() => props.editor.chain().focus().sinkListItem("block").run()}
|
|
171
|
+
isDisabled={!props.editor.can().sinkListItem("block")}
|
|
174
172
|
mainTooltip="Indent"
|
|
175
173
|
secondaryTooltip={formatKeyboardShortcut("Tab")}
|
|
176
174
|
icon={RiIndentIncrease}
|
|
177
175
|
/>
|
|
178
176
|
|
|
179
177
|
<SimpleToolbarButton
|
|
180
|
-
onClick={() =>
|
|
181
|
-
props.editor.chain().focus().liftListItem("tcblock").run()
|
|
182
|
-
}
|
|
178
|
+
onClick={() => props.editor.chain().focus().liftListItem("block").run()}
|
|
183
179
|
isDisabled={
|
|
184
180
|
!props.editor.can().command(({ state }) => {
|
|
185
181
|
const block = findBlock(state.selection);
|
|
@@ -59,8 +59,7 @@ export const DragHandle = (props: {
|
|
|
59
59
|
if (currentBlock.node.firstChild?.textContent.length !== 0) {
|
|
60
60
|
// Create new block after current block
|
|
61
61
|
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()!;
|
|
64
63
|
props.view.state.tr.insert(endOfBlock, newBlock);
|
|
65
64
|
props.view.dispatch(props.view.state.tr.insert(endOfBlock, newBlock));
|
|
66
65
|
props.view.dispatch(
|
|
@@ -13,6 +13,9 @@ export interface TrailingNodeOptions {
|
|
|
13
13
|
node: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Add a trailing node to the document so the user can always click at the bottom of the document and start typing
|
|
18
|
+
*/
|
|
16
19
|
export const TrailingNode = Extension.create<TrailingNodeOptions>({
|
|
17
20
|
name: "trailingNode",
|
|
18
21
|
|
|
@@ -29,8 +32,8 @@ export const TrailingNode = Extension.create<TrailingNodeOptions>({
|
|
|
29
32
|
const { doc, tr, schema } = state;
|
|
30
33
|
const shouldInsertNodeAtEnd = plugin.getState(state);
|
|
31
34
|
const endPosition = doc.content.size - 2;
|
|
32
|
-
const type = schema.nodes["
|
|
33
|
-
const contenttype = schema.nodes["
|
|
35
|
+
const type = schema.nodes["block"];
|
|
36
|
+
const contenttype = schema.nodes["content"];
|
|
34
37
|
if (!shouldInsertNodeAtEnd) {
|
|
35
38
|
return;
|
|
36
39
|
}
|
|
@@ -58,10 +61,10 @@ export const TrailingNode = Extension.create<TrailingNodeOptions>({
|
|
|
58
61
|
|
|
59
62
|
lastNode = lastNode.lastChild;
|
|
60
63
|
|
|
61
|
-
if (!lastNode || lastNode.type.name !== "
|
|
62
|
-
throw new Error("Expected
|
|
64
|
+
if (!lastNode || lastNode.type.name !== "block") {
|
|
65
|
+
throw new Error("Expected block");
|
|
63
66
|
}
|
|
64
|
-
return lastNode.nodeSize > 4; // empty <
|
|
67
|
+
return lastNode.nodeSize > 4; // empty <block><content/></block> is length 4
|
|
65
68
|
},
|
|
66
69
|
},
|
|
67
70
|
}),
|
package/src/useEditor.ts
CHANGED
|
@@ -17,6 +17,10 @@ const blockNoteOptions = {
|
|
|
17
17
|
enablePasteRules: true,
|
|
18
18
|
enableCoreExtensions: false,
|
|
19
19
|
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Main hook for importing a BlockNote editor into a react project
|
|
23
|
+
*/
|
|
20
24
|
export const useEditor = (
|
|
21
25
|
options: Partial<BlockNoteEditorOptions> = {},
|
|
22
26
|
deps: DependencyList = []
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { Extensions } from "@tiptap/core";
|
|
2
2
|
import { Node } from "@tiptap/core";
|
|
3
3
|
export declare const Document: Node<any, any>;
|
|
4
|
+
/**
|
|
5
|
+
* Get all the Tiptap extensions BlockNote is configured with by default
|
|
6
|
+
*/
|
|
4
7
|
export declare const getBlockNoteExtensions: () => Extensions;
|
|
@@ -7,4 +7,7 @@ import { Extension } from "@tiptap/core";
|
|
|
7
7
|
export interface TrailingNodeOptions {
|
|
8
8
|
node: string;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Add a trailing node to the document so the user can always click at the bottom of the document and start typing
|
|
12
|
+
*/
|
|
10
13
|
export declare const TrailingNode: Extension<TrailingNodeOptions, any>;
|
package/types/src/useEditor.d.ts
CHANGED
|
@@ -4,5 +4,8 @@ declare type BlockNoteEditorOptions = EditorOptions & {
|
|
|
4
4
|
enableBlockNoteExtensions: boolean;
|
|
5
5
|
disableHistoryExtension: boolean;
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* Main hook for importing a BlockNote editor into a react project
|
|
9
|
+
*/
|
|
7
10
|
export declare const useEditor: (options?: Partial<BlockNoteEditorOptions>, deps?: DependencyList) => import("@tiptap/react").Editor | null;
|
|
8
11
|
export {};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Node structure
|
|
2
|
-
|
|
3
|
-
We use the following document structure:
|
|
4
|
-
|
|
5
|
-
```xml
|
|
6
|
-
<blockgroup>
|
|
7
|
-
<block>
|
|
8
|
-
<content>Parent element 1</content>
|
|
9
|
-
<blockgroup>
|
|
10
|
-
<block>
|
|
11
|
-
<content>Nested / child / indented item</content>
|
|
12
|
-
</block>
|
|
13
|
-
</blockgroup>
|
|
14
|
-
</block>
|
|
15
|
-
<block>
|
|
16
|
-
<content>Parent element 2</content>
|
|
17
|
-
<blockgroup>
|
|
18
|
-
<block>...</block>
|
|
19
|
-
<block>...</block>
|
|
20
|
-
</blockgroup>
|
|
21
|
-
</block>
|
|
22
|
-
<block>
|
|
23
|
-
<content>Element 3 without children</content>
|
|
24
|
-
</block>
|
|
25
|
-
</blockgroup>
|
|
26
|
-
```
|