Upgrade blocknote dependencies from 0.31.1 to 0.47.0. (#18207)

This PR pgrades all BlockNote packages (@blocknote/core,
@blocknote/react, @blocknote/mantine, @blocknote/server-util,
@blocknote/xl-docx-exporter, @blocknote/xl-pdf-exporter) to 0.47.0 and
adapts the codebase to the new API.

### Changes
- Dependency upgrades: Bumped all BlockNote packages to 0.47.0, added
required Mantine v8 peer dependencies, removed unnecessary prosemirror
resolutions
- Formatting toolbar: Replaced the manual reimplementation of
FormattingToolbarController (which handled visibility, positioning,
portal rendering, text-alignment-based placement, and a
dangerouslySetInnerHTML transition trick) with BlockNote's built-in
FormattingToolbarController. The toolbar buttons themselves are
unchanged.
- Side menu: Replaced manual drag handle menu positioning and rendering
(DashboardBlockDragHandleMenu, DashboardBlockColorPicker, and their
floating configs) with BlockNote's built-in SideMenuController,
DragHandleButton, and DragHandleMenu components. Deleted 4 files that
became dead code.
- Extension API migration: Replaced deprecated editor.suggestionMenus
and editor.formattingToolbar APIs with the new extension system
(SuggestionMenu, useExtensionState, editor.getExtension())
- Slash menu fixes: Filtered out BlockNote's new default "File" item
(added in 0.47) to avoid duplicates with our custom one; added icon
mappings for new block types (Toggle List, Divider, Toggle Headings,
Headings 4-6)
- Server-side: Switched @blocknote/server-util to dynamic import() to
handle ESM-only transitive dependencies in CJS context
This commit is contained in:
Abdullah.
2026-02-27 13:16:49 +05:00
committed by GitHub
parent def5ea5764
commit 4ed09a3feb
26 changed files with 851 additions and 1220 deletions
@@ -1,4 +1,4 @@
import { filterSuggestionItems } from '@blocknote/core';
import { filterSuggestionItems } from '@blocknote/core/extensions';
import { BlockNoteView } from '@blocknote/mantine';
import { SuggestionMenuController } from '@blocknote/react';
import { useTheme } from '@emotion/react';
@@ -1,18 +1,16 @@
import { type BLOCK_SCHEMA } from '@/blocknote-editor/blocks/Schema';
import { SuggestionMenu } from '@blocknote/core/extensions';
import { useExtensionState } from '@blocknote/react';
import { useStore } from 'jotai';
import { useCallback, useState } from 'react';
import { isSlashMenuOpenComponentState } from '@/blocknote-editor/states/isSlashMenuOpenComponentState';
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
import { useStore } from 'jotai';
import { useCallback } from 'react';
export type BlockEditorDropdownFocusEffectProps = {
editor: typeof BLOCK_SCHEMA.BlockNoteEditor;
};
export const BlockEditorDropdownFocusEffect = () => {
const [prevShowing, setPrevShowing] = useState<boolean | null>(null);
export const BlockEditorDropdownFocusEffect = ({
editor,
}: BlockEditorDropdownFocusEffectProps) => {
const isSlashMenuOpenState = useAtomComponentStateCallbackState(
isSlashMenuOpenComponentState,
);
@@ -25,30 +23,21 @@ export const BlockEditorDropdownFocusEffect = ({
const store = useStore();
const updateCallBack = useCallback(
(event: any) => {
const eventWantsToOpen = event.show === true;
const suggestionState = useExtensionState(SuggestionMenu);
const isSlashMenuShowing =
suggestionState?.show === true && suggestionState?.triggerCharacter === '/';
const syncSlashMenuState = useCallback(
(showing: boolean) => {
const isAlreadyOpen = store.get(isSlashMenuOpenState);
const shouldOpen = eventWantsToOpen && !isAlreadyOpen;
if (shouldOpen) {
if (showing && isAlreadyOpen !== true) {
setActiveDropdownFocusIdAndMemorizePrevious('custom-slash-menu');
store.set(isSlashMenuOpenState, true);
return;
}
const eventWantsToClose = event.show === false;
const isAlreadyClosed = !isAlreadyOpen;
const shouldClose = eventWantsToClose && !isAlreadyClosed;
if (shouldClose) {
} else if (!showing && isAlreadyOpen === true) {
goBackToPreviousDropdownFocusId();
store.set(isSlashMenuOpenState, false);
return;
}
},
[
@@ -59,7 +48,10 @@ export const BlockEditorDropdownFocusEffect = ({
],
);
editor.suggestionMenus.on('update /', updateCallBack);
if (prevShowing !== isSlashMenuShowing) {
setPrevShowing(isSlashMenuShowing);
syncSlashMenuState(isSlashMenuShowing);
}
return <></>;
};
@@ -1,42 +1,36 @@
import { type BLOCK_SCHEMA } from '@/blocknote-editor/blocks/Schema';
import { useComponentsContext } from '@blocknote/react';
import { SuggestionMenu } from '@blocknote/core/extensions';
import { useBlockNoteEditor, useComponentsContext } from '@blocknote/react';
type CustomAddBlockItemProps = {
editor: typeof BLOCK_SCHEMA.BlockNoteEditor;
children: React.ReactNode; // Adding the children prop
children: React.ReactNode;
};
type ContentItem = {
type: string;
text: string;
styles: any;
};
export const CustomAddBlockItem = ({
editor,
children,
}: CustomAddBlockItemProps) => {
const Components = useComponentsContext();
const blockNoteEditor = useBlockNoteEditor();
if (!Components) {
return null;
}
const handleClick = () => {
const blockIdentifier = editor.getTextCursorPosition().block;
const currentBlockContent = blockIdentifier?.content as
| Array<ContentItem>
| undefined;
const currentBlock = blockNoteEditor.getTextCursorPosition().block;
const blockContent = currentBlock?.content;
const [firstElement] = currentBlockContent || [];
const isEmpty = Array.isArray(blockContent) && blockContent.length === 0;
if (firstElement === undefined) {
editor.openSuggestionMenu('/');
} else {
editor.openSuggestionMenu('/');
editor.sideMenu.unfreezeMenu();
if (isEmpty) {
editor.setTextCursorPosition(currentBlock);
}
blockNoteEditor.getExtension(SuggestionMenu)?.openSuggestionMenu('/');
};
return (
<Components.Generic.Menu.Item onClick={handleClick}>
{children}
@@ -25,15 +25,11 @@ export const CustomSideMenu = ({ editor }: CustomSideMenuProps) => {
const { t } = useLingui();
return (
<SideMenuController
sideMenu={(props) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<SideMenu {...props}>
sideMenu={() => (
<SideMenu>
<DragHandleButton
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
dragHandleMenu={(props) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<DragHandleMenu {...props}>
dragHandleMenu={() => (
<DragHandleMenu>
<CustomAddBlockItem editor={editor}>
<CustomSideMenuOptions
LeftIcon={IconPlus}
@@ -41,17 +37,14 @@ export const CustomSideMenu = ({ editor }: CustomSideMenuProps) => {
Variant="normal"
/>
</CustomAddBlockItem>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<BlockColorsItem {...props}>
<BlockColorsItem>
<CustomSideMenuOptions
LeftIcon={IconColorSwatch}
text={t`Change Color`}
Variant="normal"
/>
</BlockColorsItem>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<RemoveBlockItem {...props}>
{' '}
<RemoveBlockItem>
<CustomSideMenuOptions
LeftIcon={IconTrash}
text={t`Delete`}
@@ -33,7 +33,7 @@ export const CustomSlashMenu = ({
const currentBlock = editor?.getTextCursorPosition()?.block;
const blockType = currentBlock?.type;
const headingLevel =
blockType === 'heading' ? (currentBlock?.props?.level as number) : null;
blockType === 'heading' ? Number(currentBlock?.props?.level) : null;
const getOffsetValue = (placement: string) => {
if (!placement.startsWith('top')) return 0;