Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import {
FormattingToolbarController,
blockTypeSelectItems,
getDefaultBlockTypeSelectItems,
useCreateBlockNote,
BlockTypeSelectItem,
FormattingToolbar,
Expand Down Expand Up @@ -60,7 +60,7 @@ export default function App() {
// Sets the items in the Block Type Select.
blockTypeSelectItems={[
// Gets the default Block Type Select items.
...blockTypeSelectItems(editor.dictionary),
...getDefaultBlockTypeSelectItems(editor),
// Adds an item for the Alert block.
{
name: "Alert",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Block } from "@blocknote/core";
import {
blockTypeSelectItems,
getDefaultBlockTypeSelectItems,
useBlockNoteEditor,
useEditorContentOrSelectionChange,
} from "@blocknote/react";
Expand Down Expand Up @@ -122,7 +122,7 @@ function MUIBlockTypeSelect() {

// Gets the default items for the select.
const defaultBlockTypeSelectItems = useMemo(
() => blockTypeSelectItems(editor.dictionary),
() => getDefaultBlockTypeSelectItems(editor),
[editor.dictionary],
);

Expand Down
4 changes: 2 additions & 2 deletions examples/06-custom-schema/05-alert-block-full-ux/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
FormattingToolbar,
FormattingToolbarController,
SuggestionMenuController,
blockTypeSelectItems,
getDefaultBlockTypeSelectItems,
getDefaultReactSlashMenuItems,
useCreateBlockNote,
} from "@blocknote/react";
Expand Down Expand Up @@ -93,7 +93,7 @@ export default function App() {
// Sets the items in the Block Type Select.
blockTypeSelectItems={[
// Gets the default Block Type Select items.
...blockTypeSelectItems(editor.dictionary),
...getDefaultBlockTypeSelectItems(editor),
// Adds an item for the Alert block.
{
name: "Alert",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function App() {
{
type: "paragraph",
content:
"Notice how only heading levels 1-3 are avaiable, and toggle headings are not shown.",
"Notice how only heading levels 1-3 are available, and toggle headings are not shown.",
},
{
type: "paragraph",
Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this with fresh eyes again, and I'm thinking that the editorHasBlockWithType type guards should maybe be gated within the BlockTypeSelect component's filteredItems instead. Take this for example:

{
      name: editor.dictionary.slash_menu.quote.title,
      type: "quote",
      icon: RiQuoteText,
      isSelected: (block) => block.type === "quote",
    }

Like, can't we know exactly what block this is trying to select based on the type & optional props object? This would allow us to filter the list down & cut down on the type guards needed.

Maybe this is over complicating it, but having a filter for this sort of thing is sort of useful generically, like in the slash menu, block type select & other places surely

Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {
Block,
BlockNoteEditor,
BlockSchema,
Dictionary,
editorHasBlockWithType,
InlineContentSchema,
StyleSchema,
} from "@blocknote/core";
Expand Down Expand Up @@ -29,7 +30,6 @@ import {
import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor.js";
import { useEditorContentOrSelectionChange } from "../../../hooks/useEditorContentOrSelectionChange.js";
import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks.js";
import { useDictionary } from "../../../i18n/dictionary.js";

export type BlockTypeSelectItem = {
name: string;
Expand All @@ -41,146 +41,123 @@ export type BlockTypeSelectItem = {
) => boolean;
};

export const blockTypeSelectItems = (
dict: Dictionary,
): BlockTypeSelectItem[] => [
{
name: dict.slash_menu.paragraph.title,
type: "paragraph",
icon: RiText,
isSelected: (block) => block.type === "paragraph",
},
{
name: dict.slash_menu.heading.title,
type: "heading",
props: { level: 1 },
icon: RiH1,
isSelected: (block) =>
block.type === "heading" &&
"level" in block.props &&
block.props.level === 1,
},
{
name: dict.slash_menu.heading_2.title,
type: "heading",
props: { level: 2 },
icon: RiH2,
isSelected: (block) =>
block.type === "heading" &&
"level" in block.props &&
block.props.level === 2,
},
{
name: dict.slash_menu.heading_3.title,
type: "heading",
props: { level: 3 },
icon: RiH3,
isSelected: (block) =>
block.type === "heading" &&
"level" in block.props &&
block.props.level === 3,
},
{
name: dict.slash_menu.heading_4.title,
type: "heading",
props: { level: 4 },
icon: RiH4,
isSelected: (block) =>
block.type === "heading" &&
"level" in block.props &&
block.props.level === 4,
},
{
name: dict.slash_menu.heading_5.title,
type: "heading",
props: { level: 5 },
icon: RiH5,
isSelected: (block) =>
block.type === "heading" &&
"level" in block.props &&
block.props.level === 5,
},
{
name: dict.slash_menu.heading_6.title,
type: "heading",
props: { level: 6 },
icon: RiH6,
isSelected: (block) =>
block.type === "heading" &&
"level" in block.props &&
block.props.level === 6,
},
{
name: dict.slash_menu.toggle_heading.title,
type: "heading",
props: { level: 1, isToggleable: true },
icon: RiH1,
isSelected: (block) =>
block.type === "heading" &&
"level" in block.props &&
block.props.level === 1 &&
"isToggleable" in block.props &&
block.props.isToggleable,
},
{
name: dict.slash_menu.toggle_heading_2.title,
type: "heading",
props: { level: 2, isToggleable: true },
icon: RiH2,
isSelected: (block) =>
block.type === "heading" &&
"level" in block.props &&
block.props.level === 2 &&
"isToggleable" in block.props &&
block.props.isToggleable,
},
{
name: dict.slash_menu.toggle_heading_3.title,
type: "heading",
props: { level: 3, isToggleable: true },
icon: RiH3,
isSelected: (block) =>
block.type === "heading" &&
"level" in block.props &&
block.props.level === 3 &&
"isToggleable" in block.props &&
block.props.isToggleable,
},
{
name: dict.slash_menu.quote.title,
type: "quote",
icon: RiQuoteText,
isSelected: (block) => block.type === "quote",
},
{
name: dict.slash_menu.toggle_list.title,
type: "toggleListItem",
icon: RiPlayList2Fill,
isSelected: (block) => block.type === "toggleListItem",
},
{
name: dict.slash_menu.bullet_list.title,
type: "bulletListItem",
icon: RiListUnordered,
isSelected: (block) => block.type === "bulletListItem",
},
{
name: dict.slash_menu.numbered_list.title,
type: "numberedListItem",
icon: RiListOrdered,
isSelected: (block) => block.type === "numberedListItem",
},
{
name: dict.slash_menu.check_list.title,
type: "checkListItem",
icon: RiListCheck3,
isSelected: (block) => block.type === "checkListItem",
},
];
const headingLevelIcons: Record<any, IconType> = {
1: RiH1,
2: RiH2,
3: RiH3,
4: RiH4,
5: RiH5,
6: RiH6,
};

export function getDefaultBlockTypeSelectItems<
BSchema extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema,
>(editor: BlockNoteEditor<BSchema, I, S>) {
const items: BlockTypeSelectItem[] = [];

if (editorHasBlockWithType(editor, "paragraph")) {
items.push({
name: editor.dictionary.slash_menu.paragraph.title,
type: "paragraph",
icon: RiText,
isSelected: (block) => block.type === "paragraph",
});
}

if (editorHasBlockWithType(editor, "heading", { level: "number" })) {
(
editor.schema.blockSchema.heading.propSchema.level.values || [1, 2, 3]
).forEach((level) => {
items.push({
name: editor.dictionary.slash_menu[
`heading${level === 1 ? "" : "_" + level}` as keyof typeof editor.dictionary.slash_menu
].title,
type: "heading",
props: { level },
icon: headingLevelIcons[level],
isSelected: (block) =>
block.type === "heading" &&
"level" in block.props &&
block.props.level === level,
});
});
}

if (
editorHasBlockWithType(editor, "heading", {
level: "number",
isToggleable: "boolean",
})
) {
(editor.schema.blockSchema.heading.propSchema.level.values || [1, 2, 3])
.filter((level) => level <= 3)
.forEach((level) => {
items.push({
name: editor.dictionary.slash_menu[
`toggle_heading${level === 1 ? "" : "_" + level}` as keyof typeof editor.dictionary.slash_menu
].title,
type: "heading",
props: { level, isToggleable: true },
icon: headingLevelIcons[level],
isSelected: (block) =>
block.type === "heading" &&
"level" in block.props &&
block.props.level === level &&
"isToggleable" in block.props &&
block.props.isToggleable,
});
});
}

if (editorHasBlockWithType(editor, "quote")) {
items.push({
name: editor.dictionary.slash_menu.quote.title,
type: "quote",
icon: RiQuoteText,
isSelected: (block) => block.type === "quote",
});
}

if (editorHasBlockWithType(editor, "toggleListItem")) {
items.push({
name: editor.dictionary.slash_menu.toggle_list.title,
type: "toggleListItem",
icon: RiPlayList2Fill,
isSelected: (block) => block.type === "toggleListItem",
});
}
if (editorHasBlockWithType(editor, "bulletListItem")) {
items.push({
name: editor.dictionary.slash_menu.bullet_list.title,
type: "bulletListItem",
icon: RiListUnordered,
isSelected: (block) => block.type === "bulletListItem",
});
}
if (editorHasBlockWithType(editor, "numberedListItem")) {
items.push({
name: editor.dictionary.slash_menu.numbered_list.title,
type: "numberedListItem",
icon: RiListOrdered,
isSelected: (block) => block.type === "numberedListItem",
});
}
if (editorHasBlockWithType(editor, "checkListItem")) {
items.push({
name: editor.dictionary.slash_menu.check_list.title,
type: "checkListItem",
icon: RiListCheck3,
isSelected: (block) => block.type === "checkListItem",
});
}

return items;
}

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

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

const filteredItems: BlockTypeSelectItem[] = useMemo(() => {
return (props.items || blockTypeSelectItems(dict)).filter(
(item) => item.type in editor.schema.blockSchema,
);
}, [editor, dict, props.items]);
return props.items || getDefaultBlockTypeSelectItems(editor);
}, [editor, props.items]);

const shouldShow: boolean = useMemo(
() => filteredItems.find((item) => item.type === block.type) !== undefined,
Expand Down
Loading