Skip to content

Commit 0005fbd

Browse files
committed
✨(frontend) add multi columns support for editor
We add multi columns support for editor, now you can add columns to your document.
1 parent 2491ad7 commit 0005fbd

File tree

10 files changed

+117
-23
lines changed

10 files changed

+117
-23
lines changed

src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,4 +351,27 @@ test.describe('Doc Editor', () => {
351351

352352
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
353353
});
354+
355+
test('it checks the multi columns', async ({ page, browserName }) => {
356+
await createDoc(page, 'doc-multi-columns', browserName, 1);
357+
358+
await page.locator('.bn-block-outer').last().fill('/');
359+
360+
await page.getByText('Three Columns', { exact: true }).click();
361+
362+
await page.locator('.bn-block-column').first().fill('Column 1');
363+
await page.locator('.bn-block-column').nth(1).fill('Column 2');
364+
await page.locator('.bn-block-column').last().fill('Column 3');
365+
366+
expect(await page.locator('.bn-block-column').count()).toBe(3);
367+
await expect(
368+
page.locator('.bn-block-column[data-node-type="column"]').first(),
369+
).toHaveText('Column 1');
370+
await expect(
371+
page.locator('.bn-block-column[data-node-type="column"]').nth(1),
372+
).toHaveText('Column 2');
373+
await expect(
374+
page.locator('.bn-block-column[data-node-type="column"]').last(),
375+
).toHaveText('Column 3');
376+
});
354377
});

src/frontend/apps/impress/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@blocknote/core": "0.22.0",
1919
"@blocknote/mantine": "0.22.0",
2020
"@blocknote/react": "0.22.0",
21+
"@blocknote/xl-multi-column": "0.22.0",
2122
"@gouvfr-lasuite/integration": "1.0.2",
2223
"@hocuspocus/provider": "2.15.0",
2324
"@openfun/cunningham-react": "2.9.4",

src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
1-
import { Dictionary, locales } from '@blocknote/core';
1+
import {
2+
Dictionary,
3+
combineByGroup,
4+
filterSuggestionItems,
5+
locales,
6+
} from '@blocknote/core';
27
import '@blocknote/core/fonts/inter.css';
38
import { BlockNoteView } from '@blocknote/mantine';
49
import '@blocknote/mantine/style.css';
5-
import { useCreateBlockNote } from '@blocknote/react';
10+
import {
11+
SuggestionMenuController,
12+
getDefaultReactSlashMenuItems,
13+
useCreateBlockNote,
14+
} from '@blocknote/react';
15+
import {
16+
getMultiColumnSlashMenuItems,
17+
multiColumnDropCursor,
18+
locales as multiColumnLocales,
19+
} from '@blocknote/xl-multi-column';
620
import { HocuspocusProvider } from '@hocuspocus/provider';
7-
import React, { useEffect } from 'react';
21+
import React, { useEffect, useMemo } from 'react';
822
import { useTranslation } from 'react-i18next';
923
import * as Y from 'yjs';
1024

@@ -16,7 +30,7 @@ import { useUploadFile } from '../hook';
1630
import { useHeadings } from '../hook/useHeadings';
1731
import useSaveDoc from '../hook/useSaveDoc';
1832
import { useEditorStore } from '../stores';
19-
import { randomColor } from '../utils';
33+
import { blockNoteWithMultiColumn, randomColor } from '../utils';
2034

2135
import { BlockNoteToolbar } from './BlockNoteToolbar';
2236

@@ -120,8 +134,14 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
120134
return cursor;
121135
},
122136
},
123-
dictionary: locales[lang as keyof typeof locales] as Dictionary,
137+
dictionary: {
138+
...(locales[lang as keyof typeof locales] as Dictionary),
139+
multi_column:
140+
multiColumnLocales[lang as keyof typeof multiColumnLocales],
141+
},
124142
uploadFile,
143+
schema: blockNoteWithMultiColumn,
144+
dropCursor: multiColumnDropCursor,
125145
},
126146
[collabName, lang, provider, uploadFile],
127147
);
@@ -152,6 +172,18 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
152172
};
153173
}, [setEditor, editor]);
154174

175+
const getSlashMenuItems = useMemo(() => {
176+
// eslint-disable-next-line @typescript-eslint/require-await
177+
return async (query: string) =>
178+
filterSuggestionItems(
179+
combineByGroup(
180+
getDefaultReactSlashMenuItems(editor),
181+
getMultiColumnSlashMenuItems(editor),
182+
),
183+
query,
184+
);
185+
}, [editor]);
186+
155187
return (
156188
<Box $css={cssEditor(readOnly)}>
157189
{errorAttachment && (
@@ -169,7 +201,12 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
169201
formattingToolbar={false}
170202
editable={!readOnly}
171203
theme="light"
204+
slashMenu={false}
172205
>
206+
<SuggestionMenuController
207+
triggerCharacter="/"
208+
getItems={getSlashMenuItems}
209+
/>
173210
<BlockNoteToolbar />
174211
</BlockNoteView>
175212
</Box>
@@ -195,6 +232,7 @@ export const BlockNoteEditorVersion = ({
195232
},
196233
provider: undefined,
197234
},
235+
schema: blockNoteWithMultiColumn,
198236
},
199237
[initialContent],
200238
);

src/frontend/apps/impress/src/features/docs/doc-editor/hook/useHeadings.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { BlockNoteEditor } from '@blocknote/core';
21
import { useEffect } from 'react';
32

43
import { useHeadingStore } from '../stores';
4+
import { DocsBlockNoteEditor } from '../types';
55

6-
export const useHeadings = (editor: BlockNoteEditor) => {
6+
export const useHeadings = (editor: DocsBlockNoteEditor) => {
77
const { setHeadings, resetHeadings } = useHeadingStore();
88

99
useEffect(() => {

src/frontend/apps/impress/src/features/docs/doc-editor/stores/useEditorStore.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { BlockNoteEditor } from '@blocknote/core';
21
import { create } from 'zustand';
32

3+
import { DocsBlockNoteEditor } from '../types';
4+
45
export interface UseEditorstore {
5-
editor?: BlockNoteEditor;
6-
setEditor: (editor: BlockNoteEditor | undefined) => void;
6+
editor?: DocsBlockNoteEditor;
7+
setEditor: (editor: DocsBlockNoteEditor | undefined) => void;
78
}
89

910
export const useEditorStore = create<UseEditorstore>((set) => ({

src/frontend/apps/impress/src/features/docs/doc-editor/stores/useHeadingStore.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { BlockNoteEditor } from '@blocknote/core';
21
import { create } from 'zustand';
32

4-
import { HeadingBlock } from '../types';
3+
import { DocsBlockNoteEditor, HeadingBlock } from '../types';
54

65
const recursiveTextContent = (content: HeadingBlock['content']): string => {
76
if (!content) {
@@ -21,7 +20,7 @@ const recursiveTextContent = (content: HeadingBlock['content']): string => {
2120

2221
export interface UseHeadingStore {
2322
headings: HeadingBlock[];
24-
setHeadings: (editor: BlockNoteEditor) => void;
23+
setHeadings: (editor: DocsBlockNoteEditor) => void;
2524
resetHeadings: () => void;
2625
}
2726

src/frontend/apps/impress/src/features/docs/doc-editor/types.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { BlockNoteEditor } from '@blocknote/core';
2+
3+
import { blockNoteWithMultiColumn } from './utils';
4+
15
export interface DocAttachment {
26
file: string;
37
}
@@ -12,3 +16,9 @@ export type HeadingBlock = {
1216
level: number;
1317
};
1418
};
19+
20+
export type DocsBlockNoteEditor = BlockNoteEditor<
21+
typeof blockNoteWithMultiColumn.blockSchema,
22+
typeof blockNoteWithMultiColumn.inlineContentSchema,
23+
typeof blockNoteWithMultiColumn.styleSchema
24+
>;

src/frontend/apps/impress/src/features/docs/doc-editor/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { BlockNoteSchema } from '@blocknote/core';
2+
import { withMultiColumn } from '@blocknote/xl-multi-column';
3+
14
export const randomColor = () => {
25
const randomInt = (min: number, max: number) => {
36
return Math.floor(Math.random() * (max - min + 1)) + min;
@@ -25,3 +28,7 @@ function hslToHex(h: number, s: number, l: number) {
2528

2629
export const toBase64 = (str: Uint8Array) =>
2730
Buffer.from(str).toString('base64');
31+
32+
export const blockNoteWithMultiColumn = withMultiColumn(
33+
BlockNoteSchema.create(),
34+
);

src/frontend/apps/impress/src/features/docs/doc-table-content/components/Heading.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { BlockNoteEditor } from '@blocknote/core';
21
import { useState } from 'react';
32

43
import { BoxButton, Text } from '@/components';
54
import { useCunninghamTheme } from '@/cunningham';
5+
import { DocsBlockNoteEditor } from '@/features/docs/doc-editor';
66
import { useResponsiveStore } from '@/stores';
77

88
const sizeMap: { [key: number]: string } = {
@@ -17,7 +17,7 @@ export type HeadingsHighlight = {
1717
}[];
1818

1919
interface HeadingProps {
20-
editor: BlockNoteEditor;
20+
editor: DocsBlockNoteEditor;
2121
level: number;
2222
text: string;
2323
headingId: string;

src/frontend/yarn.lock

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,21 @@
10751075
y-protocols "^1.0.6"
10761076
yjs "^13.6.15"
10771077

1078+
"@blocknote/xl-multi-column@^0.22.0":
1079+
version "0.22.0"
1080+
resolved "https://registry.yarnpkg.com/@blocknote/xl-multi-column/-/xl-multi-column-0.22.0.tgz#495a4dc4080c3fb7b2a4d436653502d1abd1d3f6"
1081+
integrity sha512-RcrCbH3VPGojB+R5gFLKQ48c2nnr0AqoiAsdpR3w13FvynRY4qVjq3ZVzpHPfKFs0/2D0YNxhQu4fny9roaazQ==
1082+
dependencies:
1083+
"@blocknote/core" "^0.22.0"
1084+
"@blocknote/react" "^0.22.0"
1085+
"@tiptap/core" "^2.7.1"
1086+
prosemirror-model "^1.23.0"
1087+
prosemirror-state "^1.4.3"
1088+
prosemirror-tables "^1.3.7"
1089+
prosemirror-transform "^1.9.0"
1090+
prosemirror-view "^1.33.7"
1091+
react-icons "^5.2.1"
1092+
10781093
"@cspotcode/source-map-support@^0.8.0":
10791094
version "0.8.1"
10801095
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
@@ -4736,7 +4751,7 @@
47364751
dependencies:
47374752
"@types/node" "*"
47384753

4739-
"@types/node@*", "@types/[email protected]":
4754+
"@types/node@*":
47404755
version "22.10.3"
47414756
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.3.tgz#cdc2a89bf6e5d5e593fad08e83f74d7348d5dd10"
47424757
integrity sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw==
@@ -4798,7 +4813,7 @@
47984813
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
47994814
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
48004815

4801-
"@types/react-dom@*", "@types/[email protected]":
4816+
"@types/react-dom@*":
48024817
version "18.3.1"
48034818
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.1.tgz#1e4654c08a9cdcfb6594c780ac59b55aad42fe07"
48044819
integrity sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==
@@ -4943,7 +4958,7 @@
49434958
dependencies:
49444959
"@types/yargs-parser" "*"
49454960

4946-
"@typescript-eslint/eslint-plugin@*", "@typescript-eslint/eslint-plugin@8.19.0", "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
4961+
"@typescript-eslint/eslint-plugin@*", "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
49474962
version "8.19.0"
49484963
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.0.tgz#2b1e1b791e21d5fc27ddc93884db066444f597b5"
49494964
integrity sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==
@@ -4958,7 +4973,7 @@
49584973
natural-compare "^1.4.0"
49594974
ts-api-utils "^1.3.0"
49604975

4961-
"@typescript-eslint/parser@*", "@typescript-eslint/parser@8.19.0", "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
4976+
"@typescript-eslint/parser@*", "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
49624977
version "8.19.0"
49634978
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.19.0.tgz#f1512e6e5c491b03aabb2718b95becde22b15292"
49644979
integrity sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==
@@ -7048,7 +7063,7 @@ eslint-visitor-keys@^4.2.0:
70487063
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45"
70497064
integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==
70507065

7051-
7066+
eslint@*:
70527067
version "8.57.0"
70537068
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668"
70547069
integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==
@@ -10923,7 +10938,7 @@ prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.3:
1092310938
prosemirror-transform "^1.0.0"
1092410939
prosemirror-view "^1.27.0"
1092510940

10926-
prosemirror-tables@^1.6.1:
10941+
prosemirror-tables@^1.3.7, prosemirror-tables@^1.6.1:
1092710942
version "1.6.2"
1092810943
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.6.2.tgz#cec9e9ac6ecf81d67147c19ab39125d56c8351ae"
1092910944
integrity sha512-97dKocVLrEVTQjZ4GBLdrrMw7Gv3no8H8yMwf5IRM9OoHrzbWpcH5jJxYgNQIRCtdIqwDctT1HdMHrGTiwp1dQ==
@@ -10942,7 +10957,7 @@ prosemirror-trailing-node@^3.0.0:
1094210957
"@remirror/core-constants" "3.0.0"
1094310958
escape-string-regexp "^4.0.0"
1094410959

10945-
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.7.3:
10960+
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.7.3, prosemirror-transform@^1.9.0:
1094610961
version "1.10.2"
1094710962
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz#8ebac4e305b586cd96595aa028118c9191bbf052"
1094810963
integrity sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==
@@ -12930,7 +12945,7 @@ typed-array-length@^1.0.7:
1293012945
possible-typed-array-names "^1.0.0"
1293112946
reflect.getprototypeof "^1.0.6"
1293212947

12933-
typescript@*, typescript@5.7.2, typescript@^5.0.4:
12948+
typescript@*, typescript@^5.0.4:
1293412949
version "5.7.2"
1293512950
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
1293612951
integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==

0 commit comments

Comments
 (0)