Skip to content

Commit f6b505f

Browse files
Added BlockIdentifier & small changes (#112)
* Added BlockIdentifier & small changes * `getBlocks()` now returns undefined when the block isn't found. * Fixed build error --------- Co-authored-by: Matthew Lipski <[email protected]>
1 parent 99a4b3d commit f6b505f

File tree

4 files changed

+93
-24
lines changed

4 files changed

+93
-24
lines changed

examples/vanilla/src/main.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const editor = new BlockNoteEditor({
1818
blockSideMenuFactory,
1919
},
2020
onUpdate: () => {
21-
console.log(editor.allBlocks);
21+
console.log(editor.topLevelBlocks);
2222
},
2323
editorDOMAttributes: {
2424
class: "editor",

packages/core/src/api/Editor.ts

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { Editor as TiptapEditor } from "@tiptap/core";
22
import { Node } from "prosemirror-model";
33
import { getBlockInfoFromPos } from "../extensions/Blocks/helpers/getBlockInfoFromPos";
4-
import { Block, PartialBlock } from "../extensions/Blocks/api/blockTypes";
4+
import {
5+
Block,
6+
BlockIdentifier,
7+
PartialBlock,
8+
} from "../extensions/Blocks/api/blockTypes";
59
import { TextCursorPosition } from "../extensions/Blocks/api/cursorPositionTypes";
610
import { nodeToBlock } from "./nodeConversions/nodeConversions";
711
import {
@@ -24,7 +28,8 @@ export class Editor {
2428
) {}
2529

2630
/**
27-
* Gets a list of all top-level blocks that are in the editor.
31+
* Gets a snapshot of all top-level blocks that are in the editor.
32+
* @returns An array containing a snapshot of all top-level (non-nested) blocks in the editor.
2833
*/
2934
public get topLevelBlocks(): Block[] {
3035
const blocks: Block[] = [];
@@ -38,26 +43,73 @@ export class Editor {
3843
return blocks;
3944
}
4045

46+
/**
47+
* Retrieves a snapshot of an existing block from the editor.
48+
* @param block The identifier of an existing block that should be retrieved.
49+
* @returns The block that matches the identifier, or undefined if no matching block was found.
50+
*/
51+
public getBlock(block: BlockIdentifier): Block | undefined {
52+
const id = typeof block === "string" ? block : block.id;
53+
let newBlock: Block | undefined = undefined;
54+
55+
this.tiptapEditor.state.doc.firstChild!.descendants((node) => {
56+
if (typeof newBlock !== "undefined") {
57+
return false;
58+
}
59+
60+
if (node.type.name !== "blockContainer" || node.attrs.id !== id) {
61+
return true;
62+
}
63+
64+
newBlock = nodeToBlock(node, this.blockCache);
65+
66+
return false;
67+
});
68+
69+
return newBlock;
70+
}
71+
4172
/**
4273
* Traverses all blocks in the editor, including all nested blocks, and executes a callback for each. The traversal is
43-
* depth-first, which is the same order as blocks appear in the editor by y-coordinate.
74+
* depth-first, which is the same order as blocks appear in the editor by y-coordinate. Stops traversal if the callback
75+
* returns false;
4476
* @param callback The callback to execute for each block.
4577
* @param reverse Whether the blocks should be traversed in reverse order.
4678
*/
47-
public allBlocks(
48-
callback: (block: Block) => void,
79+
public forEachBlock(
80+
callback: (block: Block) => boolean,
4981
reverse: boolean = false
5082
): void {
83+
let stop = false;
84+
5185
function helper(blocks: Block[]) {
5286
if (reverse) {
5387
for (const block of blocks.reverse()) {
5488
helper(block.children);
55-
callback(block);
89+
90+
if (stop) {
91+
return;
92+
}
93+
94+
stop = !callback(block);
95+
96+
if (stop) {
97+
return;
98+
}
5699
}
57100
} else {
58101
for (const block of blocks) {
59-
callback(block);
102+
stop = !callback(block);
103+
104+
if (stop) {
105+
return;
106+
}
107+
60108
helper(block.children);
109+
110+
if (stop) {
111+
return;
112+
}
61113
}
62114
}
63115
}
@@ -66,7 +118,8 @@ export class Editor {
66118
}
67119

68120
/**
69-
* Gets information regarding the position of the text cursor in the editor.
121+
* Gets a snapshot of the text cursor position within the editor.
122+
* @returns A snapshot of the text cursor position.
70123
*/
71124
public get textCursorPosition(): TextCursorPosition {
72125
const { node } = getBlockInfoFromPos(
@@ -124,7 +177,7 @@ export class Editor {
124177
}
125178

126179
/**
127-
* Executes a callback function whenever the editor's content changes.
180+
* Executes a callback function whenever the editor's contents change.
128181
* @param callback The callback function to execute.
129182
*/
130183
public onContentChange(callback: () => void) {
@@ -136,6 +189,7 @@ export class Editor {
136189
* is simplified in order to better conform to HTML standards. Block structuring elements are removed, children of
137190
* blocks which aren't list items are lifted out of them, and list items blocks are wrapped in `ul`/`ol` tags.
138191
* @param blocks The list of blocks to serialize into HTML.
192+
* @returns An HTML representation of the blocks.
139193
*/
140194
public async blocksToHTML(blocks: Block[]): Promise<string> {
141195
return blocksToHTML(blocks, this.tiptapEditor.schema);
@@ -144,6 +198,7 @@ export class Editor {
144198
/**
145199
* Creates a list of blocks from an HTML string.
146200
* @param htmlString The HTML string to create a list of blocks from.
201+
* @returns A list of blocks parsed from the HTML string.
147202
*/
148203
public async HTMLToBlocks(htmlString: string): Promise<Block[]> {
149204
return HTMLToBlocks(htmlString, this.tiptapEditor.schema);
@@ -152,8 +207,9 @@ export class Editor {
152207
/**
153208
* Serializes a list of blocks into a Markdown string. The output is simplified as Markdown does not support all
154209
* features of BlockNote. Block structuring elements are removed, children of blocks which aren't list items are
155-
* lifted out of them, and certain styles are removed.
210+
* un-nested, and certain styles are removed.
156211
* @param blocks The list of blocks to serialize into Markdown.
212+
* @returns A Markdown representation of the blocks.
157213
*/
158214
public async blocksToMarkdown(blocks: Block[]): Promise<string> {
159215
return blocksToMarkdown(blocks, this.tiptapEditor.schema);
@@ -162,6 +218,7 @@ export class Editor {
162218
/**
163219
* Creates a list of blocks from a Markdown string.
164220
* @param markdownString The Markdown string to create a list of blocks from.
221+
* @returns A list of blocks parsed from the Markdown string.
165222
*/
166223
public async markdownToBlocks(markdownString: string): Promise<Block[]> {
167224
return markdownToBlocks(markdownString, this.tiptapEditor.schema);

packages/core/src/api/blockManipulation/blockManipulation.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,29 @@
11
import { Editor } from "@tiptap/core";
22
import { Node } from "prosemirror-model";
3-
import { Block, PartialBlock } from "../../extensions/Blocks/api/blockTypes";
3+
import {
4+
BlockIdentifier,
5+
PartialBlock,
6+
} from "../../extensions/Blocks/api/blockTypes";
47
import { blockToNode } from "../nodeConversions/nodeConversions";
58
import { getNodeById } from "../util/nodeUtil";
69

710
export function insertBlocks(
811
blocksToInsert: PartialBlock[],
9-
blockToInsertAt: Block,
12+
referenceBlock: BlockIdentifier,
1013
placement: "before" | "after" | "nested" = "before",
1114
editor: Editor
1215
): void {
16+
const id =
17+
typeof referenceBlock === "string" ? referenceBlock : referenceBlock.id;
18+
1319
const nodesToInsert: Node[] = [];
1420
for (const blockSpec of blocksToInsert) {
1521
nodesToInsert.push(blockToNode(blockSpec, editor.schema));
1622
}
1723

1824
let insertionPos = -1;
1925

20-
const { node, posBeforeNode } = getNodeById(
21-
blockToInsertAt.id,
22-
editor.state.doc
23-
);
26+
const { node, posBeforeNode } = getNodeById(id, editor.state.doc);
2427

2528
if (placement === "before") {
2629
insertionPos = posBeforeNode;
@@ -54,18 +57,25 @@ export function insertBlocks(
5457
}
5558

5659
export function updateBlock(
57-
blockToUpdate: Block,
58-
updatedBlock: PartialBlock,
60+
blockToUpdate: BlockIdentifier,
61+
update: PartialBlock,
5962
editor: Editor
6063
) {
61-
const { posBeforeNode } = getNodeById(blockToUpdate.id, editor.state.doc);
64+
const id =
65+
typeof blockToUpdate === "string" ? blockToUpdate : blockToUpdate.id;
66+
const { posBeforeNode } = getNodeById(id, editor.state.doc);
6267

63-
editor.commands.BNUpdateBlock(posBeforeNode + 1, updatedBlock);
68+
editor.commands.BNUpdateBlock(posBeforeNode + 1, update);
6469
}
6570

66-
export function removeBlocks(blocksToRemove: Block[], editor: Editor) {
71+
export function removeBlocks(
72+
blocksToRemove: BlockIdentifier[],
73+
editor: Editor
74+
) {
6775
const idsOfBlocksToRemove = new Set<string>(
68-
blocksToRemove.map((block) => block.id)
76+
blocksToRemove.map((block) =>
77+
typeof block === "string" ? block : block.id
78+
)
6979
);
7080

7181
let removedSize = 0;
@@ -106,7 +116,7 @@ export function removeBlocks(blocksToRemove: Block[], editor: Editor) {
106116
}
107117

108118
export function replaceBlocks(
109-
blocksToRemove: Block[],
119+
blocksToRemove: BlockIdentifier[],
110120
blocksToInsert: PartialBlock[],
111121
editor: Editor
112122
) {

packages/core/src/extensions/Blocks/api/blockTypes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export type Block =
4545
| BulletListItemBlock
4646
| NumberedListItemBlock;
4747

48+
export type BlockIdentifier = string | Block;
49+
4850
/** Define "Partial Blocks", these are for updating or creating blocks */
4951
export type PartialBlockTemplate<B extends Block> = B extends Block
5052
? Partial<Omit<B, "props" | "children" | "content" | "type">> & {

0 commit comments

Comments
 (0)