1- import { TextSelection , type Transaction } from "prosemirror-state" ;
2- import { TableMap } from "prosemirror-tables" ;
1+ import {
2+ NodeSelection ,
3+ TextSelection ,
4+ type Transaction ,
5+ } from "prosemirror-state" ;
6+ import { CellSelection } from "prosemirror-tables" ;
37
48import { Block } from "../../../blocks/defaultBlocks.js" ;
59import { Selection } from "../../../editor/selectionTypes.js" ;
10+ import { MultipleNodeSelection } from "../../../extensions-shared/MultipleNodeSelection.js" ;
611import {
712 BlockIdentifier ,
813 BlockSchema ,
@@ -15,7 +20,7 @@ import {
1520 prosemirrorSliceToSlicedBlocks ,
1621} from "../../nodeConversions/nodeToBlock.js" ;
1722import { getNodeById } from "../../nodeUtil.js" ;
18- import { getBlockNoteSchema , getPmSchema } from "../../pmUtil.js" ;
23+ import { getPmSchema } from "../../pmUtil.js" ;
1924
2025export function getSelection <
2126 BSchema extends BlockSchema ,
@@ -24,16 +29,24 @@ export function getSelection<
2429> ( tr : Transaction ) : Selection < BSchema , I , S > | undefined {
2530 const pmSchema = getPmSchema ( tr ) ;
2631 // Return undefined if the selection is collapsed or a node is selected.
27- if ( tr . selection . empty || "node" in tr . selection ) {
32+ if ( tr . selection . empty || tr . selection instanceof NodeSelection ) {
2833 return undefined ;
2934 }
3035
31- const $startBlockBeforePos = tr . doc . resolve (
32- getNearestBlockPos ( tr . doc , tr . selection . from ) . posBeforeNode ,
33- ) ;
34- const $endBlockBeforePos = tr . doc . resolve (
35- getNearestBlockPos ( tr . doc , tr . selection . to ) . posBeforeNode ,
36- ) ;
36+ const $startBlockBeforePos =
37+ tr . selection instanceof MultipleNodeSelection
38+ ? tr . selection . $anchor
39+ : tr . doc . resolve (
40+ getNearestBlockPos ( tr . doc , tr . selection . from ) . posBeforeNode ,
41+ ) ;
42+ const $endBlockBeforePos =
43+ tr . selection instanceof MultipleNodeSelection
44+ ? tr . doc . resolve (
45+ tr . selection . head - tr . selection . $head . nodeBefore ! . nodeSize ,
46+ )
47+ : tr . doc . resolve (
48+ getNearestBlockPos ( tr . doc , tr . selection . to ) . posBeforeNode ,
49+ ) ;
3750
3851 // Converts the node at the given index and depth around `$startBlockBeforePos`
3952 // to a block. Used to get blocks at given indices at the shared depth and
@@ -140,84 +153,123 @@ export function setSelection(
140153 const startBlockId =
141154 typeof startBlock === "string" ? startBlock : startBlock . id ;
142155 const endBlockId = typeof endBlock === "string" ? endBlock : endBlock . id ;
143- const pmSchema = getPmSchema ( tr ) ;
144- const schema = getBlockNoteSchema ( pmSchema ) ;
145156
146157 if ( startBlockId === endBlockId ) {
147- throw new Error (
148- `Attempting to set selection with the same anchor and head blocks (id ${ startBlockId } )` ,
149- ) ;
150- }
151- const anchorPosInfo = getNodeById ( startBlockId , tr . doc ) ;
152- if ( ! anchorPosInfo ) {
153- throw new Error ( `Block with ID ${ startBlockId } not found` ) ;
154- }
155- const headPosInfo = getNodeById ( endBlockId , tr . doc ) ;
156- if ( ! headPosInfo ) {
157- throw new Error ( `Block with ID ${ endBlockId } not found` ) ;
158- }
158+ // If the same block is provided for the start and end, its content gets
159+ // selected.
160+ const posInfo = getNodeById ( startBlockId , tr . doc ) ;
161+ if ( ! posInfo ) {
162+ throw new Error ( `Block with ID ${ startBlockId } not found` ) ;
163+ }
159164
160- const anchorBlockInfo = getBlockInfo ( anchorPosInfo ) ;
161- const headBlockInfo = getBlockInfo ( headPosInfo ) ;
162-
163- const anchorBlockConfig =
164- schema . blockSchema [
165- anchorBlockInfo . blockNoteType as keyof typeof schema . blockSchema
166- ] ;
167- const headBlockConfig =
168- schema . blockSchema [
169- headBlockInfo . blockNoteType as keyof typeof schema . blockSchema
170- ] ;
171-
172- if (
173- ! anchorBlockInfo . isBlockContainer ||
174- anchorBlockConfig . content === "none"
175- ) {
176- throw new Error (
177- `Attempting to set selection anchor in block without content (id ${ startBlockId } )` ,
178- ) ;
165+ const blockInfo = getBlockInfo ( posInfo ) ;
166+
167+ // Case for regular blocks.
168+ if ( blockInfo . isBlockContainer ) {
169+ const content = blockInfo . blockContent . node . type . spec . content ! ;
170+
171+ // Set `NodeSelection` on the `blockContent` node if it has no content.
172+ if ( content === "" ) {
173+ tr . setSelection (
174+ NodeSelection . create ( tr . doc , blockInfo . blockContent . beforePos ) ,
175+ ) ;
176+
177+ return ;
178+ }
179+
180+ // Set a `TextSelection` spanning the block's inline content, if it has
181+ // inline content.
182+ if ( content === "inline*" ) {
183+ tr . setSelection (
184+ TextSelection . create (
185+ tr . doc ,
186+ blockInfo . blockContent . beforePos + 1 ,
187+ blockInfo . blockContent . afterPos - 1 ,
188+ ) ,
189+ ) ;
190+
191+ return ;
192+ }
193+
194+ // Set a `CellSelection` spanning all cells in the table, if it has table
195+ // content.
196+ if ( content === "tableRow+" ) {
197+ const firstRowBeforePos = blockInfo . blockContent . beforePos + 1 ;
198+ const firstCellBeforePos = firstRowBeforePos + 1 ;
199+ const lastRowAfterPos = blockInfo . blockContent . afterPos - 1 ;
200+ const lastCellAfterPos = lastRowAfterPos - 1 ;
201+
202+ tr . setSelection (
203+ CellSelection . create (
204+ tr . doc ,
205+ firstCellBeforePos ,
206+ lastCellAfterPos -
207+ tr . doc . resolve ( lastCellAfterPos ) . nodeBefore ! . nodeSize ,
208+ ) ,
209+ ) ;
210+
211+ return ;
212+ }
213+
214+ throw new Error (
215+ `Invalid content type: ${ content } for node type ${ blockInfo . blockContent . node . type . name } ` ,
216+ ) ;
217+ }
218+
219+ // Case for when block is a `columnList`.
220+ if ( blockInfo . blockNoteType === "columnList" ) {
221+ const firstColumnBeforePos = blockInfo . bnBlock . beforePos + 1 ;
222+ const firstBlockBeforePos = firstColumnBeforePos + 1 ;
223+ const lastColumnAfterPos = blockInfo . bnBlock . afterPos - 1 ;
224+ const lastBlockAfterPos = lastColumnAfterPos - 1 ;
225+
226+ tr . setSelection (
227+ MultipleNodeSelection . create (
228+ tr . doc ,
229+ firstBlockBeforePos ,
230+ lastBlockAfterPos -
231+ tr . doc . resolve ( lastBlockAfterPos ) . nodeBefore ! . nodeSize ,
232+ ) ,
233+ ) ;
234+ }
235+
236+ // Case for when block is a `column`.
237+ if ( blockInfo . blockNoteType === "column" ) {
238+ const firstBlockBeforePos = blockInfo . bnBlock . beforePos + 1 ;
239+ const lastBlockAfterPos = blockInfo . bnBlock . afterPos - 1 ;
240+
241+ // Run recursively as the column may only have one block.
242+ setSelection (
243+ tr ,
244+ tr . doc . resolve ( firstBlockBeforePos ) . nodeAfter ! . attrs . id ,
245+ tr . doc . resolve ( lastBlockAfterPos ) . nodeBefore ! . attrs . id ,
246+ ) ;
247+ }
248+
249+ throw new Error ( `Invalid block node: ${ blockInfo . blockNoteType } ` ) ;
179250 }
180- if ( ! headBlockInfo . isBlockContainer || headBlockConfig . content === "none" ) {
181- throw new Error (
182- `Attempting to set selection anchor in block without content (id ${ endBlockId } )` ,
183- ) ;
251+
252+ const startPosInfo = getNodeById ( startBlockId , tr . doc ) ;
253+ if ( ! startPosInfo ) {
254+ throw new Error ( `Block with ID ${ startBlockId } not found` ) ;
184255 }
185256
186- let startPos : number ;
187- let endPos : number ;
257+ const startBlockInfo = getBlockInfo ( startPosInfo ) ;
188258
189- if ( anchorBlockConfig . content === "table" ) {
190- const tableMap = TableMap . get ( anchorBlockInfo . blockContent . node ) ;
191- const firstCellPos =
192- anchorBlockInfo . blockContent . beforePos +
193- tableMap . positionAt ( 0 , 0 , anchorBlockInfo . blockContent . node ) +
194- 1 ;
195- startPos = firstCellPos + 2 ;
196- } else {
197- startPos = anchorBlockInfo . blockContent . beforePos + 1 ;
259+ const endPosInfo = getNodeById ( endBlockId , tr . doc ) ;
260+ if ( ! endPosInfo ) {
261+ throw new Error ( `Block with ID ${ endBlockId } not found` ) ;
198262 }
199263
200- if ( headBlockConfig . content === "table" ) {
201- const tableMap = TableMap . get ( headBlockInfo . blockContent . node ) ;
202- const lastCellPos =
203- headBlockInfo . blockContent . beforePos +
204- tableMap . positionAt (
205- tableMap . height - 1 ,
206- tableMap . width - 1 ,
207- headBlockInfo . blockContent . node ,
208- ) +
209- 1 ;
210- const lastCellNodeSize = tr . doc . resolve ( lastCellPos ) . nodeAfter ! . nodeSize ;
211- endPos = lastCellPos + lastCellNodeSize - 2 ;
212- } else {
213- endPos = headBlockInfo . blockContent . afterPos - 1 ;
214- }
264+ const endBlockInfo = getBlockInfo ( endPosInfo ) ;
215265
216- // TODO: We should polish up the `MultipleNodeSelection` and use that instead.
217- // Right now it's missing a few things like a jsonID and styling to show
218- // which nodes are selected. `TextSelection` is ok for now, but has the
219- // restriction that the start/end blocks must have content.
220- tr . setSelection ( TextSelection . create ( tr . doc , startPos , endPos ) ) ;
266+ tr . setSelection (
267+ MultipleNodeSelection . create (
268+ tr . doc ,
269+ startBlockInfo . bnBlock . beforePos ,
270+ endBlockInfo . bnBlock . afterPos ,
271+ ) ,
272+ ) ;
221273}
222274
223275export function getSelectionCutBlocks ( tr : Transaction ) {
0 commit comments