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
@@ -42,10 +42,10 @@ export const FileBlock = createReactBlockSpec(
type: 'file',
propSchema: {
url: {
default: '' as string,
default: '',
},
name: {
default: '' as string,
default: '',
},
fileCategory: {
default: 'OTHER' as AttachmentFileCategory,
@@ -10,7 +10,7 @@ import { MentionInlineContent } from '@/blocknote-editor/blocks/MentionInlineCon
export const BLOCK_SCHEMA = BlockNoteSchema.create({
blockSpecs: {
...defaultBlockSpecs,
file: FileBlock,
file: FileBlock(),
},
inlineContentSpecs: {
...defaultInlineContentSpecs,
@@ -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;
@@ -1,6 +1,6 @@
import { getDefaultReactSlashMenuItems } from '@blocknote/react';
import { type SuggestionItem } from '@/blocknote-editor/components/CustomSlashMenu';
import { type SuggestionItem } from '@/blocknote-editor/types/types';
import { type BLOCK_SCHEMA } from '@/blocknote-editor/blocks/Schema';
import {
@@ -11,10 +11,15 @@ import {
IconH1,
IconH2,
IconH3,
IconH4,
IconH5,
IconH6,
IconHeadphones,
IconList,
IconListCheck,
IconListDetails,
IconListNumbers,
IconMinus,
IconMoodSmile,
IconPhoto,
IconPilcrow,
@@ -26,6 +31,14 @@ const Icons: Record<string, IconComponent> = {
'Heading 1': IconH1,
'Heading 2': IconH2,
'Heading 3': IconH3,
'Heading 4': IconH4,
'Heading 5': IconH5,
'Heading 6': IconH6,
'Toggle Heading 1': IconH1,
'Toggle Heading 2': IconH2,
'Toggle Heading 3': IconH3,
'Toggle List': IconListDetails,
Divider: IconMinus,
Quote: IconBlockquote,
'Numbered List': IconListNumbers,
'Bullet List': IconList,
@@ -41,10 +54,12 @@ const Icons: Record<string, IconComponent> = {
export const getSlashMenu = (editor: typeof BLOCK_SCHEMA.BlockNoteEditor) => {
const items: SuggestionItem[] = [
...getDefaultReactSlashMenuItems(editor).map((x) => ({
...x,
Icon: Icons[x.title],
})),
...getDefaultReactSlashMenuItems(editor)
.filter((item) => item.title !== 'File')
.map((x) => ({
...x,
Icon: Icons[x.title],
})),
{
title: 'File',
aliases: ['file', 'folder'],