diff --git a/CHANGELOG.md b/CHANGELOG.md
index 99ecaa94b..4d196db66 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,10 @@ and this project adheres to
## [2.0.1] - 2025-01-17
+## Added
+
+✨(frontend) add multi columns support for editor #533
+
## Fixed
-🐛(frontend) share modal is shown when you don't have the abilities #557
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts
index 8dba8dab5..673a03221 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts
@@ -366,4 +366,27 @@ test.describe('Doc Editor', () => {
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
});
+
+ test('it checks the multi columns', async ({ page, browserName }) => {
+ await createDoc(page, 'doc-multi-columns', browserName, 1);
+
+ await page.locator('.bn-block-outer').last().fill('/');
+
+ await page.getByText('Three Columns', { exact: true }).click();
+
+ await page.locator('.bn-block-column').first().fill('Column 1');
+ await page.locator('.bn-block-column').nth(1).fill('Column 2');
+ await page.locator('.bn-block-column').last().fill('Column 3');
+
+ expect(await page.locator('.bn-block-column').count()).toBe(3);
+ await expect(
+ page.locator('.bn-block-column[data-node-type="column"]').first(),
+ ).toHaveText('Column 1');
+ await expect(
+ page.locator('.bn-block-column[data-node-type="column"]').nth(1),
+ ).toHaveText('Column 2');
+ await expect(
+ page.locator('.bn-block-column[data-node-type="column"]').last(),
+ ).toHaveText('Column 3');
+ });
});
diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json
index 658588e53..be7f681ec 100644
--- a/src/frontend/apps/impress/package.json
+++ b/src/frontend/apps/impress/package.json
@@ -18,6 +18,7 @@
"@blocknote/core": "0.21.0",
"@blocknote/mantine": "0.21.0",
"@blocknote/react": "0.21.0",
+ "@blocknote/xl-multi-column": "0.21.0",
"@gouvfr-lasuite/integration": "1.0.2",
"@hocuspocus/provider": "2.15.0",
"@openfun/cunningham-react": "2.9.4",
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx
index 326163ddb..1930e23f1 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx
@@ -1,10 +1,24 @@
-import { Dictionary, locales } from '@blocknote/core';
+import {
+ Dictionary,
+ combineByGroup,
+ filterSuggestionItems,
+ locales,
+} from '@blocknote/core';
import '@blocknote/core/fonts/inter.css';
import { BlockNoteView } from '@blocknote/mantine';
import '@blocknote/mantine/style.css';
-import { useCreateBlockNote } from '@blocknote/react';
+import {
+ SuggestionMenuController,
+ getDefaultReactSlashMenuItems,
+ useCreateBlockNote,
+} from '@blocknote/react';
+import {
+ getMultiColumnSlashMenuItems,
+ multiColumnDropCursor,
+ locales as multiColumnLocales,
+} from '@blocknote/xl-multi-column';
import { HocuspocusProvider } from '@hocuspocus/provider';
-import { useEffect } from 'react';
+import { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import * as Y from 'yjs';
@@ -17,7 +31,7 @@ import { useUploadFile } from '../hook';
import { useHeadings } from '../hook/useHeadings';
import useSaveDoc from '../hook/useSaveDoc';
import { useEditorStore } from '../stores';
-import { randomColor } from '../utils';
+import { blockNoteWithMultiColumn, randomColor } from '../utils';
import { BlockNoteToolbar } from './BlockNoteToolbar';
@@ -163,8 +177,14 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
return cursor;
},
},
- dictionary: locales[lang as keyof typeof locales] as Dictionary,
+ dictionary: {
+ ...(locales[lang as keyof typeof locales] as Dictionary),
+ multi_column:
+ multiColumnLocales[lang as keyof typeof multiColumnLocales],
+ },
uploadFile,
+ schema: blockNoteWithMultiColumn,
+ dropCursor: multiColumnDropCursor,
},
[collabName, lang, provider, uploadFile],
);
@@ -178,6 +198,18 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
};
}, [setEditor, editor]);
+ const getSlashMenuItems = useMemo(() => {
+ // eslint-disable-next-line @typescript-eslint/require-await
+ return async (query: string) =>
+ filterSuggestionItems(
+ combineByGroup(
+ getDefaultReactSlashMenuItems(editor),
+ getMultiColumnSlashMenuItems(editor),
+ ),
+ query,
+ );
+ }, [editor]);
+
return (
{
formattingToolbar={false}
editable={!readOnly}
theme="light"
+ slashMenu={false}
>
+
@@ -225,6 +262,7 @@ export const BlockNoteEditorVersion = ({
},
provider: undefined,
},
+ schema: blockNoteWithMultiColumn,
},
[initialContent],
);
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useHeadings.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useHeadings.tsx
index 9468a7963..8b88eb3d6 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useHeadings.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useHeadings.tsx
@@ -1,9 +1,9 @@
-import { BlockNoteEditor } from '@blocknote/core';
import { useEffect } from 'react';
import { useHeadingStore } from '../stores';
+import { DocsBlockNoteEditor } from '../types';
-export const useHeadings = (editor: BlockNoteEditor) => {
+export const useHeadings = (editor: DocsBlockNoteEditor) => {
const { setHeadings, resetHeadings } = useHeadingStore();
useEffect(() => {
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useEditorStore.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useEditorStore.tsx
index 025f2ad8e..9a846b37c 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useEditorStore.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useEditorStore.tsx
@@ -1,9 +1,10 @@
-import { BlockNoteEditor } from '@blocknote/core';
import { create } from 'zustand';
+import { DocsBlockNoteEditor } from '../types';
+
export interface UseEditorstore {
- editor?: BlockNoteEditor;
- setEditor: (editor: BlockNoteEditor | undefined) => void;
+ editor?: DocsBlockNoteEditor;
+ setEditor: (editor: DocsBlockNoteEditor | undefined) => void;
}
export const useEditorStore = create((set) => ({
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useHeadingStore.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useHeadingStore.tsx
index ac9b8a4b3..e67800c6c 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useHeadingStore.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/stores/useHeadingStore.tsx
@@ -1,7 +1,6 @@
-import { BlockNoteEditor } from '@blocknote/core';
import { create } from 'zustand';
-import { HeadingBlock } from '../types';
+import { DocsBlockNoteEditor, HeadingBlock } from '../types';
const recursiveTextContent = (content: HeadingBlock['content']): string => {
if (!content) {
@@ -21,7 +20,7 @@ const recursiveTextContent = (content: HeadingBlock['content']): string => {
export interface UseHeadingStore {
headings: HeadingBlock[];
- setHeadings: (editor: BlockNoteEditor) => void;
+ setHeadings: (editor: DocsBlockNoteEditor) => void;
resetHeadings: () => void;
}
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/types.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/types.tsx
index 19094b6c5..634f85c61 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/types.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/types.tsx
@@ -1,3 +1,7 @@
+import { BlockNoteEditor } from '@blocknote/core';
+
+import { blockNoteWithMultiColumn } from './utils';
+
export interface DocAttachment {
file: string;
}
@@ -12,3 +16,9 @@ export type HeadingBlock = {
level: number;
};
};
+
+export type DocsBlockNoteEditor = BlockNoteEditor<
+ typeof blockNoteWithMultiColumn.blockSchema,
+ typeof blockNoteWithMultiColumn.inlineContentSchema,
+ typeof blockNoteWithMultiColumn.styleSchema
+>;
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/utils.ts b/src/frontend/apps/impress/src/features/docs/doc-editor/utils.ts
index a3d311180..15b4e87b2 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/utils.ts
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/utils.ts
@@ -1,3 +1,6 @@
+import { BlockNoteSchema } from '@blocknote/core';
+import { withMultiColumn } from '@blocknote/xl-multi-column';
+
export const randomColor = () => {
const randomInt = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
@@ -25,3 +28,7 @@ function hslToHex(h: number, s: number, l: number) {
export const toBase64 = (str: Uint8Array) =>
Buffer.from(str).toString('base64');
+
+export const blockNoteWithMultiColumn = withMultiColumn(
+ BlockNoteSchema.create(),
+);
diff --git a/src/frontend/apps/impress/src/features/docs/doc-table-content/components/Heading.tsx b/src/frontend/apps/impress/src/features/docs/doc-table-content/components/Heading.tsx
index 032a44d53..6445fc674 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-table-content/components/Heading.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-table-content/components/Heading.tsx
@@ -1,8 +1,8 @@
-import { BlockNoteEditor } from '@blocknote/core';
import { useState } from 'react';
import { BoxButton, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
+import { DocsBlockNoteEditor } from '@/features/docs/doc-editor';
import { useResponsiveStore } from '@/stores';
const leftPaddingMap: { [key: number]: string } = {
@@ -17,7 +17,7 @@ export type HeadingsHighlight = {
}[];
interface HeadingProps {
- editor: BlockNoteEditor;
+ editor: DocsBlockNoteEditor;
level: number;
text: string;
headingId: string;
diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock
index 14bdb369b..76225bb16 100644
--- a/src/frontend/yarn.lock
+++ b/src/frontend/yarn.lock
@@ -1136,6 +1136,21 @@
y-protocols "^1.0.6"
yjs "^13.6.15"
+"@blocknote/xl-multi-column@0.21.0":
+ version "0.21.0"
+ resolved "https://registry.yarnpkg.com/@blocknote/xl-multi-column/-/xl-multi-column-0.21.0.tgz#913bf07d6fb2c5445a6e4a375646997035e351e2"
+ integrity sha512-nWRZCP14pcbbA4F274kBL4UXI6RNmxZRKynsqACBPC/B3bns8GDh0GWMEPz/KN/6kuOkm/bYHHCkglDlEIEF1Q==
+ dependencies:
+ "@blocknote/core" "^0.21.0"
+ "@blocknote/react" "^0.21.0"
+ "@tiptap/core" "^2.7.1"
+ prosemirror-model "^1.23.0"
+ prosemirror-state "^1.4.3"
+ prosemirror-tables "^1.3.7"
+ prosemirror-transform "^1.9.0"
+ prosemirror-view "^1.33.7"
+ react-icons "^5.2.1"
+
"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
@@ -11122,7 +11137,7 @@ prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.3:
prosemirror-transform "^1.0.0"
prosemirror-view "^1.27.0"
-prosemirror-tables@^1.6.1:
+prosemirror-tables@^1.3.7, prosemirror-tables@^1.6.1:
version "1.6.2"
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.6.2.tgz#cec9e9ac6ecf81d67147c19ab39125d56c8351ae"
integrity sha512-97dKocVLrEVTQjZ4GBLdrrMw7Gv3no8H8yMwf5IRM9OoHrzbWpcH5jJxYgNQIRCtdIqwDctT1HdMHrGTiwp1dQ==