1
1
import { useMemo , useState } from "react" ;
2
- import {
3
- Block ,
4
- BlockNoteEditor ,
5
- BlockSchema ,
6
- PartialBlock ,
7
- } from "@blocknote/core" ;
2
+ import { BlockNoteEditor , BlockSchema } from "@blocknote/core" ;
8
3
import { IconType } from "react-icons" ;
9
4
import {
10
5
RiH1 ,
@@ -15,114 +10,112 @@ import {
15
10
RiText ,
16
11
} from "react-icons/ri" ;
17
12
18
- import {
19
- ToolbarDropdown ,
20
- ToolbarDropdownProps ,
21
- } from "../../../SharedComponents/Toolbar/components/ToolbarDropdown" ;
13
+ import { ToolbarDropdown } from "../../../SharedComponents/Toolbar/components/ToolbarDropdown" ;
22
14
import { useEditorSelectionChange } from "../../../hooks/useEditorSelectionChange" ;
23
15
import { useEditorContentChange } from "../../../hooks/useEditorContentChange" ;
16
+ import { ToolbarDropdownItemProps } from "../../../SharedComponents/Toolbar/components/ToolbarDropdownItem" ;
24
17
25
- type HeadingLevels = "1" | "2" | "3" ;
26
-
27
- const headingIcons : Record < HeadingLevels , IconType > = {
28
- "1" : RiH1 ,
29
- "2" : RiH2 ,
30
- "3" : RiH3 ,
18
+ export type BlockTypeDropdownItem = {
19
+ name : string ;
20
+ type : string ;
21
+ props ?: Record < string , string > ;
22
+ icon : IconType ;
31
23
} ;
32
24
33
- const shouldShow = < BSchema extends BlockSchema > ( block : Block < BSchema > ) => {
34
- if ( block . type === "paragraph" ) {
35
- return true ;
36
- }
37
-
38
- if ( block . type === "heading" && "level" in block . props ) {
39
- return true ;
40
- }
41
-
42
- if ( block . type === "bulletListItem" ) {
43
- return true ;
44
- }
45
-
46
- return block . type === "numberedListItem" ;
47
- } ;
25
+ export const defaultBlockTypeDropdownItems : BlockTypeDropdownItem [ ] = [
26
+ {
27
+ name : "Paragraph" ,
28
+ type : "paragraph" ,
29
+ icon : RiText ,
30
+ } ,
31
+ {
32
+ name : "Heading 1" ,
33
+ type : "heading" ,
34
+ props : { level : "1" } ,
35
+ icon : RiH1 ,
36
+ } ,
37
+ {
38
+ name : "Heading 2" ,
39
+ type : "heading" ,
40
+ props : { level : "2" } ,
41
+ icon : RiH2 ,
42
+ } ,
43
+ {
44
+ name : "Heading 3" ,
45
+ type : "heading" ,
46
+ props : { level : "3" } ,
47
+ icon : RiH3 ,
48
+ } ,
49
+ {
50
+ name : "Bullet List" ,
51
+ type : "bulletListItem" ,
52
+ icon : RiListUnordered ,
53
+ } ,
54
+ {
55
+ name : "Numbered List" ,
56
+ type : "numberedListItem" ,
57
+ icon : RiListOrdered ,
58
+ } ,
59
+ ] ;
48
60
49
61
export const BlockTypeDropdown = < BSchema extends BlockSchema > ( props : {
50
62
editor : BlockNoteEditor < BSchema > ;
63
+ items ?: BlockTypeDropdownItem [ ] ;
51
64
} ) => {
52
65
const [ block , setBlock ] = useState (
53
66
props . editor . getTextCursorPosition ( ) . block
54
67
) ;
55
68
56
- const dropdownItems : ToolbarDropdownProps [ "items" ] = useMemo ( ( ) => {
57
- const items : ToolbarDropdownProps [ "items" ] = [ ] ;
58
-
59
- if ( "paragraph" in props . editor . schema ) {
60
- items . push ( {
61
- onClick : ( ) => {
62
- props . editor . focus ( ) ;
63
- props . editor . updateBlock ( block , {
64
- type : "paragraph" ,
65
- props : { } ,
66
- } ) ;
67
- } ,
68
- text : "Paragraph" ,
69
- icon : RiText ,
70
- isSelected : block . type === "paragraph" ,
71
- } ) ;
72
- }
73
-
74
- if (
75
- "heading" in props . editor . schema &&
76
- "level" in props . editor . schema . heading . propSchema
77
- ) {
78
- items . push (
79
- ...( [ "1" , "2" , "3" ] as const ) . map ( ( level ) => ( {
80
- onClick : ( ) => {
81
- props . editor . focus ( ) ;
82
- props . editor . updateBlock ( block , {
83
- type : "heading" ,
84
- props : { level : level } ,
85
- } as PartialBlock < BSchema > ) ;
86
- } ,
87
- text : "Heading " + level ,
88
- icon : headingIcons [ level ] ,
89
- isSelected : block . type === "heading" && block . props . level === level ,
90
- } ) )
91
- ) ;
92
- }
93
-
94
- if ( "bulletListItem" in props . editor . schema ) {
95
- items . push ( {
96
- onClick : ( ) => {
97
- props . editor . focus ( ) ;
98
- props . editor . updateBlock ( block , {
99
- type : "bulletListItem" ,
100
- props : { } ,
101
- } ) ;
102
- } ,
103
- text : "Bullet List" ,
104
- icon : RiListUnordered ,
105
- isSelected : block . type === "bulletListItem" ,
106
- } ) ;
107
- }
69
+ const filteredItems : BlockTypeDropdownItem [ ] = useMemo ( ( ) => {
70
+ return ( props . items || defaultBlockTypeDropdownItems ) . filter ( ( item ) => {
71
+ // Checks if block type exists in the schema
72
+ if ( ! ( item . type in props . editor . schema ) ) {
73
+ return false ;
74
+ }
75
+
76
+ // Checks if props for the block type are valid
77
+ for ( const [ prop , value ] of Object . entries ( item . props || { } ) ) {
78
+ const propSchema = props . editor . schema [ item . type ] . propSchema ;
79
+
80
+ // Checks if the prop exists for the block type
81
+ if ( ! ( prop in propSchema ) ) {
82
+ return false ;
83
+ }
84
+
85
+ // Checks if the prop's value is valid
86
+ if (
87
+ propSchema [ prop ] . values !== undefined &&
88
+ ! propSchema [ prop ] . values ! . includes ( value )
89
+ ) {
90
+ return false ;
91
+ }
92
+ }
93
+
94
+ return true ;
95
+ } ) ;
96
+ } , [ props . editor , props . items ] ) ;
97
+
98
+ const shouldShow : boolean = useMemo (
99
+ ( ) => filteredItems . find ( ( item ) => item . type === block . type ) !== undefined ,
100
+ [ block . type , filteredItems ]
101
+ ) ;
108
102
109
- if ( "numberedListItem" in props . editor . schema ) {
110
- items . push ( {
103
+ const fullItems : ToolbarDropdownItemProps [ ] = useMemo (
104
+ ( ) =>
105
+ filteredItems . map ( ( item ) => ( {
106
+ text : item . name ,
107
+ icon : item . icon ,
111
108
onClick : ( ) => {
112
109
props . editor . focus ( ) ;
113
110
props . editor . updateBlock ( block , {
114
- type : "numberedListItem" ,
111
+ type : item . type ,
115
112
props : { } ,
116
113
} ) ;
117
114
} ,
118
- text : "Numbered List" ,
119
- icon : RiListOrdered ,
120
- isSelected : block . type === "numberedListItem" ,
121
- } ) ;
122
- }
123
-
124
- return items ;
125
- } , [ block , props . editor ] ) ;
115
+ isSelected : block . type === item . type ,
116
+ } ) ) ,
117
+ [ block , filteredItems , props . editor ]
118
+ ) ;
126
119
127
120
useEditorContentChange ( props . editor , ( ) => {
128
121
setBlock ( props . editor . getTextCursorPosition ( ) . block ) ;
@@ -132,9 +125,9 @@ export const BlockTypeDropdown = <BSchema extends BlockSchema>(props: {
132
125
setBlock ( props . editor . getTextCursorPosition ( ) . block ) ;
133
126
} ) ;
134
127
135
- if ( ! shouldShow ( block ) ) {
128
+ if ( ! shouldShow ) {
136
129
return null ;
137
130
}
138
131
139
- return < ToolbarDropdown items = { dropdownItems } /> ;
132
+ return < ToolbarDropdown items = { fullItems } /> ;
140
133
} ;
0 commit comments