Skip to content

clean getitems #579

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
57 changes: 14 additions & 43 deletions examples/editor/examples/basic/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import {
BlockNoteEditor,
DefaultBlockSchema,
defaultInlineContentSchema,
defaultInlineContentSpecs,
DefaultStyleSchema,
InlineContentSchema,
filterSuggestionItems,
InlineContentSpecs,
uploadToTmpFilesDotOrg_DEV_ONLY,
} from "@blocknote/core";
Expand All @@ -13,7 +9,6 @@ import {
BlockNoteView,
createReactInlineContentSpec,
DefaultPositionedSuggestionMenu,
MantineSuggestionMenuItemProps,
useBlockNote,
} from "@blocknote/react";
import "@blocknote/react/style.css";
Expand Down Expand Up @@ -43,46 +38,16 @@ const customInlineContentSpecs = {
...defaultInlineContentSpecs,
mention: MentionInlineContent,
} satisfies InlineContentSpecs;
const customInlineContentSchema = {
...defaultInlineContentSchema,
mention: MentionInlineContent.config,
} satisfies InlineContentSchema;

async function getMentionMenuItems(
editor: BlockNoteEditor<
DefaultBlockSchema,
typeof customInlineContentSchema,
DefaultStyleSchema
>,
query: string,
closeMenu: () => void,
clearQuery: () => void
): Promise<MantineSuggestionMenuItemProps[]> {
async function getMentionMenuItems(query: string) {
const users = ["Steve", "Bob", "Joe", "Mike"];
const items: MantineSuggestionMenuItemProps[] = users.map((user) => ({
name: user,
execute: () => {
closeMenu();
clearQuery();
const items = users.map((user) => ({
title: user,

editor._tiptapEditor.commands.insertContent({
type: "mention",
attrs: {
user: user,
},
});
},
aliases: [] as string[],
}));

return items.filter(
({ name, aliases }) =>
name.toLowerCase().startsWith(query.toLowerCase()) ||
(aliases &&
aliases.filter((alias) =>
alias.toLowerCase().startsWith(query.toLowerCase())
).length !== 0)
);
return filterSuggestionItems(items, query);
}

export function App() {
Expand All @@ -107,9 +72,15 @@ export function App() {
<DefaultPositionedSuggestionMenu
editor={editor}
triggerCharacter={"@"}
getItems={(query, closeMenu, clearQuery) =>
getMentionMenuItems(editor, query, closeMenu, clearQuery)
}
getItems={async (query) => getMentionMenuItems(query)}
onItemClick={(item) => {
editor._tiptapEditor.commands.insertContent({
type: "mention",
attrs: {
user: item.title,
},
});
}}
/>
</BlockNoteView>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,15 @@
import {
Block,
BlockNoteEditor,
BlockSchema,
DefaultBlockSchema,
DefaultInlineContentSchema,
DefaultStyleSchema,
formatKeyboardShortcut,
imageToolbarPluginKey,
InlineContentSchema,
isStyledTextInlineContent,
PartialBlock,
StyleSchema,
} from "@blocknote/core";
import {
RiH1,
RiH2,
RiH3,
RiImage2Fill,
RiListOrdered,
RiListUnordered,
RiTable2,
RiText,
} from "react-icons/ri";

import { MantineSuggestionMenuItemProps } from "./MantineDefaults/MantineSuggestionMenuItem";
} from "../../schema";
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
import { DefaultBlockSchema } from "../../blocks/defaultBlocks";
import { formatKeyboardShortcut } from "../../util/browser";
import { imageToolbarPluginKey } from "../ImageToolbar/ImageToolbarPlugin";

// Sets the editor's text cursor position to the next content editable block,
// so either a block with inline content or a table. The last block is always a
Expand Down Expand Up @@ -88,117 +74,89 @@ export function insertOrUpdateBlock<
return insertedBlock;
}

// TODO: Probably want an easier way of customizing the items list.
export async function defaultGetItems<
BSchema extends BlockSchema = DefaultBlockSchema,
I extends InlineContentSchema = DefaultInlineContentSchema,
S extends StyleSchema = DefaultStyleSchema
>(
editor: BlockNoteEditor<BSchema, I, S>,
query: string,
closeMenu: () => void,
clearQuery: () => void
): Promise<MantineSuggestionMenuItemProps[]> {
const items: MantineSuggestionMenuItemProps[] = [
export function getDefaultSlashMenuItems() {
return [
{
name: "Heading 1",
execute: () => {
closeMenu();
clearQuery();

title: "Heading 1",
// Unfortunately, we can't use a more specific BlockNoteEditor type here,
// Typescript seems to get in the way there
// This means that we don't have type checking for calling insertOrUpdateBlock etc. :(
onItemClick: (editor: BlockNoteEditor<any, any, any>) => {
insertOrUpdateBlock(editor, {
type: "heading",
props: { level: 1 },
} as PartialBlock<BSchema, I, S>);
} satisfies PartialBlock<DefaultBlockSchema, any, any>);
},
subtext: "Used for a top-level heading",
icon: <RiH1 size={18} />,
badge: formatKeyboardShortcut("Mod-Alt-1"),
aliases: ["h", "heading1", "h1"],
group: "Headings",
},
{
name: "Heading 2",
execute: () => {
closeMenu();
clearQuery();

title: "Heading 2",
onItemClick: (editor: BlockNoteEditor<any, any, any>) => {
insertOrUpdateBlock(editor, {
type: "heading",
props: { level: 2 },
} as PartialBlock<BSchema, I, S>);
} satisfies PartialBlock<DefaultBlockSchema, any, any>);
},
subtext: "Used for key sections",
icon: <RiH2 size={18} />,
badge: formatKeyboardShortcut("Mod-Alt-2"),
aliases: ["h2", "heading2", "subheading"],
group: "Headings",
},
{
name: "Heading 3",
execute: () => {
closeMenu();
clearQuery();

title: "Heading 3",
onItemClick: (editor: BlockNoteEditor<any, any, any>) => {
insertOrUpdateBlock(editor, {
type: "heading",
props: { level: 3 },
} as PartialBlock<BSchema, I, S>);
} satisfies PartialBlock<DefaultBlockSchema, any, any>);
},
subtext: "Used for subsections and group headings",
icon: <RiH3 size={18} />,
badge: formatKeyboardShortcut("Mod-Alt-3"),
aliases: ["h3", "heading3", "subheading"],
group: "Headings",
},
{
name: "Numbered List",
execute: () => {
closeMenu();
clearQuery();

insertOrUpdateBlock(editor, { type: "numberedListItem" });
title: "Numbered List",
onItemClick: (editor: BlockNoteEditor<any, any, any>) => {
insertOrUpdateBlock(editor, {
type: "numberedListItem",
} satisfies PartialBlock<DefaultBlockSchema, any, any>);
},
subtext: "Used to display a numbered list",
icon: <RiListOrdered size={18} />,
badge: formatKeyboardShortcut("Mod-Shift-7"),
aliases: ["ol", "li", "list", "numberedlist", "numbered list"],
group: "Basic blocks",
},
{
name: "Bullet List",
execute: () => {
closeMenu();
clearQuery();

insertOrUpdateBlock(editor, { type: "bulletListItem" });
title: "Bullet List",
onItemClick: (editor: BlockNoteEditor<any, any, any>) => {
insertOrUpdateBlock(editor, {
type: "bulletListItem",
} satisfies PartialBlock<DefaultBlockSchema, any, any>);
},
subtext: "Used to display an unordered list",
icon: <RiListUnordered size={18} />,
badge: formatKeyboardShortcut("Mod-Shift-8"),
aliases: ["ul", "li", "list", "bulletlist", "bullet list"],
group: "Basic blocks",
},
{
name: "Paragraph",
execute: () => {
closeMenu();
clearQuery();

insertOrUpdateBlock(editor, { type: "paragraph" });
title: "Paragraph",
onItemClick: (editor: BlockNoteEditor<any, any, any>) => {
insertOrUpdateBlock(editor, {
type: "paragraph",
} satisfies PartialBlock<DefaultBlockSchema, any, any>);
},
subtext: "Used for the body of your document",
icon: <RiText size={18} />,
badge: formatKeyboardShortcut("Mod-Alt-0"),
aliases: ["p", "paragraph"],
group: "Basic blocks",
},
{
name: "Table",
execute: () => {
closeMenu();
clearQuery();

title: "Table",
onItemClick: (editor: BlockNoteEditor<any, any, any>) => {
insertOrUpdateBlock(editor, {
type: "table",
content: {
Expand All @@ -212,22 +170,19 @@ export async function defaultGetItems<
},
],
},
} as PartialBlock<BSchema, I, S>);
} satisfies PartialBlock<DefaultBlockSchema, any, any>);
},
subtext: "Used for for tables",
icon: <RiTable2 size={18} />,
aliases: ["table"],
group: "Advanced",
badge: undefined,
},
{
name: "Image",
execute: () => {
closeMenu();
clearQuery();

title: "Image",
onItemClick: (editor: BlockNoteEditor<any, any, any>) => {
const insertedBlock = insertOrUpdateBlock(editor, {
type: "image",
});
} satisfies PartialBlock<DefaultBlockSchema, any, any>);

// Immediately open the image toolbar
editor._tiptapEditor.view.dispatch(
Expand All @@ -237,7 +192,6 @@ export async function defaultGetItems<
);
},
subtext: "Insert an image",
icon: <RiImage2Fill />,
aliases: [
"image",
"imageUpload",
Expand All @@ -250,29 +204,16 @@ export async function defaultGetItems<
"dropbox",
],
group: "Media",
} satisfies MantineSuggestionMenuItemProps,
];

// For testing async
// return new Promise((resolve) => {
// setTimeout(() => {
// console.log("return items");
// resolve(
// items.filter(
// ({ name, aliases }) =>
// name.toLowerCase().startsWith(query.toLowerCase()) ||
// (aliases &&
// aliases.filter((alias) =>
// alias.toLowerCase().startsWith(query.toLowerCase())
// ).length !== 0)
// )
// );
// }, 1000);
// });
},
] as const;
}

export function filterSuggestionItems<
T extends { title: string; aliases?: readonly string[] }
>(items: T[], query: string) {
return items.filter(
({ name, aliases }) =>
name.toLowerCase().startsWith(query.toLowerCase()) ||
({ title, aliases }) =>
title.toLowerCase().startsWith(query.toLowerCase()) ||
(aliases &&
aliases.filter((alias) =>
alias.toLowerCase().startsWith(query.toLowerCase())
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from "./editor/selectionTypes";
export * from "./extensions/FormattingToolbar/FormattingToolbarPlugin";
export * from "./extensions/HyperlinkToolbar/HyperlinkToolbarPlugin";
export * from "./extensions/ImageToolbar/ImageToolbarPlugin";
export * from "./extensions/SuggestionMenu/defaultSlashMenuItems";
export * from "./extensions/SuggestionMenu/SuggestionPlugin";
export * from "./extensions/SideMenu/SideMenuPlugin";
export * from "./extensions/TableHandles/TableHandlesPlugin";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { flip, offset } from "@floating-ui/react";
import { FC, useState } from "react";

import { useUIPluginState } from "../../hooks/useUIPluginState";
import { useUiElementPositioning } from "../../hooks/useUiElementPositioning";
import { useUIElementPositioning } from "../../hooks/useUIElementPositioning";
import { useEditorChange } from "../../hooks/useEditorChange";
import {
DefaultFormattingToolbar,
Expand Down Expand Up @@ -67,7 +67,7 @@ export const DefaultPositionedFormattingToolbar = <
const state = useUIPluginState(
props.editor.formattingToolbar.onUpdate.bind(props.editor.formattingToolbar)
);
const { isMounted, ref, style } = useUiElementPositioning(
const { isMounted, ref, style } = useUIElementPositioning(
state?.show || false,
state?.referencePos || null,
3000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { flip, offset } from "@floating-ui/react";
import { FC } from "react";

import { useUIPluginState } from "../../hooks/useUIPluginState";
import { useUiElementPositioning } from "../../hooks/useUiElementPositioning";
import { useUIElementPositioning } from "../../hooks/useUIElementPositioning";
import {
DefaultHyperlinkToolbar,
HyperlinkToolbarProps,
Expand All @@ -35,7 +35,7 @@ export const DefaultPositionedHyperlinkToolbar = <
const state = useUIPluginState(
props.editor.hyperlinkToolbar.onUpdate.bind(props.editor.hyperlinkToolbar)
);
const { isMounted, ref, style } = useUiElementPositioning(
const { isMounted, ref, style } = useUIElementPositioning(
state?.show || false,
state?.referencePos || null,
4000,
Expand Down
Loading