(frontend) add multi columns support for editor

We add multi columns support for editor,
now you can add columns to your document.
Works with export.
📄AGPL feature.
This commit is contained in:
Anthony LC
2025-07-25 15:27:01 +02:00
parent 1ae831cabd
commit 11d0bafc94
7 changed files with 106 additions and 5 deletions

View File

@@ -13,6 +13,7 @@ and this project adheres to
- ✨(backend) allow masking documents from the list view #1171 - ✨(backend) allow masking documents from the list view #1171
- ✨(frontend) subdocs can manage link reach #1190 - ✨(frontend) subdocs can manage link reach #1190
- ✨(frontend) add duplicate action to doc tree #1175 - ✨(frontend) add duplicate action to doc tree #1175
- ✨(frontend) add multi columns support for editor #1219
### Changed ### Changed

View File

@@ -136,9 +136,10 @@ NODE_ENV=production NEXT_PUBLIC_PUBLISH_AS_MIT=false yarn build
Packages with licences incompatible with the MIT licence: Packages with licences incompatible with the MIT licence:
* `xl-docx-exporter`: [AGPL-3.0](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-docx-exporter/LICENSE), * `xl-docx-exporter`: [AGPL-3.0](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-docx-exporter/LICENSE),
* `xl-pdf-exporter`: [AGPL-3.0](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-pdf-exporter/LICENSE) * `xl-pdf-exporter`: [AGPL-3.0](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-pdf-exporter/LICENSE),
* `xl-multi-column`: [AGPL-3.0](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-multi-column/LICENSE).
In `.env.development`, `PUBLISH_AS_MIT` is set to `false`, allowing developers to test Docs with all its features. In `.env.development`, `PUBLISH_AS_MIT` is set to `false`, allowing developers to test Docs with all its features.
⚠️ If you run Docs in production with `PUBLISH_AS_MIT` set to `false` make sure you fulfill your [BlockNote licensing](https://github.com/TypeCellOS/BlockNote/blob/main/packages/xl-pdf-exporter/LICENSE) or [subscription](https://www.blocknotejs.org/about#partner-with-us) obligations. ⚠️ If you run Docs in production with `PUBLISH_AS_MIT` set to `false` make sure you fulfill your BlockNote licensing or [subscription](https://www.blocknotejs.org/about#partner-with-us) obligations.

View File

@@ -346,4 +346,69 @@ test.describe('Doc Export', () => {
const pdfData = await pdf(pdfBuffer); const pdfData = await pdf(pdfBuffer);
expect(pdfData.text).toContain('Hello World'); expect(pdfData.text).toContain('Hello World');
}); });
test('it exports the doc with multi columns', async ({
page,
browserName,
}) => {
const [randomDoc] = 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');
await page
.getByRole('button', {
name: 'download',
exact: true,
})
.click();
await expect(
page.getByRole('button', {
name: 'Download',
exact: true,
}),
).toBeVisible();
const downloadPromise = page.waitForEvent('download', (download) => {
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
});
void page
.getByRole('button', {
name: 'Download',
exact: true,
})
.click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
const pdfBuffer = await cs.toBuffer(await download.createReadStream());
const pdfData = await pdf(pdfBuffer);
expect(pdfData.text).toContain('Column 1');
expect(pdfData.text).toContain('Column 2');
expect(pdfData.text).toContain('Column 3');
});
}); });

View File

@@ -21,6 +21,7 @@
"@blocknote/mantine": "0.34.0", "@blocknote/mantine": "0.34.0",
"@blocknote/react": "0.34.0", "@blocknote/react": "0.34.0",
"@blocknote/xl-docx-exporter": "0.34.0", "@blocknote/xl-docx-exporter": "0.34.0",
"@blocknote/xl-multi-column": "0.34.0",
"@blocknote/xl-pdf-exporter": "0.34.0", "@blocknote/xl-pdf-exporter": "0.34.0",
"@dnd-kit/core": "6.3.1", "@dnd-kit/core": "6.3.1",
"@dnd-kit/modifiers": "9.0.0", "@dnd-kit/modifiers": "9.0.0",

View File

@@ -28,8 +28,13 @@ import { randomColor } from '../utils';
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu'; import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
import { BlockNoteToolbar } from './BlockNoteToolBar/BlockNoteToolbar'; import { BlockNoteToolbar } from './BlockNoteToolBar/BlockNoteToolbar';
import { CalloutBlock, DividerBlock } from './custom-blocks'; import { CalloutBlock, DividerBlock } from './custom-blocks';
import XLMultiColumn from './xl-multi-column';
export const blockNoteSchema = withPageBreak( const multiColumnDropCursor = XLMultiColumn?.multiColumnDropCursor;
const multiColumnLocales = XLMultiColumn?.locales;
const withMultiColumn = XLMultiColumn?.withMultiColumn;
const baseBlockNoteSchema = withPageBreak(
BlockNoteSchema.create({ BlockNoteSchema.create({
blockSpecs: { blockSpecs: {
...defaultBlockSpecs, ...defaultBlockSpecs,
@@ -39,6 +44,9 @@ export const blockNoteSchema = withPageBreak(
}), }),
); );
export const blockNoteSchema = (withMultiColumn?.(baseBlockNoteSchema) ||
baseBlockNoteSchema) as typeof baseBlockNoteSchema;
interface BlockNoteEditorProps { interface BlockNoteEditorProps {
doc: Doc; doc: Doc;
provider: HocuspocusProvider; provider: HocuspocusProvider;
@@ -116,7 +124,11 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
}, },
showCursorLabels: showCursorLabels as 'always' | 'activity', showCursorLabels: showCursorLabels as 'always' | 'activity',
}, },
dictionary: locales[lang as keyof typeof locales], dictionary: {
...locales[lang as keyof typeof locales],
multi_column:
multiColumnLocales?.[lang as keyof typeof multiColumnLocales],
},
tables: { tables: {
splitCells: true, splitCells: true,
cellBackgroundColor: true, cellBackgroundColor: true,
@@ -125,6 +137,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
}, },
uploadFile, uploadFile,
schema: blockNoteSchema, schema: blockNoteSchema,
dropCursor: multiColumnDropCursor,
}, },
[collabName, lang, provider, uploadFile], [collabName, lang, provider, uploadFile],
); );

View File

@@ -15,6 +15,10 @@ import {
getCalloutReactSlashMenuItems, getCalloutReactSlashMenuItems,
getDividerReactSlashMenuItems, getDividerReactSlashMenuItems,
} from './custom-blocks'; } from './custom-blocks';
import XLMultiColumn from './xl-multi-column';
const getMultiColumnSlashMenuItems =
XLMultiColumn?.getMultiColumnSlashMenuItems;
export const BlockNoteSuggestionMenu = () => { export const BlockNoteSuggestionMenu = () => {
const editor = useBlockNoteEditor<DocsBlockSchema>(); const editor = useBlockNoteEditor<DocsBlockSchema>();
@@ -27,8 +31,9 @@ export const BlockNoteSuggestionMenu = () => {
filterSuggestionItems( filterSuggestionItems(
combineByGroup( combineByGroup(
getDefaultReactSlashMenuItems(editor), getDefaultReactSlashMenuItems(editor),
getPageBreakReactSlashMenuItems(editor),
getCalloutReactSlashMenuItems(editor, t, basicBlocksName), getCalloutReactSlashMenuItems(editor, t, basicBlocksName),
getMultiColumnSlashMenuItems?.(editor) || [],
getPageBreakReactSlashMenuItems(editor),
getDividerReactSlashMenuItems(editor, t, basicBlocksName), getDividerReactSlashMenuItems(editor, t, basicBlocksName),
), ),
query, query,

View File

@@ -0,0 +1,15 @@
/**
* To import XL modules you must import from the index file.
* This is to ensure that the XL modules are only loaded when
* the application is not published as MIT.
*/
import * as XLMultiColumn from '@blocknote/xl-multi-column';
let modulesXL = undefined;
if (process.env.NEXT_PUBLIC_PUBLISH_AS_MIT === 'false') {
modulesXL = XLMultiColumn;
}
type ModulesXL = typeof XLMultiColumn | undefined;
export default modulesXL as ModulesXL;