(frontend) add export page break

Blocknotejs introduced the ability to export a
document with page breaks.
This commit adds the page break feature to the
editor and so to our export feature.
This commit is contained in:
Anthony LC
2025-02-10 12:05:21 +01:00
committed by Anthony LC
parent 5ead18c94c
commit a6b3cfdb0c
9 changed files with 96 additions and 17 deletions

View File

@@ -41,8 +41,16 @@ test.describe('Doc Export', () => {
await expect(page.getByRole('button', { name: 'Download' })).toBeVisible();
});
test('it exports the doc to pdf', async ({ page, browserName }) => {
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
test('it exports the doc with pdf line break', async ({
page,
browserName,
}) => {
const [randomDoc] = await createDoc(
page,
'doc-editor-line-break',
browserName,
1,
);
const downloadPromise = page.waitForEvent('download', (download) => {
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
@@ -50,8 +58,20 @@ test.describe('Doc Export', () => {
await verifyDocName(page, randomDoc);
await page.locator('.ProseMirror.bn-editor').click();
await page.locator('.ProseMirror.bn-editor').fill('Hello World');
const editor = page.locator('.ProseMirror.bn-editor');
await editor.click();
await editor.locator('.bn-block-outer').last().fill('Hello');
await page.keyboard.press('Enter');
await editor.locator('.bn-block-outer').last().fill('/');
await page.getByText('Page Break').click();
await expect(editor.locator('.bn-page-break')).toBeVisible();
await page.keyboard.press('Enter');
await editor.locator('.bn-block-outer').last().fill('World');
await page
.getByRole('button', {
@@ -69,9 +89,10 @@ test.describe('Doc Export', () => {
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
const pdfBuffer = await cs.toBuffer(await download.createReadStream());
const pdfText = (await pdf(pdfBuffer)).text;
const pdfData = await pdf(pdfBuffer);
expect(pdfText).toContain('Hello World'); // This is the doc text
expect(pdfData.numpages).toBe(2);
expect(pdfData.text).toContain('\n\nHello\n\nWorld'); // This is the doc text
});
test('it exports the doc to docx', async ({ page, browserName }) => {

View File

@@ -1,4 +1,9 @@
import { Dictionary, locales } from '@blocknote/core';
import {
BlockNoteSchema,
Dictionary,
locales,
withPageBreak,
} from '@blocknote/core';
import '@blocknote/core/fonts/inter.css';
import { BlockNoteView } from '@blocknote/mantine';
import '@blocknote/mantine/style.css';
@@ -19,6 +24,7 @@ import useSaveDoc from '../hook/useSaveDoc';
import { useEditorStore } from '../stores';
import { randomColor } from '../utils';
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
import { BlockNoteToolbar } from './BlockNoteToolbar';
const cssEditor = (readonly: boolean) => css`
@@ -142,6 +148,8 @@ const cssEditor = (readonly: boolean) => css`
}
`;
export const blockNoteSchema = withPageBreak(BlockNoteSchema.create());
interface BlockNoteEditorProps {
doc: Doc;
provider: HocuspocusProvider;
@@ -217,6 +225,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
},
dictionary: locales[lang as keyof typeof locales] as Dictionary,
uploadFile,
schema: blockNoteSchema,
},
[collabName, lang, provider, uploadFile],
);
@@ -249,10 +258,12 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
<BlockNoteView
editor={editor}
formattingToolbar={false}
slashMenu={false}
editable={!readOnly}
theme="light"
>
<BlockNoteToolbar />
<BlockNoteSuggestionMenu />
</BlockNoteView>
</Box>
);
@@ -277,6 +288,7 @@ export const BlockNoteEditorVersion = ({
},
provider: undefined,
},
schema: blockNoteSchema,
},
[initialContent],
);

View File

@@ -0,0 +1,35 @@
import { combineByGroup, filterSuggestionItems } from '@blocknote/core';
import '@blocknote/mantine/style.css';
import {
SuggestionMenuController,
getDefaultReactSlashMenuItems,
getPageBreakReactSlashMenuItems,
useBlockNoteEditor,
} from '@blocknote/react';
import React, { useMemo } from 'react';
import { DocsBlockNoteEditor } from '../types';
export const BlockNoteSuggestionMenu = () => {
const editor = useBlockNoteEditor() as DocsBlockNoteEditor;
const getSlashMenuItems = useMemo(() => {
return async (query: string) =>
Promise.resolve(
filterSuggestionItems(
combineByGroup(
getDefaultReactSlashMenuItems(editor),
getPageBreakReactSlashMenuItems(editor),
),
query,
),
);
}, [editor]);
return (
<SuggestionMenuController
triggerCharacter="/"
getItems={getSlashMenuItems}
/>
);
};

View File

@@ -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(() => {

View File

@@ -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<UseEditorstore>((set) => ({

View File

@@ -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;
}

View File

@@ -1,3 +1,7 @@
import { BlockNoteEditor } from '@blocknote/core';
import { blockNoteSchema } from './components/BlockNoteEditor';
export interface DocAttachment {
file: string;
}
@@ -12,3 +16,9 @@ export type HeadingBlock = {
level: number;
};
};
export type DocsBlockNoteEditor = BlockNoteEditor<
typeof blockNoteSchema.blockSchema,
typeof blockNoteSchema.inlineContentSchema,
typeof blockNoteSchema.styleSchema
>;

View File

@@ -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;