diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json
index 28dc88295..5ef956bde 100644
--- a/src/frontend/apps/impress/package.json
+++ b/src/frontend/apps/impress/package.json
@@ -39,6 +39,7 @@
"@dnd-kit/modifiers": "9.0.0",
"@emoji-mart/data": "1.2.1",
"@emoji-mart/react": "1.1.1",
+ "@floating-ui/react": "^0.27.19",
"@fontsource-variable/inter": "5.3.0",
"@fontsource-variable/material-symbols-outlined": "5.3.3",
"@fontsource/material-icons": "5.3.0",
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/DocsDragHandleMenu.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/DocsDragHandleMenu.tsx
new file mode 100644
index 000000000..198f246f0
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/DocsDragHandleMenu.tsx
@@ -0,0 +1,66 @@
+/**
+ * Overrides the default DragHandleMenu with a custom implementation for the Docs editor.
+ * See: https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/SideMenu/DragHandleMenu/DragHandleMenu.tsx
+ */
+import {
+ BlockColorsItem,
+ DragHandleMenu,
+ RemoveBlockItem,
+ TableColumnHeaderItem,
+ TableRowHeaderItem,
+ useComponentsContext,
+ useDictionary,
+} from '@blocknote/react';
+
+import { Box } from '@/components';
+import { PresentBlockItem } from '@/docs/doc-presenter/components/PresentBlockItem';
+import ColorIcon from '@/icons/color.svg';
+import TableHeaderColumnIcon from '@/icons/table-header-column.svg';
+import TableHeaderRowIcon from '@/icons/table-header-row.svg';
+import TrashIcon from '@/icons/trash.svg';
+
+import { LinkToBlockItem } from './LinkToBlockItem';
+import { SideMenuSeparator } from './SideMenuSeparator';
+
+export const DocsDragHandleMenu = () => {
+ const Components = useComponentsContext();
+ const dict = useDictionary();
+
+ if (Components === undefined) {
+ return null;
+ }
+
+ return (
+
+
+
+
+ {dict.drag_handle.colors_menuitem}
+
+
+
+
+
+
+
+
+
+ {dict.drag_handle.header_row_menuitem}
+
+
+
+
+
+ {dict.drag_handle.header_column_menuitem}
+
+
+
+
+
+
+ {dict.drag_handle.delete_menuitem}
+
+
+
+ );
+};
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/DocsSideMenu.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/DocsSideMenu.tsx
index 529a6ede2..fcacad77b 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/DocsSideMenu.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/DocsSideMenu.tsx
@@ -1,74 +1,105 @@
/**
- * Overrides the default DragHandleMenu with a custom implementation for the Docs editor.
- * See: https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/SideMenu/DragHandleMenu/DragHandleMenu.tsx
+ * Overrides the default SideMenu with a custom implementation for the Docs editor.
+ * See: https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/SideMenu/SideMenu.tsx
*/
+import type { Block } from '@blocknote/core';
+import { SideMenuExtension } from '@blocknote/core/extensions';
import {
- BlockColorsItem,
- DragHandleMenu,
- RemoveBlockItem,
SideMenu,
SideMenuController,
- TableColumnHeaderItem,
- TableRowHeaderItem,
- useComponentsContext,
- useDictionary,
+ useBlockNoteEditor,
+ useExtensionState,
} from '@blocknote/react';
+import { offset } from '@floating-ui/react';
+import { useMemo } from 'react';
-import { Box, HorizontalSeparator } from '@/components';
-import { PresentBlockItem } from '@/docs/doc-presenter/components/PresentBlockItem';
-import ColorIcon from '@/icons/color.svg';
-import TableHeaderColumnIcon from '@/icons/table-header-column.svg';
-import TableHeaderRowIcon from '@/icons/table-header-row.svg';
-import TrashIcon from '@/icons/trash.svg';
+import type {
+ DocsBlockNoteEditor,
+ DocsBlockSchema,
+ DocsInlineContentSchema,
+ DocsStyleSchema,
+} from '../../types';
-import { LinkToBlockItem } from './LinkToBlockItem';
-import { TableHeaderSeparator } from './TableHeaderSeparator';
+import { DocsDragHandleMenu } from './DocsDragHandleMenu';
-const DocsDragHandleMenu = () => {
- const Components = useComponentsContext();
- const dict = useDictionary();
-
- if (Components === undefined) {
- return null;
+/**
+ * Blocknote v0.54.0
+ * See: https://github.com/TypeCellOS/BlockNote/blob/main/packages/react/src/components/SideMenu/SideMenuController.tsx#L21
+ *
+ * Blocknote changed the way the side menu is positioned.
+ * Because we are modifying the height of the heading blocks we need to adjust the offset of the side menu to match the
+ * new height of the heading blocks.
+ *
+ * @todo Try to see with Blocknote if we can have a better way to handle this, maybe a prop to set the offset of the side menu.
+ */
+function getBlockOffset(
+ editor: DocsBlockNoteEditor,
+ block: Block,
+): number {
+ if (block.type === 'heading') {
+ switch (block.props.level) {
+ case 1:
+ return 13;
+ case 2:
+ return 7;
+ case 3:
+ return 4;
+ default:
+ return 0;
+ }
}
+ // File blocks without a URL all render the same "Add file" button,
+ // regardless of their type.
+ if (
+ editor.schema.blockSpecs[block.type]?.implementation.meta
+ ?.fileBlockAccept &&
+ (!('url' in block.props) || !block.props.url)
+ ) {
+ return 12;
+ }
+
+ if (block.type === 'file') {
+ return 4;
+ }
+
+ if (block.type === 'audio' || block.type === 'table') {
+ return 15;
+ }
+
+ return 0;
+}
+
+export const DocsSideMenu = () => {
+ const editor = useBlockNoteEditor<
+ DocsBlockSchema,
+ DocsInlineContentSchema,
+ DocsStyleSchema
+ >();
+ const block = useExtensionState(SideMenuExtension, {
+ selector: (state) =>
+ state?.block as Block<
+ DocsBlockSchema,
+ DocsInlineContentSchema,
+ DocsStyleSchema
+ > | null,
+ });
+
+ const floatingUIOptions = useMemo(
+ () => ({
+ useFloatingOptions: {
+ middleware: [
+ offset({ crossAxis: block ? getBlockOffset(editor, block) : 0 }),
+ ],
+ },
+ }),
+ [editor, block],
+ );
+
return (
-
-
-
-
- {dict.drag_handle.colors_menuitem}
-
-
-
-
-
-
-
-
-
- {dict.drag_handle.header_row_menuitem}
-
-
-
-
-
- {dict.drag_handle.header_column_menuitem}
-
-
-
-
-
-
- {dict.drag_handle.delete_menuitem}
-
-
-
+ }
+ />
);
};
-
-export const DocsSideMenu = () => (
- }
- />
-);
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/SideMenuSeparator.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/SideMenuSeparator.tsx
new file mode 100644
index 000000000..8909f6922
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/SideMenuSeparator.tsx
@@ -0,0 +1,38 @@
+import { SideMenuExtension } from '@blocknote/core/extensions';
+import { useBlockNoteEditor, useExtensionState } from '@blocknote/react';
+
+import { type BoxType, HorizontalSeparator } from '@/components';
+
+import type { DocsBlockNoteEditor } from '../../types';
+
+type SideMenuSeparatorProps = {
+ typeDisplay?: 'table';
+ typeHide?: 'codeBlock';
+} & BoxType;
+
+export const SideMenuSeparator = ({
+ typeDisplay,
+ typeHide,
+ ...props
+}: SideMenuSeparatorProps) => {
+ const editor: DocsBlockNoteEditor = useBlockNoteEditor();
+ const block = useExtensionState(SideMenuExtension, {
+ editor,
+ selector: (state) => state?.block,
+ });
+
+ if (
+ typeDisplay &&
+ (block === undefined ||
+ block.type !== typeDisplay ||
+ !editor.settings.tables.headers)
+ ) {
+ return null;
+ }
+
+ if (typeHide && (block === undefined || block.type === typeHide)) {
+ return null;
+ }
+
+ return ;
+};
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/TableHeaderSeparator.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/TableHeaderSeparator.tsx
deleted file mode 100644
index bae9030f2..000000000
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocsSideMenu/TableHeaderSeparator.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { SideMenuExtension } from '@blocknote/core/extensions';
-import { useBlockNoteEditor, useExtensionState } from '@blocknote/react';
-
-import { HorizontalSeparator } from '@/components';
-
-import type { DocsBlockNoteEditor } from '../../types';
-
-export const TableHeaderSeparator = () => {
- const editor: DocsBlockNoteEditor = useBlockNoteEditor();
- const block = useExtensionState(SideMenuExtension, {
- editor,
- selector: (state) => state?.block,
- });
-
- if (
- block === undefined ||
- block.type !== 'table' ||
- !editor.settings.tables.headers
- ) {
- return null;
- }
-
- return ;
-};
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx
index d5bc96493..f43d5f014 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx
@@ -192,9 +192,6 @@ export const DocsEditorStyle = createGlobalStyle`
var(--c--contextuals--border--semantic--brand--tertiary);
background: var(--c--contextuals--border--semantic--brand--secondary);
}
- .bn-side-menu[data-block-type='divider'] {
- height: 28px;
- }
/**
* Checklist items
@@ -251,15 +248,6 @@ export const DocsEditorStyle = createGlobalStyle`
--level: 0.875rem;
}
}
- .bn-side-menu[data-block-type='heading'][data-level='1'] {
- height: 54px;
- }
- .bn-side-menu[data-block-type='heading'][data-level='2'] {
- height: 43px;
- }
- .bn-side-menu[data-block-type='heading'][data-level='3'] {
- height: 35px;
- }
& .bn-default-styles h1 {
font-size: 1.875rem;
}
@@ -309,15 +297,6 @@ export const DocsEditorStyle = createGlobalStyle`
& .bn-editor {
padding-right: 10px;
}
- .bn-side-menu[data-block-type='heading'][data-level='1'] {
- height: 46px;
- }
- .bn-side-menu[data-block-type='heading'][data-level='2'] {
- height: 40px;
- }
- .bn-side-menu[data-block-type='heading'][data-level='3'] {
- height: 40px;
- }
[data-content-type='heading'] {
--level: 1.6rem;
&[data-level='2'] {