@@ -23,7 +23,13 @@ import {
23
23
BlockIdentifier ,
24
24
PartialBlock ,
25
25
} from "./extensions/Blocks/api/blockTypes" ;
26
+ import {
27
+ ColorStyle ,
28
+ Styles ,
29
+ ToggledStyle ,
30
+ } from "./extensions/Blocks/api/inlineContentTypes" ;
26
31
import { TextCursorPosition } from "./extensions/Blocks/api/cursorPositionTypes" ;
32
+ import { Selection } from "./extensions/Blocks/api/selectionTypes" ;
27
33
import { getBlockInfoFromPos } from "./extensions/Blocks/helpers/getBlockInfoFromPos" ;
28
34
import {
29
35
BaseSlashMenuItem ,
@@ -102,6 +108,10 @@ export class BlockNoteEditor {
102
108
return this . _tiptapEditor . view . dom as HTMLDivElement ;
103
109
}
104
110
111
+ public focus ( ) {
112
+ this . _tiptapEditor . view . focus ( ) ;
113
+ }
114
+
105
115
constructor ( options : Partial < BlockNoteEditorOptions > = { } ) {
106
116
// apply defaults
107
117
options = {
@@ -229,15 +239,15 @@ export class BlockNoteEditor {
229
239
230
240
function traverseBlockArray ( blockArray : Block [ ] ) : boolean {
231
241
for ( const block of blockArray ) {
232
- if ( callback ( block ) === false ) {
242
+ if ( ! callback ( block ) ) {
233
243
return false ;
234
244
}
235
245
236
246
const children = reverse
237
247
? block . children . slice ( ) . reverse ( )
238
248
: block . children ;
239
249
240
- if ( traverseBlockArray ( children ) === false ) {
250
+ if ( ! traverseBlockArray ( children ) ) {
241
251
return false ;
242
252
}
243
253
}
@@ -319,6 +329,44 @@ export class BlockNoteEditor {
319
329
}
320
330
}
321
331
332
+ /**
333
+ * Gets a snapshot of the current selection.
334
+ */
335
+ public getSelection ( ) : Selection | undefined {
336
+ if (
337
+ this . _tiptapEditor . state . selection . from ===
338
+ this . _tiptapEditor . state . selection . to
339
+ ) {
340
+ return undefined ;
341
+ }
342
+
343
+ const blocks : Block [ ] = [ ] ;
344
+
345
+ this . _tiptapEditor . state . doc . descendants ( ( node , pos ) => {
346
+ if ( node . type . spec . group !== "blockContent" ) {
347
+ return true ;
348
+ }
349
+
350
+ if (
351
+ pos + node . nodeSize < this . _tiptapEditor . state . selection . from ||
352
+ pos > this . _tiptapEditor . state . selection . to
353
+ ) {
354
+ return true ;
355
+ }
356
+
357
+ blocks . push (
358
+ nodeToBlock (
359
+ this . _tiptapEditor . state . doc . resolve ( pos ) . node ( ) ,
360
+ this . blockCache
361
+ )
362
+ ) ;
363
+
364
+ return false ;
365
+ } ) ;
366
+
367
+ return { blocks : blocks } ;
368
+ }
369
+
322
370
/**
323
371
* Checks if the editor is currently editable, or if it's locked.
324
372
* @returns True if the editor is editable, false otherwise.
@@ -384,6 +432,169 @@ export class BlockNoteEditor {
384
432
replaceBlocks ( blocksToRemove , blocksToInsert , this . _tiptapEditor ) ;
385
433
}
386
434
435
+ /**
436
+ * Gets the active text styles at the text cursor position or at the end of the current selection if it's active.
437
+ */
438
+ public getActiveStyles ( ) {
439
+ const styles : Styles = { } ;
440
+ const marks = this . _tiptapEditor . state . selection . $to . marks ( ) ;
441
+
442
+ const toggleStyles = new Set < ToggledStyle > ( [
443
+ "bold" ,
444
+ "italic" ,
445
+ "underline" ,
446
+ "strike" ,
447
+ "code" ,
448
+ ] ) ;
449
+ const colorStyles = new Set < ColorStyle > ( [ "textColor" , "backgroundColor" ] ) ;
450
+
451
+ for ( const mark of marks ) {
452
+ if ( toggleStyles . has ( mark . type . name as ToggledStyle ) ) {
453
+ styles [ mark . type . name as ToggledStyle ] = true ;
454
+ } else if ( colorStyles . has ( mark . type . name as ColorStyle ) ) {
455
+ styles [ mark . type . name as ColorStyle ] = mark . attrs . color ;
456
+ }
457
+ }
458
+
459
+ return styles ;
460
+ }
461
+
462
+ /**
463
+ * Adds styles to the currently selected content.
464
+ * @param styles The styles to add.
465
+ */
466
+ public addStyles ( styles : Styles ) {
467
+ const toggleStyles = new Set < ToggledStyle > ( [
468
+ "bold" ,
469
+ "italic" ,
470
+ "underline" ,
471
+ "strike" ,
472
+ "code" ,
473
+ ] ) ;
474
+ const colorStyles = new Set < ColorStyle > ( [ "textColor" , "backgroundColor" ] ) ;
475
+
476
+ for ( const [ style , value ] of Object . entries ( styles ) ) {
477
+ if ( toggleStyles . has ( style as ToggledStyle ) ) {
478
+ this . _tiptapEditor . commands . setMark ( style ) ;
479
+ } else if ( colorStyles . has ( style as ColorStyle ) ) {
480
+ this . _tiptapEditor . commands . setMark ( style , { color : value } ) ;
481
+ }
482
+ }
483
+ }
484
+
485
+ /**
486
+ * Removes styles from the currently selected content.
487
+ * @param styles The styles to remove.
488
+ */
489
+ public removeStyles ( styles : Styles ) {
490
+ for ( const style of Object . keys ( styles ) ) {
491
+ this . _tiptapEditor . commands . unsetMark ( style ) ;
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Toggles styles on the currently selected content.
497
+ * @param styles The styles to toggle.
498
+ */
499
+ public toggleStyles ( styles : Styles ) {
500
+ const toggleStyles = new Set < ToggledStyle > ( [
501
+ "bold" ,
502
+ "italic" ,
503
+ "underline" ,
504
+ "strike" ,
505
+ "code" ,
506
+ ] ) ;
507
+ const colorStyles = new Set < ColorStyle > ( [ "textColor" , "backgroundColor" ] ) ;
508
+
509
+ for ( const [ style , value ] of Object . entries ( styles ) ) {
510
+ if ( toggleStyles . has ( style as ToggledStyle ) ) {
511
+ this . _tiptapEditor . commands . toggleMark ( style ) ;
512
+ } else if ( colorStyles . has ( style as ColorStyle ) ) {
513
+ this . _tiptapEditor . commands . toggleMark ( style , { color : value } ) ;
514
+ }
515
+ }
516
+ }
517
+
518
+ /**
519
+ * Gets the currently selected text.
520
+ */
521
+ public getSelectedText ( ) {
522
+ return this . _tiptapEditor . state . doc . textBetween (
523
+ this . _tiptapEditor . state . selection . from ,
524
+ this . _tiptapEditor . state . selection . to
525
+ ) ;
526
+ }
527
+
528
+ /**
529
+ * Gets the URL of the last link in the current selection, or `undefined` if there are no links in the selection.
530
+ */
531
+ public getSelectedLinkUrl ( ) {
532
+ return this . _tiptapEditor . getAttributes ( "link" ) . href as string | undefined ;
533
+ }
534
+
535
+ /**
536
+ * Creates a new link to replace the selected content.
537
+ * @param url The link URL.
538
+ * @param text The text to display the link with.
539
+ */
540
+ public createLink ( url : string , text ?: string ) {
541
+ if ( url === "" ) {
542
+ return ;
543
+ }
544
+
545
+ let { from, to } = this . _tiptapEditor . state . selection ;
546
+
547
+ if ( ! text ) {
548
+ text = this . _tiptapEditor . state . doc . textBetween ( from , to ) ;
549
+ }
550
+
551
+ const mark = this . _tiptapEditor . schema . mark ( "link" , { href : url } ) ;
552
+
553
+ this . _tiptapEditor . view . dispatch (
554
+ this . _tiptapEditor . view . state . tr
555
+ . insertText ( text , from , to )
556
+ . addMark ( from , from + text . length , mark )
557
+ ) ;
558
+ }
559
+
560
+ /**
561
+ * Checks if the block containing the text cursor can be nested.
562
+ */
563
+ public canNestBlock ( ) {
564
+ const { startPos, depth } = getBlockInfoFromPos (
565
+ this . _tiptapEditor . state . doc ,
566
+ this . _tiptapEditor . state . selection . from
567
+ ) ! ;
568
+
569
+ return this . _tiptapEditor . state . doc . resolve ( startPos ) . index ( depth - 1 ) > 0 ;
570
+ }
571
+
572
+ /**
573
+ * Nests the block containing the text cursor into the block above it.
574
+ */
575
+ public nestBlock ( ) {
576
+ this . _tiptapEditor . commands . sinkListItem ( "blockContainer" ) ;
577
+ }
578
+
579
+ /**
580
+ * Checks if the block containing the text cursor is nested.
581
+ */
582
+ public canUnnestBlock ( ) {
583
+ const { depth } = getBlockInfoFromPos (
584
+ this . _tiptapEditor . state . doc ,
585
+ this . _tiptapEditor . state . selection . from
586
+ ) ! ;
587
+
588
+ return depth > 2 ;
589
+ }
590
+
591
+ /**
592
+ * Lifts the block containing the text cursor out of its parent.
593
+ */
594
+ public unnestBlock ( ) {
595
+ this . _tiptapEditor . commands . liftListItem ( "blockContainer" ) ;
596
+ }
597
+
387
598
/**
388
599
* Serializes blocks into an HTML string. To better conform to HTML standards, children of blocks which aren't list
389
600
* items are un-nested in the output HTML.
0 commit comments