Skip to content

fix: Custom UI element API #177

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 3 commits into from
Apr 20, 2023
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
3 changes: 2 additions & 1 deletion packages/core/src/BlockNoteEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ export type BlockNoteEditorOptions = {
enableBlockNoteExtensions: boolean;
disableHistoryExtension: boolean;
/**
* Factories used to create a custom UI for BlockNote
* UI element factories for creating a custom UI, including custom positioning
* & rendering.
*/
uiFactories: UiFactories;
/**
Expand Down
65 changes: 46 additions & 19 deletions packages/react/src/hooks/useBlockNote.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { BlockNoteEditor, BlockNoteEditorOptions } from "@blocknote/core";
import { DependencyList, useEffect, useState } from "react";
import { DependencyList, FC, useEffect, useState } from "react";
import { createReactBlockSideMenuFactory } from "../BlockSideMenu/BlockSideMenuFactory";
import { createReactFormattingToolbarFactory } from "../FormattingToolbar/FormattingToolbarFactory";
import { createReactHyperlinkToolbarFactory } from "../HyperlinkToolbar/HyperlinkToolbarFactory";
import { createReactSlashMenuFactory } from "../SlashMenu/SlashMenuFactory";
import { defaultReactSlashMenuItems } from "../SlashMenu/defaultReactSlashMenuItems";
import { getBlockNoteTheme } from "../BlockNoteTheme";
import { DragHandleMenuProps } from "../BlockSideMenu/components/DragHandleMenu";

//based on https://github.com/ueberdosis/tiptap/blob/main/packages/react/src/useEditor.ts

type CustomElements = Partial<{
formattingToolbar: FC<{ editor: BlockNoteEditor }>;
dragHandleMenu: FC<DragHandleMenuProps>;
}>;

function useForceUpdate() {
const [, setValue] = useState(0);

Expand All @@ -18,7 +25,11 @@ function useForceUpdate() {
* Main hook for importing a BlockNote editor into a React project
*/
export const useBlockNote = (
options: Partial<BlockNoteEditorOptions> = {},
options: Partial<
BlockNoteEditorOptions & {
customElements: CustomElements;
}
> = {},
deps: DependencyList = []
) => {
const [editor, setEditor] = useState<BlockNoteEditor | null>(null);
Expand All @@ -33,25 +44,41 @@ export const useBlockNote = (
slashCommands: defaultReactSlashMenuItems,
...options,
};
if (!newOptions.uiFactories) {
newOptions = {
...newOptions,
uiFactories: {
formattingToolbarFactory: createReactFormattingToolbarFactory(
getBlockNoteTheme(newOptions.theme === "dark")
),
hyperlinkToolbarFactory: createReactHyperlinkToolbarFactory(
getBlockNoteTheme(newOptions.theme === "dark")
),
slashMenuFactory: createReactSlashMenuFactory(
getBlockNoteTheme(newOptions.theme === "dark")
),
blockSideMenuFactory: createReactBlockSideMenuFactory(
getBlockNoteTheme(newOptions.theme === "dark")
),
},

let uiFactories: any;

if (newOptions.customElements && newOptions.uiFactories) {
console.warn(
"BlockNote editor initialized with both `customElements` and `uiFactories` options, prioritizing `uiFactories`."
);
}

if (newOptions.uiFactories) {
uiFactories = newOptions.uiFactories;
} else {
uiFactories = {
formattingToolbarFactory: createReactFormattingToolbarFactory(
getBlockNoteTheme(newOptions.theme === "dark"),
newOptions.customElements?.formattingToolbar
),
hyperlinkToolbarFactory: createReactHyperlinkToolbarFactory(
getBlockNoteTheme(newOptions.theme === "dark")
),
slashMenuFactory: createReactSlashMenuFactory(
getBlockNoteTheme(newOptions.theme === "dark")
),
blockSideMenuFactory: createReactBlockSideMenuFactory(
getBlockNoteTheme(newOptions.theme === "dark"),
newOptions.customElements?.dragHandleMenu
),
};
}

newOptions = {
...newOptions,
uiFactories: uiFactories,
};

console.log("create new blocknote instance");
const instance = new BlockNoteEditor(
newOptions as Partial<BlockNoteEditorOptions>
Expand Down
14 changes: 11 additions & 3 deletions packages/website/docs/docs/editor.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Customizing the Editor

While you can get started with BlockNote in minutes, it's likely that you'll want to customize its features and functionality to better suit your app.
While you can get started with BlockNote in minutes, it's likely that you'll
want to customize its features and functionality to better suit your app.

## Editor Options

There are a number of options that you can pass to `useBlockNote()`, which you can use to customize the editor. You can find the full list of these below:
There are a number of options that you can pass to `useBlockNote()`, which you
can use to customize the editor. You can find the full list of these below:

```typescript
export type BlockNoteEditorOptions = Partial<{
Expand All @@ -15,8 +17,10 @@ export type BlockNoteEditorOptions = Partial<{
onEditorContentChange: (editor: BlockNoteEditor) => void;
onTextCursorPositionChange: (editor: BlockNoteEditor) => void;
slashMenuItems: ReactSlashMenuItem[];
customElements: CustomElements;
uiFactories: UiFactories;
defaultStyles: boolean;
theme: "light" | "dark";
}>;
```

Expand All @@ -34,10 +38,14 @@ export type BlockNoteEditorOptions = Partial<{

`slashMenuItems:` The commands that are listed in the editor's [Slash Menu](/docs/slash-menu). If this option isn't defined, a default list of commands is loaded.

`uiFactories:` Factories used to create a custom UI for BlockNote, which you can find out more about in [Creating Your Own UI Elements](/docs/vanilla-js#creating-your-own-ui-elements).
`customElements:` React components for a custom [Formatting Toolbar](/docs/formatting-toolbar#custom-formatting-toolbar) and/or [Drag Handle Menu](/docs/side-menu#custom-drag-handle-menu) to use.

`uiFactories:` UI element factories for creating a custom UI, including custom positioning & rendering. You can find out more about UI factories in [Creating Your Own UI Elements](/docs/vanilla-js#creating-your-own-ui-elements).

`defaultStyles`: Whether to use the default font and reset the styles of `<p>`, `<li>`, `<h1>`, etc. elements that are used in BlockNote. Defaults to true if undefined.

`theme:` Whether to use the light or dark theme.

## Demo: Saving & Restoring Editor Contents

By default, BlockNote doesn't preserve the editor contents when your app is reopened or refreshed. However, using the editor options, you can change this by using the editor options.
Expand Down
17 changes: 7 additions & 10 deletions packages/website/docs/docs/formatting-toolbar.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ type CustomFormattingToolbarProps = {
const CustomFormattingToolbar = (props: CustomFormattingToolbarProps): JSX.Element => ...;
```

You can then tell BlockNote to use your custom Formatting Toolbar using the `uiFactories` option in `useBlockNote`, but you first have to turn it into a `FormattingToolbarFactory`. Fortunately, you can easily do this using the `createReactFormattingToolbarFactory` function:
You can then tell BlockNote to use your custom Formatting Toolbar using
the `customElements` option in `useBlockNote`:

```typescript
const editor = useBlockNote({
uiFactories: {
formattingToolbarFactory: createReactFormattingToolbarFactory(
CustomFormattingToolbar
),
customElements: {
formattingToolbar: CustomFormattingToolbar
},
});
```
Expand Down Expand Up @@ -180,12 +179,10 @@ const CustomFormattingToolbar = (props: { editor: BlockNoteEditor }) => {
export default function App() {
// Creates a new editor instance.
const editor: BlockNoteEditor = useBlockNote({
uiFactories: {
customElements: {
// Makes the editor instance use the custom toolbar.
formattingToolbarFactory: createReactFormattingToolbarFactory(
CustomFormattingToolbar
),
},
formattingToolbar: CustomFormattingToolbar
}
});

// Renders the editor instance.
Expand Down
14 changes: 6 additions & 8 deletions packages/website/docs/docs/side-menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,9 @@ const CustomDragHandleMenu = (props: {
export default function App() {
// Creates a new editor instance.
const editor: BlockNoteEditor = useBlockNote({
uiFactories: {
customElements: {
// Makes the editor instance use the custom menu.
blockSideMenuFactory:
createReactBlockSideMenuFactory(CustomDragHandleMenu),
dragHandleMenu: CustomDragHandleMenu
},
});
// Renders the editor instance.
Expand All @@ -77,14 +76,13 @@ type CustomDragHandleMenuProps = {
const CustomDragHandleMenu = (props: CustomDragHandleMenuProps): JSX.Element => ...;
```

You can then tell BlockNote to use your custom Drag Handle Menu using the `uiFactories` option in `useBlockNote`, but you first have to turn it into a `SideMenuFactory` that uses it. Fortunately, you can easily do this using the `createReactSideMenuFactory` function:
You can then tell BlockNote to use your custom Drag Handle Menu using
the `customElements` option in `useBlockNote`:

```typescript
const editor = useBlockNote({
uiFactories: {
blockSideMenuFactory: createReactBlockSideMenuFactory(
CustomBlockSideMenu
),
customElements: {
blockSideMenuFactory: CustomBlockSideMenu
},
});
```
Expand Down