Skip to content

Commit 6f1387e

Browse files
feat: Better selections and moving multiple blocks (#1276)
* Made `getSelection` only return blocks at the spanned by the selection at the depth of the shallowest spanned block * Fixed bug where moving blocks across a `columnList` using Cmd+ArrowUp/ArrowDown wouldn't work * Revert "Fixed bug where moving blocks across a `columnList` using Cmd+ArrowUp/ArrowDown wouldn't work" This reverts commit 8c4ddd3. * Renamed `moveBlock` -> `moveBlocks` * Small fixes * - Made selections & moving blocks up/down more closely mimic Notion - Moved `prevBlock`, `nextBlock`, and `parentBlock` from `Selection` fields to getters on the editor which take a `BlockIdentifier` - Fixed table handles error being thrown sometimes when removing blocks (was triggering on `moveBlocks`) * Fixed small bug with getting blocks * Made `moveBlocks` work with columns * Added `setSelection` and unit tests * Cleaned up block getters * Implemented PR feedback * Made `getNodeById` return undefined instead of throwing error * Updated docs
1 parent 59a2b69 commit 6f1387e

File tree

31 files changed

+13432
-4284
lines changed

31 files changed

+13432
-4284
lines changed

docs/pages/docs/editor-api/cursor-selections.mdx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,23 @@ const selection = editor.getSelection();
8787

8888
`returns:` A snapshot of the current selection, or `undefined` if no selection is active.
8989

90+
### Setting Selection
91+
92+
You update the selection to span from one block to another using the following call:
93+
94+
```typescript
95+
setSelection(startBlock: BlockIdentifier, endBlock: BlockIdentifier): void;
96+
97+
// Usage
98+
editor.setSelection(startBlockIdentifier, endBlockIdentifier);
99+
```
100+
101+
`startBlock:` The [identifier](/docs/editor-api/manipulating-blocks#block-identifiers) of the block where the selection should start.
102+
103+
`endBlock:` The [identifier](/docs/editor-api/manipulating-blocks#block-identifiers) of the block where the selection should end.
104+
105+
Both `startBlock` and `endBlock` must point to a block with content. The updated selection will span from the start of the first block to the end of the last block.
106+
90107
## Getting Selected Blocks
91108

92109
The demo below displays the blocks in the current [selection](/docs/editor-api/cursor-selections#selections) as JSON below the editor. If a selection isn't active, it displays the block containing the [text cursor](/docs/editor-api/cursor-selections#text-cursor) instead.

docs/pages/docs/editor-api/manipulating-blocks.mdx

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,17 @@ path: /docs/manipulating-blocks
1010
Below, we explain the methods on `editor` you can use to read Blocks from the editor, and how to create / remove / update Blocks:
1111

1212
- [`get document`](/docs/editor-api/manipulating-blocks#getting-the-document)
13-
- [`getBlock`](/docs/editor-api/manipulating-blocks#getting-a-specific-block)
13+
- [`getBlock`](/docs/editor-api/manipulating-blocks#single-specific-block)
14+
- [`getPrevBlock`](/docs/editor-api/manipulating-blocks#previous-block)
15+
- [`getNextBlock`](/docs/editor-api/manipulating-blocks#next-block)
16+
- [`getParentBlock`](/docs/editor-api/manipulating-blocks#parent-block)
1417
- [`forEachBlock`](/docs/editor-api/manipulating-blocks#traversing-all-blocks)
1518
- [`insertBlocks`](/docs/editor-api/manipulating-blocks#inserting-new-blocks)
1619
- [`updateBlock`](/docs/editor-api/manipulating-blocks#updating-blocks)
1720
- [`removeBlocks`](/docs/editor-api/manipulating-blocks#removing-blocks)
1821
- [`replaceBlocks`](/docs/editor-api/manipulating-blocks#replacing-blocks)
22+
- [`moveBlocksUp`](/docs/editor-api/manipulating-blocks#moving-up)
23+
- [`moveBlocksDown`](/docs/editor-api/manipulating-blocks#moving-down)
1924
- [`canNestBlock`](/docs/editor-api/manipulating-blocks#nesting-blocks)
2025
- [`nestBlock`](/docs/editor-api/manipulating-blocks#nesting-blocks)
2126
- [`canUnnestBlock`](/docs/editor-api/manipulating-blocks#un-nesting-blocks)
@@ -70,7 +75,9 @@ const blocks = editor.document;
7075

7176
We already used this for the [Document JSON](/docs/editor-basics/document-structure#document-json) demo.
7277

73-
### Getting a Specific Block
78+
### Getting Specific Blocks
79+
80+
#### Single Specific Block
7481

7582
Use `getBlock` to retrieve a snapshot of a specific block in the editor:
7683

@@ -85,6 +92,51 @@ const block = editor.getBlock(blockIdentifier);
8592

8693
`returns:` The block that matches the identifier, or `undefined` if no matching block was found.
8794

95+
#### Previous Block
96+
97+
Use `getPrevBlock` to retrieve a snapshot of a block's previous sibling in the editor:
98+
99+
```typescript
100+
getPrevBlock(blockIdentifier: BlockIdentifier): Block | undefined;
101+
102+
// Usage
103+
const prevBlock = editor.getPrevBlock(blockIdentifier);
104+
```
105+
106+
`blockIdentifier:` The [identifier](/docs/editor-api/manipulating-blocks#block-identifiers) of an existing block for which the previous sibling should be retrieved.
107+
108+
`returns:` The previous sibling of the block that matches the identifier, or `undefined` if no matching block was found. Also `undefined` when the matching block is the first in the document, or the first child of a parent block.
109+
110+
#### Next Block
111+
112+
Use `getNextBlock` to retrieve a snapshot of a block's next sibling in the editor:
113+
114+
```typescript
115+
getNextBlock(blockIdentifier: BlockIdentifier): Block | undefined;
116+
117+
// Usage
118+
const nextBlock = editor.getNextBlock(blockIdentifier);
119+
```
120+
121+
`blockIdentifier:` The [identifier](/docs/editor-api/manipulating-blocks#block-identifiers) of an existing block for which the next sibling should be retrieved.
122+
123+
`returns:` The next sibling of the block that matches the identifier, or `undefined` if no matching block was found. Also `undefined` when the matching block is the last in the document, or the last child of a parent block.
124+
125+
#### Parent Block
126+
127+
Use `getParentBlock` to retrieve a snapshot of a block's parent in the editor:
128+
129+
```typescript
130+
getParentBlock(blockIdentifier: BlockIdentifier): Block | undefined;
131+
132+
// Usage
133+
const parentBlock = editor.getParentBlock(blockIdentifier);
134+
```
135+
136+
`blockIdentifier:` The [identifier](/docs/editor-api/manipulating-blocks#block-identifiers) of an existing block for which the parent should be retrieved.
137+
138+
`returns:` The parent of the block that matches the identifier, or `undefined` if no matching block was found. Also `undefined` when the matching block is not nested in a parent block.
139+
88140
### Traversing All Blocks
89141

90142
Use `forEachBlock` to traverse all blocks in the editor depth-first, and execute a callback for each block:
@@ -193,6 +245,30 @@ If the blocks that should be removed are not adjacent or are at different nestin
193245

194246
Throws an error if any of the blocks to remove could not be found.
195247

248+
## Moving Blocks Up/Down
249+
250+
### Moving Up
251+
252+
Use `moveBlocksUp` to move the selected blocks up:
253+
254+
```typescript
255+
moveBlocksUp(): void;
256+
257+
// Usage
258+
editor.moveBlocksUp();
259+
```
260+
261+
### Moving Down
262+
263+
Use `moveBlocksDown` to move the selected blocks down:
264+
265+
```typescript
266+
moveBlocksDown(): void;
267+
268+
// Usage
269+
editor.moveBlocksDown();
270+
```
271+
196272
## Nesting & Un-nesting Blocks
197273

198274
BlockNote also provides functions to nest & un-nest the block containing the [Text Cursor](/docs/editor-api/cursor-selections#text-cursor).

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,23 @@ export function insertBlocks<
3232
);
3333
}
3434

35-
const { node, posBeforeNode } = getNodeById(
36-
id,
37-
editor._tiptapEditor.state.doc
38-
);
35+
const posInfo = getNodeById(id, editor._tiptapEditor.state.doc);
36+
if (!posInfo) {
37+
throw new Error(`Block with ID ${id} not found`);
38+
}
3939

4040
// TODO: we might want to use the ReplaceStep directly here instead of insert,
4141
// because the fitting algorithm should not be necessary and might even cause unexpected behavior
4242
if (placement === "before") {
4343
editor.dispatch(
44-
editor._tiptapEditor.state.tr.insert(posBeforeNode, nodesToInsert)
44+
editor._tiptapEditor.state.tr.insert(posInfo.posBeforeNode, nodesToInsert)
4545
);
4646
}
4747

4848
if (placement === "after") {
4949
editor.dispatch(
5050
editor._tiptapEditor.state.tr.insert(
51-
posBeforeNode + node.nodeSize,
51+
posInfo.posBeforeNode + posInfo.node.nodeSize,
5252
nodesToInsert
5353
)
5454
);

0 commit comments

Comments
 (0)