Skip to content

Commit f95cc2c

Browse files
committed
Fixed BlockTypeSelect not filtering items based on schema
1 parent 9c6f563 commit f95cc2c

File tree

2 files changed

+119
-144
lines changed
  • examples/06-custom-schema/07-configuring-blocks/src
  • packages/react/src/components/FormattingToolbar/DefaultSelects

2 files changed

+119
-144
lines changed

examples/06-custom-schema/07-configuring-blocks/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export default function App() {
3030
{
3131
type: "paragraph",
3232
content:
33-
"Notice how only heading levels 1-3 are avaiable, and toggle headings are not shown.",
33+
"Notice how only heading levels 1-3 are available, and toggle headings are not shown.",
3434
},
3535
{
3636
type: "paragraph",

packages/react/src/components/FormattingToolbar/DefaultSelects/BlockTypeSelect.tsx

Lines changed: 118 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {
22
Block,
3+
BlockNoteEditor,
34
BlockSchema,
4-
Dictionary,
5+
editorHasBlockWithType,
56
InlineContentSchema,
67
StyleSchema,
78
} from "@blocknote/core";
@@ -29,7 +30,6 @@ import {
2930
import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js";
3031
import { useEditorContentOrSelectionChange } from "../../../hooks/useEditorContentOrSelectionChange.js";
3132
import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks.js";
32-
import { useDictionary } from "../../../i18n/dictionary.js";
3333

3434
export type BlockTypeSelectItem = {
3535
name: string;
@@ -41,146 +41,123 @@ export type BlockTypeSelectItem = {
4141
) => boolean;
4242
};
4343

44-
export const blockTypeSelectItems = (
45-
dict: Dictionary,
46-
): BlockTypeSelectItem[] => [
47-
{
48-
name: dict.slash_menu.paragraph.title,
49-
type: "paragraph",
50-
icon: RiText,
51-
isSelected: (block) => block.type === "paragraph",
52-
},
53-
{
54-
name: dict.slash_menu.heading.title,
55-
type: "heading",
56-
props: { level: 1 },
57-
icon: RiH1,
58-
isSelected: (block) =>
59-
block.type === "heading" &&
60-
"level" in block.props &&
61-
block.props.level === 1,
62-
},
63-
{
64-
name: dict.slash_menu.heading_2.title,
65-
type: "heading",
66-
props: { level: 2 },
67-
icon: RiH2,
68-
isSelected: (block) =>
69-
block.type === "heading" &&
70-
"level" in block.props &&
71-
block.props.level === 2,
72-
},
73-
{
74-
name: dict.slash_menu.heading_3.title,
75-
type: "heading",
76-
props: { level: 3 },
77-
icon: RiH3,
78-
isSelected: (block) =>
79-
block.type === "heading" &&
80-
"level" in block.props &&
81-
block.props.level === 3,
82-
},
83-
{
84-
name: dict.slash_menu.heading_4.title,
85-
type: "heading",
86-
props: { level: 4 },
87-
icon: RiH4,
88-
isSelected: (block) =>
89-
block.type === "heading" &&
90-
"level" in block.props &&
91-
block.props.level === 4,
92-
},
93-
{
94-
name: dict.slash_menu.heading_5.title,
95-
type: "heading",
96-
props: { level: 5 },
97-
icon: RiH5,
98-
isSelected: (block) =>
99-
block.type === "heading" &&
100-
"level" in block.props &&
101-
block.props.level === 5,
102-
},
103-
{
104-
name: dict.slash_menu.heading_6.title,
105-
type: "heading",
106-
props: { level: 6 },
107-
icon: RiH6,
108-
isSelected: (block) =>
109-
block.type === "heading" &&
110-
"level" in block.props &&
111-
block.props.level === 6,
112-
},
113-
{
114-
name: dict.slash_menu.toggle_heading.title,
115-
type: "heading",
116-
props: { level: 1, isToggleable: true },
117-
icon: RiH1,
118-
isSelected: (block) =>
119-
block.type === "heading" &&
120-
"level" in block.props &&
121-
block.props.level === 1 &&
122-
"isToggleable" in block.props &&
123-
block.props.isToggleable,
124-
},
125-
{
126-
name: dict.slash_menu.toggle_heading_2.title,
127-
type: "heading",
128-
props: { level: 2, isToggleable: true },
129-
icon: RiH2,
130-
isSelected: (block) =>
131-
block.type === "heading" &&
132-
"level" in block.props &&
133-
block.props.level === 2 &&
134-
"isToggleable" in block.props &&
135-
block.props.isToggleable,
136-
},
137-
{
138-
name: dict.slash_menu.toggle_heading_3.title,
139-
type: "heading",
140-
props: { level: 3, isToggleable: true },
141-
icon: RiH3,
142-
isSelected: (block) =>
143-
block.type === "heading" &&
144-
"level" in block.props &&
145-
block.props.level === 3 &&
146-
"isToggleable" in block.props &&
147-
block.props.isToggleable,
148-
},
149-
{
150-
name: dict.slash_menu.quote.title,
151-
type: "quote",
152-
icon: RiQuoteText,
153-
isSelected: (block) => block.type === "quote",
154-
},
155-
{
156-
name: dict.slash_menu.toggle_list.title,
157-
type: "toggleListItem",
158-
icon: RiPlayList2Fill,
159-
isSelected: (block) => block.type === "toggleListItem",
160-
},
161-
{
162-
name: dict.slash_menu.bullet_list.title,
163-
type: "bulletListItem",
164-
icon: RiListUnordered,
165-
isSelected: (block) => block.type === "bulletListItem",
166-
},
167-
{
168-
name: dict.slash_menu.numbered_list.title,
169-
type: "numberedListItem",
170-
icon: RiListOrdered,
171-
isSelected: (block) => block.type === "numberedListItem",
172-
},
173-
{
174-
name: dict.slash_menu.check_list.title,
175-
type: "checkListItem",
176-
icon: RiListCheck3,
177-
isSelected: (block) => block.type === "checkListItem",
178-
},
179-
];
44+
const headingLevelIcons: Record<any, IconType> = {
45+
1: RiH1,
46+
2: RiH2,
47+
3: RiH3,
48+
4: RiH4,
49+
5: RiH5,
50+
6: RiH6,
51+
};
52+
53+
export function getDefaultBlockTypeSelectItems<
54+
BSchema extends BlockSchema,
55+
I extends InlineContentSchema,
56+
S extends StyleSchema,
57+
>(editor: BlockNoteEditor<BSchema, I, S>) {
58+
const items: BlockTypeSelectItem[] = [];
59+
60+
if (editorHasBlockWithType(editor, "paragraph")) {
61+
items.push({
62+
name: editor.dictionary.slash_menu.paragraph.title,
63+
type: "paragraph",
64+
icon: RiText,
65+
isSelected: (block) => block.type === "paragraph",
66+
});
67+
}
68+
69+
if (editorHasBlockWithType(editor, "heading", { level: "number" })) {
70+
(
71+
editor.schema.blockSchema.heading.propSchema.level.values || [1, 2, 3]
72+
).forEach((level) => {
73+
items.push({
74+
name: editor.dictionary.slash_menu[
75+
`heading${level === 1 ? "" : "_" + level}` as keyof typeof editor.dictionary.slash_menu
76+
].title,
77+
type: "heading",
78+
props: { level },
79+
icon: headingLevelIcons[level],
80+
isSelected: (block) =>
81+
block.type === "heading" &&
82+
"level" in block.props &&
83+
block.props.level === level,
84+
});
85+
});
86+
}
87+
88+
if (
89+
editorHasBlockWithType(editor, "heading", {
90+
level: "number",
91+
isToggleable: "boolean",
92+
})
93+
) {
94+
(editor.schema.blockSchema.heading.propSchema.level.values || [1, 2, 3])
95+
.filter((level) => level <= 3)
96+
.forEach((level) => {
97+
items.push({
98+
name: editor.dictionary.slash_menu[
99+
`toggle_heading${level === 1 ? "" : "_" + level}` as keyof typeof editor.dictionary.slash_menu
100+
].title,
101+
type: "heading",
102+
props: { level, isToggleable: true },
103+
icon: headingLevelIcons[level],
104+
isSelected: (block) =>
105+
block.type === "heading" &&
106+
"level" in block.props &&
107+
block.props.level === level &&
108+
"isToggleable" in block.props &&
109+
block.props.isToggleable,
110+
});
111+
});
112+
}
113+
114+
if (editorHasBlockWithType(editor, "quote")) {
115+
items.push({
116+
name: editor.dictionary.slash_menu.quote.title,
117+
type: "quote",
118+
icon: RiQuoteText,
119+
isSelected: (block) => block.type === "quote",
120+
});
121+
}
122+
123+
if (editorHasBlockWithType(editor, "toggleListItem")) {
124+
items.push({
125+
name: editor.dictionary.slash_menu.toggle_list.title,
126+
type: "toggleListItem",
127+
icon: RiPlayList2Fill,
128+
isSelected: (block) => block.type === "toggleListItem",
129+
});
130+
}
131+
if (editorHasBlockWithType(editor, "bulletListItem")) {
132+
items.push({
133+
name: editor.dictionary.slash_menu.bullet_list.title,
134+
type: "bulletListItem",
135+
icon: RiListUnordered,
136+
isSelected: (block) => block.type === "bulletListItem",
137+
});
138+
}
139+
if (editorHasBlockWithType(editor, "numberedListItem")) {
140+
items.push({
141+
name: editor.dictionary.slash_menu.numbered_list.title,
142+
type: "numberedListItem",
143+
icon: RiListOrdered,
144+
isSelected: (block) => block.type === "numberedListItem",
145+
});
146+
}
147+
if (editorHasBlockWithType(editor, "checkListItem")) {
148+
items.push({
149+
name: editor.dictionary.slash_menu.check_list.title,
150+
type: "checkListItem",
151+
icon: RiListCheck3,
152+
isSelected: (block) => block.type === "checkListItem",
153+
});
154+
}
155+
156+
return items;
157+
}
180158

181159
export const BlockTypeSelect = (props: { items?: BlockTypeSelectItem[] }) => {
182160
const Components = useComponentsContext()!;
183-
const dict = useDictionary();
184161

185162
const editor = useBlockNoteEditor<
186163
BlockSchema,
@@ -193,10 +170,8 @@ export const BlockTypeSelect = (props: { items?: BlockTypeSelectItem[] }) => {
193170
const [block, setBlock] = useState(editor.getTextCursorPosition().block);
194171

195172
const filteredItems: BlockTypeSelectItem[] = useMemo(() => {
196-
return (props.items || blockTypeSelectItems(dict)).filter(
197-
(item) => item.type in editor.schema.blockSchema,
198-
);
199-
}, [editor, dict, props.items]);
173+
return props.items || getDefaultBlockTypeSelectItems(editor);
174+
}, [editor, props.items]);
200175

201176
const shouldShow: boolean = useMemo(
202177
() => filteredItems.find((item) => item.type === block.type) !== undefined,

0 commit comments

Comments
 (0)