Skip to content

Commit 82ffdc8

Browse files
committed
feat: selections based on locations
1 parent 30c0b9a commit 82ffdc8

File tree

12 files changed

+21634
-133
lines changed

12 files changed

+21634
-133
lines changed

packages/core/src/api/blockManipulation/__snapshots__/insertContentAt.test.ts.snap

Lines changed: 20795 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 588 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { TableMap } from "prosemirror-tables";
44
import { Block } from "../../../blocks/defaultBlocks.js";
55
import { Selection } from "../../../editor/selectionTypes.js";
66
import {
7-
BlockIdentifier,
87
BlockSchema,
98
InlineContentSchema,
109
StyleSchema,
@@ -16,15 +15,18 @@ import {
1615
} from "../../nodeConversions/nodeToBlock.js";
1716
import { getNodeById } from "../../nodeUtil.js";
1817
import { getBlockNoteSchema, getPmSchema } from "../../pmUtil.js";
18+
import { getBlockId, normalizeToRange } from "../../../locations/utils.js";
19+
import { Location } from "../../../locations/types.js";
20+
import { resolvePMToLocation } from "../../../locations/location.js";
1921

2022
export function getSelection<
2123
BSchema extends BlockSchema,
2224
I extends InlineContentSchema,
2325
S extends StyleSchema,
2426
>(tr: Transaction): Selection<BSchema, I, S> | undefined {
25-
const pmSchema = getPmSchema(tr);
2627
// Return undefined if the selection is collapsed or a node is selected.
2728
if (tr.selection.empty || "node" in tr.selection) {
29+
// TODO do we really want this?
2830
return undefined;
2931
}
3032

@@ -51,7 +53,7 @@ export function getSelection<
5153
);
5254
}
5355

54-
return nodeToBlock(node, pmSchema);
56+
return nodeToBlock(node);
5557
};
5658

5759
const blocks: Block<BSchema, I, S>[] = [];
@@ -92,7 +94,7 @@ export function getSelection<
9294
// [ id-2, id-3, id-4, id-6, id-7, id-8, id-9 ]
9395
if ($startBlockBeforePos.depth > sharedDepth) {
9496
// Adds the block that the selection starts in.
95-
blocks.push(nodeToBlock($startBlockBeforePos.nodeAfter!, pmSchema));
97+
blocks.push(nodeToBlock($startBlockBeforePos.nodeAfter!));
9698

9799
// Traverses all depths from the depth of the block in which the selection
98100
// starts, up to the shared depth.
@@ -127,19 +129,25 @@ export function getSelection<
127129
);
128130
}
129131

132+
const location = resolvePMToLocation(tr.doc, {
133+
anchor: tr.selection.anchor,
134+
head: tr.selection.head,
135+
});
136+
130137
return {
138+
meta: { location },
139+
range: normalizeToRange(location),
131140
blocks,
132141
};
133142
}
134143

135144
export function setSelection(
136145
tr: Transaction,
137-
startBlock: BlockIdentifier,
138-
endBlock: BlockIdentifier,
146+
startBlock: Location,
147+
endBlock: Location,
139148
) {
140-
const startBlockId =
141-
typeof startBlock === "string" ? startBlock : startBlock.id;
142-
const endBlockId = typeof endBlock === "string" ? endBlock : endBlock.id;
149+
const startBlockId = getBlockId(startBlock, "start");
150+
const endBlockId = getBlockId(endBlock, "end");
143151
const pmSchema = getPmSchema(tr);
144152
const schema = getBlockNoteSchema(pmSchema);
145153

packages/core/src/editor/BlockNoteEditor.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import { updateBlockTr } from "../api/blockManipulation/commands/updateBlock/upd
7474
import { getBlockInfoFromTransaction } from "../api/getBlockInfoFromPos.js";
7575
import { blockToNode } from "../api/nodeConversions/blockToNode.js";
7676
import "../style.css";
77+
import { Location } from "../locations/types.js";
7778

7879
/**
7980
* A factory function that returns a BlockNoteExtension
@@ -1257,7 +1258,7 @@ export class BlockNoteEditor<
12571258
* @param startBlock The identifier of the block that should be the start of the selection.
12581259
* @param endBlock The identifier of the block that should be the end of the selection.
12591260
*/
1260-
public setSelection(startBlock: BlockIdentifier, endBlock: BlockIdentifier) {
1261+
public setSelection(startBlock: Location, endBlock: Location) {
12611262
return this._selectionManager.setSelection(startBlock, endBlock);
12621263
}
12631264

@@ -1354,9 +1355,15 @@ export class BlockNoteEditor<
13541355
*/
13551356
public insertInlineContent(
13561357
content: PartialInlineContent<ISchema, SSchema>,
1357-
{ updateSelection = false }: { updateSelection?: boolean } = {},
1358+
{
1359+
updateSelection = false,
1360+
location,
1361+
}: { updateSelection?: boolean; location?: Location } = {},
13581362
) {
1359-
this._styleManager.insertInlineContent(content, { updateSelection });
1363+
this._styleManager.insertInlineContent(content, {
1364+
updateSelection,
1365+
location,
1366+
});
13601367
}
13611368

13621369
/**

packages/core/src/editor/managers/SelectionManager.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isNodeSelection, posToDOMRect } from "@tiptap/core";
12
import {
23
getSelection,
34
getSelectionCutBlocks,
@@ -7,22 +8,22 @@ import {
78
getTextCursorPosition,
89
setTextCursorPosition,
910
} from "../../api/blockManipulation/selections/textCursorPosition.js";
10-
import { isNodeSelection, posToDOMRect } from "@tiptap/core";
11+
import {
12+
DefaultBlockSchema,
13+
DefaultInlineContentSchema,
14+
DefaultStyleSchema,
15+
} from "../../blocks/defaultBlocks.js";
16+
import { getSelectionLocation } from "../../locations/location.js";
17+
import { Location } from "../../locations/types.js";
1118
import {
1219
BlockIdentifier,
1320
BlockSchema,
1421
InlineContentSchema,
1522
StyleSchema,
1623
} from "../../schema/index.js";
17-
import {
18-
DefaultBlockSchema,
19-
DefaultInlineContentSchema,
20-
DefaultStyleSchema,
21-
} from "../../blocks/defaultBlocks.js";
22-
import { Selection } from "../selectionTypes.js";
23-
import { TextCursorPosition } from "../cursorPositionTypes.js";
2424
import { BlockNoteEditor } from "../BlockNoteEditor.js";
25-
import { getSelectionLocation } from "../../locations/location.js";
25+
import { TextCursorPosition } from "../cursorPositionTypes.js";
26+
import { Selection } from "../selectionTypes.js";
2627

2728
export class SelectionManager<
2829
BSchema extends BlockSchema = DefaultBlockSchema,
@@ -61,7 +62,7 @@ export class SelectionManager<
6162
* @param startBlock The identifier of the block that should be the start of the selection.
6263
* @param endBlock The identifier of the block that should be the end of the selection.
6364
*/
64-
public setSelection(startBlock: BlockIdentifier, endBlock: BlockIdentifier) {
65+
public setSelection(startBlock: Location, endBlock: Location) {
6566
return this.editor.transact((tr) => setSelection(tr, startBlock, endBlock));
6667
}
6768

packages/core/src/editor/managers/StyleManager.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1+
import { TextSelection } from "@tiptap/pm/state";
12
import { insertContentAt } from "../../api/blockManipulation/insertContentAt.js";
23
import { inlineContentToNodes } from "../../api/nodeConversions/blockToNode.js";
4+
import {
5+
DefaultBlockSchema,
6+
DefaultInlineContentSchema,
7+
DefaultStyleSchema,
8+
} from "../../blocks/defaultBlocks.js";
9+
import { resolveLocation } from "../../locations/location.js";
10+
import { Location } from "../../locations/types.js";
311
import {
412
BlockSchema,
513
InlineContentSchema,
614
PartialInlineContent,
715
StyleSchema,
816
Styles,
917
} from "../../schema/index.js";
10-
import {
11-
DefaultBlockSchema,
12-
DefaultInlineContentSchema,
13-
DefaultStyleSchema,
14-
} from "../../blocks/defaultBlocks.js";
15-
import { TextSelection } from "@tiptap/pm/state";
1618
import { UnreachableCaseError } from "../../util/typescript.js";
1719
import { BlockNoteEditor } from "../BlockNoteEditor.js";
1820

@@ -30,16 +32,26 @@ export class StyleManager<
3032
*/
3133
public insertInlineContent(
3234
content: PartialInlineContent<ISchema, SSchema>,
33-
{ updateSelection = false }: { updateSelection?: boolean } = {},
35+
{
36+
updateSelection = false,
37+
location,
38+
}: { updateSelection?: boolean; location?: Location } = {},
3439
) {
3540
const nodes = inlineContentToNodes(content, this.editor.pmSchema);
3641

3742
this.editor.transact((tr) => {
43+
const range = location
44+
? resolveLocation(tr.doc, location)
45+
: {
46+
anchor: tr.selection.from,
47+
head: tr.selection.to,
48+
};
49+
3850
insertContentAt(
3951
tr,
4052
{
41-
from: tr.selection.from,
42-
to: tr.selection.to,
53+
from: range.anchor,
54+
to: range.head,
4355
},
4456
nodes,
4557
{

packages/core/src/editor/selectionTypes.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Block } from "../blocks/defaultBlocks.js";
2+
import { Point, Range } from "../locations/types.js";
23
import {
34
BlockSchema,
45
InlineContentSchema,
@@ -10,5 +11,25 @@ export type Selection<
1011
I extends InlineContentSchema,
1112
S extends StyleSchema,
1213
> = {
14+
/**
15+
* Meta information about the current selection.
16+
*/
17+
meta: {
18+
/**
19+
* The underlying location of the current selection.
20+
*
21+
* If the selection is a single cursor, this will be a {@link Point}.
22+
* If the selection is a selection range, this will be a {@link Range}.
23+
*/
24+
location: Point | Range;
25+
};
26+
/**
27+
* The range of the current selection.
28+
* @note This is the same as the {@link meta.location} but normalized to a {@link Range}.
29+
*/
30+
range: Range;
31+
/**
32+
* The blocks that the current selection spans across.
33+
*/
1334
blocks: Block<BSchema, I, S>[];
1435
};

0 commit comments

Comments
 (0)