diff --git a/CHANGELOG.md b/CHANGELOG.md
index ceab904f..79c5a72b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to
- 📝(doc) Add security.md and codeofconduct.md #604
- ✨(frontend) add home page #553
- ✨(frontend) cursor display on activity #609
+- ✨(frontend) Add export page break #623
## Fixed
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts
index db9cb820..2bce4061 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts
@@ -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 }) => {
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 af4694c7..e0d5daf0 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,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) => {
+
);
@@ -277,6 +288,7 @@ export const BlockNoteEditorVersion = ({
},
provider: undefined,
},
+ schema: blockNoteSchema,
},
[initialContent],
);
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteSuggestionMenu.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteSuggestionMenu.tsx
new file mode 100644
index 00000000..22b11365
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteSuggestionMenu.tsx
@@ -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 (
+
+ );
+};
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 9468a796..8b88eb3d 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 025f2ad8..9a846b37 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 ac9b8a4b..e67800c6 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 19094b6c..2bb97a4b 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 { 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
+>;
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 032a44d5..6445fc67 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;