diff --git a/examples/01-basic/04-all-blocks/App.tsx b/examples/01-basic/04-all-blocks/App.tsx index 6a34751b7..935e96d93 100644 --- a/examples/01-basic/04-all-blocks/App.tsx +++ b/examples/01-basic/04-all-blocks/App.tsx @@ -1,34 +1,11 @@ -import { - BlockNoteSchema, - combineByGroup, - filterSuggestionItems, - locales, -} from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; -import { - SuggestionMenuController, - getDefaultReactSlashMenuItems, - useCreateBlockNote, -} from "@blocknote/react"; -import { - getMultiColumnSlashMenuItems, - multiColumnDropCursor, - locales as multiColumnLocales, - withMultiColumn, -} from "@blocknote/xl-multi-column"; -import { useMemo } from "react"; +import { useCreateBlockNote } from "@blocknote/react"; export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ - schema: withMultiColumn(BlockNoteSchema.create()), - dropCursor: multiColumnDropCursor, - dictionary: { - ...locales.en, - multi_column: multiColumnLocales.en, - }, initialContent: [ { type: "paragraph", @@ -51,35 +28,6 @@ export default function App() { type: "paragraph", content: "Paragraph", }, - { - type: "columnList", - children: [ - { - type: "column", - props: { - width: 0.8, - }, - children: [ - { - type: "paragraph", - content: "Hello to the left!", - }, - ], - }, - { - type: "column", - props: { - width: 1.2, - }, - children: [ - { - type: "paragraph", - content: "Hello to the right!", - }, - ], - }, - ], - }, { type: "heading", content: "Heading", @@ -189,20 +137,6 @@ export default function App() { ], }); - const slashMenuItems = useMemo(() => { - return combineByGroup( - getDefaultReactSlashMenuItems(editor), - getMultiColumnSlashMenuItems(editor) - ); - }, [editor]); - // Renders the editor instance using a React component. - return ( - - filterSuggestionItems(slashMenuItems, query)} - /> - - ); + return ; } diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/App.tsx b/examples/05-interoperability/05-converting-blocks-to-pdf/App.tsx index 63ba2109a..269cc81c9 100644 --- a/examples/05-interoperability/05-converting-blocks-to-pdf/App.tsx +++ b/examples/05-interoperability/05-converting-blocks-to-pdf/App.tsx @@ -1,13 +1,25 @@ +import { + BlockNoteSchema, + combineByGroup, + filterSuggestionItems, + withPageBreak, +} 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 { + getDefaultReactSlashMenuItems, + getPageBreakReactSlashMenuItems, + SuggestionMenuController, + useCreateBlockNote, +} from "@blocknote/react"; import { PDFExporter, pdfDefaultSchemaMappings, } from "@blocknote/xl-pdf-exporter"; import { PDFViewer } from "@react-pdf/renderer"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; + import "./styles.css"; export default function App() { @@ -16,6 +28,7 @@ export default function App() { // Creates a new editor instance with some initial content. const editor = useCreateBlockNote({ + schema: withPageBreak(BlockNoteSchema.create()), initialContent: [ { type: "paragraph", @@ -180,6 +193,9 @@ export default function App() { ], }, }, + { + type: "pageBreak", + }, { type: "file", }, @@ -308,11 +324,25 @@ export default function App() { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const slashMenuItems = useMemo(() => { + return combineByGroup( + getDefaultReactSlashMenuItems(editor), + getPageBreakReactSlashMenuItems(editor) + ); + }, [editor]); + // Renders the editor instance, and its contents as HTML below. return (
- + + + filterSuggestionItems(slashMenuItems, query) + } + /> +
{pdfDocument} diff --git a/examples/05-interoperability/06-converting-blocks-to-docx/App.tsx b/examples/05-interoperability/06-converting-blocks-to-docx/App.tsx index a456d84f9..94bb8d8c9 100644 --- a/examples/05-interoperability/06-converting-blocks-to-docx/App.tsx +++ b/examples/05-interoperability/06-converting-blocks-to-docx/App.tsx @@ -1,16 +1,30 @@ +import { + BlockNoteSchema, + combineByGroup, + filterSuggestionItems, + withPageBreak, +} 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 { + getDefaultReactSlashMenuItems, + getPageBreakReactSlashMenuItems, + SuggestionMenuController, + useCreateBlockNote, +} from "@blocknote/react"; import { DOCXExporter, docxDefaultSchemaMappings, } from "@blocknote/xl-docx-exporter"; +import { useMemo } from "react"; + import "./styles.css"; export default function App() { // Creates a new editor instance with some initial content. const editor = useCreateBlockNote({ + schema: withPageBreak(BlockNoteSchema.create()), initialContent: [ { type: "paragraph", @@ -175,6 +189,9 @@ export default function App() { ], }, }, + { + type: "pageBreak", + }, { type: "file", }, @@ -305,6 +322,13 @@ export default function App() { window.URL.revokeObjectURL(link.href); }; + const slashMenuItems = useMemo(() => { + return combineByGroup( + getDefaultReactSlashMenuItems(editor), + getPageBreakReactSlashMenuItems(editor) + ); + }, [editor]); + // Renders the editor instance, and its contents as HTML below. return (
@@ -314,7 +338,14 @@ export default function App() {
- + + + filterSuggestionItems(slashMenuItems, query) + } + /> +
); diff --git a/package-lock.json b/package-lock.json index 2957c3218..51479e96d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3322,6 +3322,13 @@ "node": ">=6.9.0" } }, + "node_modules/@base2/pretty-print-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz", + "integrity": "sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/@blocknote/ariakit": { "resolved": "packages/ariakit", "link": true @@ -24963,6 +24970,29 @@ "react": "^18.3.1" } }, + "node_modules/react-element-to-jsx-string": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-15.0.0.tgz", + "integrity": "sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@base2/pretty-print-object": "1.0.1", + "is-plain-object": "5.0.0", + "react-is": "18.1.0" + }, + "peerDependencies": { + "react": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0", + "react-dom": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0" + } + }, + "node_modules/react-element-to-jsx-string/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true, + "license": "MIT" + }, "node_modules/react-github-btn": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/react-github-btn/-/react-github-btn-1.4.0.tgz", @@ -30544,6 +30574,10 @@ "vite-plugin-eslint": "^1.8.1", "vitest": "^2.0.3", "xml-formatter": "^3.6.3" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || >= 19.0.0-rc", + "react-dom": "^18.0 || ^19.0 || >= 19.0.0-rc" } }, "packages/xl-docx-exporter/node_modules/buffer": { @@ -30595,6 +30629,10 @@ "vite": "^5.3.4", "vite-plugin-eslint": "^1.8.1", "vitest": "^2.0.3" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || >= 19.0.0-rc", + "react-dom": "^18.0 || ^19.0 || >= 19.0.0-rc" } }, "packages/xl-multi-column/node_modules/cssstyle": { @@ -30825,6 +30863,7 @@ "jest-image-snapshot": "^6.4.0", "pdf-to-img": "^4.2.0", "prettier": "^2.7.1", + "react-element-to-jsx-string": "^15.0.0", "rollup-plugin-webpack-stats": "^0.2.2", "typescript": "^5.0.4", "vite": "^5.3.4", diff --git a/packages/core/src/api/exporters/html/__snapshots__/pageBreak/basic/external.html b/packages/core/src/api/exporters/html/__snapshots__/pageBreak/basic/external.html new file mode 100644 index 000000000..ba65bb062 --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/pageBreak/basic/external.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/pageBreak/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/pageBreak/basic/internal.html new file mode 100644 index 000000000..f4303a5ad --- /dev/null +++ b/packages/core/src/api/exporters/html/__snapshots__/pageBreak/basic/internal.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/pageBreak/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/pageBreak/basic/markdown.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap index 4b6cebdb8..ef16974ad 100644 --- a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +++ b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap @@ -1605,6 +1605,22 @@ exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert } `; +exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert pageBreak/basic to/from prosemirror 1`] = ` +{ + "attrs": { + "backgroundColor": "default", + "id": "1", + "textColor": "default", + }, + "content": [ + { + "type": "pageBreak", + }, + ], + "type": "blockContainer", +} +`; + exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert paragraph/basic to/from prosemirror 1`] = ` { "attrs": { diff --git a/packages/core/src/api/testUtil/cases/defaultSchema.ts b/packages/core/src/api/testUtil/cases/defaultSchema.ts index 2510f4e04..dfdff6278 100644 --- a/packages/core/src/api/testUtil/cases/defaultSchema.ts +++ b/packages/core/src/api/testUtil/cases/defaultSchema.ts @@ -6,16 +6,22 @@ import { DefaultInlineContentSchema, DefaultStyleSchema, } from "../../../blocks/defaultBlocks.js"; +import { + pageBreakSchema, + withPageBreak, +} from "../../../blocks/PageBreakBlockContent/schema.js"; import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; +import { BlockNoteSchema } from "../../../editor/BlockNoteSchema.js"; export const defaultSchemaTestCases: EditorTestCases< - DefaultBlockSchema, + DefaultBlockSchema & typeof pageBreakSchema.blockSchema, DefaultInlineContentSchema, DefaultStyleSchema > = { name: "default schema", createEditor: () => { return BlockNoteEditor.create({ + schema: withPageBreak(BlockNoteSchema.create()), uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY, }); }, @@ -202,6 +208,14 @@ export const defaultSchemaTestCases: EditorTestCases< }, ], }, + { + name: "pageBreak/basic", + blocks: [ + { + type: "pageBreak", + }, + ], + }, { name: "file/button", blocks: [ diff --git a/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.ts b/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.ts new file mode 100644 index 000000000..e1e72ab59 --- /dev/null +++ b/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.ts @@ -0,0 +1,49 @@ +import { + createBlockSpec, + CustomBlockConfig, + Props, +} from "../../schema/index.js"; + +export const pageBreakConfig = { + type: "pageBreak" as const, + propSchema: {}, + content: "none", + isFileBlock: false, + isSelectable: false, +} satisfies CustomBlockConfig; +export const pageBreakRender = () => { + const pageBreak = document.createElement("div"); + + pageBreak.className = "bn-page-break"; + pageBreak.setAttribute("data-page-break", ""); + + return { + dom: pageBreak, + }; +}; +export const pageBreakParse = ( + element: HTMLElement +): Partial> | undefined => { + if (element.tagName === "DIV" && element.hasAttribute("data-page-break")) { + return { + type: "pageBreak", + }; + } + + return undefined; +}; +export const pageBreakToExternalHTML = () => { + const pageBreak = document.createElement("div"); + + pageBreak.setAttribute("data-page-break", ""); + + return { + dom: pageBreak, + }; +}; + +export const PageBreak = createBlockSpec(pageBreakConfig, { + render: pageBreakRender, + parse: pageBreakParse, + toExternalHTML: pageBreakToExternalHTML, +}); diff --git a/packages/core/src/blocks/PageBreakBlockContent/getPageBreakSlashMenuItems.ts b/packages/core/src/blocks/PageBreakBlockContent/getPageBreakSlashMenuItems.ts new file mode 100644 index 000000000..6c7b10ad8 --- /dev/null +++ b/packages/core/src/blocks/PageBreakBlockContent/getPageBreakSlashMenuItems.ts @@ -0,0 +1,45 @@ +import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js"; +import { DefaultSuggestionItem } from "../../extensions/SuggestionMenu/DefaultSuggestionItem.js"; +import { insertOrUpdateBlock } from "../../extensions/SuggestionMenu/getDefaultSlashMenuItems.js"; +import { + BlockSchema, + InlineContentSchema, + StyleSchema, +} from "../../schema/index.js"; +import { pageBreakSchema } from "./schema.js"; + +export function checkPageBreakBlocksInSchema< + I extends InlineContentSchema, + S extends StyleSchema +>( + editor: BlockNoteEditor +): editor is BlockNoteEditor { + return ( + "pageBreak" in editor.schema.blockSchema && + editor.schema.blockSchema["pageBreak"] === + pageBreakSchema.blockSchema["pageBreak"] + ); +} + +export function getPageBreakSlashMenuItems< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>(editor: BlockNoteEditor) { + const items: (Omit & { key: "page_break" })[] = + []; + + if (checkPageBreakBlocksInSchema(editor)) { + items.push({ + ...editor.dictionary.slash_menu.page_break, + onItemClick: () => { + insertOrUpdateBlock(editor, { + type: "pageBreak", + }); + }, + key: "page_break", + }); + } + + return items; +} diff --git a/packages/core/src/blocks/PageBreakBlockContent/schema.ts b/packages/core/src/blocks/PageBreakBlockContent/schema.ts new file mode 100644 index 000000000..c9b64a59c --- /dev/null +++ b/packages/core/src/blocks/PageBreakBlockContent/schema.ts @@ -0,0 +1,40 @@ +import { BlockNoteSchema } from "../../editor/BlockNoteSchema.js"; +import { + BlockSchema, + InlineContentSchema, + StyleSchema, +} from "../../schema/index.js"; +import { PageBreak } from "./PageBreakBlockContent.js"; + +export const pageBreakSchema = BlockNoteSchema.create({ + blockSpecs: { + pageBreak: PageBreak, + }, +}); + +/** + * Adds page break support to the given schema. + */ +export const withPageBreak = < + B extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + schema: BlockNoteSchema +) => { + return BlockNoteSchema.create({ + blockSpecs: { + ...schema.blockSpecs, + ...pageBreakSchema.blockSpecs, + }, + inlineContentSpecs: schema.inlineContentSpecs, + styleSpecs: schema.styleSpecs, + }) as any as BlockNoteSchema< + // typescript needs some help here + B & { + pageBreak: typeof PageBreak.config; + }, + I, + S + >; +}; diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index 54849c2b6..284cbeb07 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -308,6 +308,19 @@ NESTED BLOCKS transition-delay: 0.1s; } +/* PAGE BREAK */ +.bn-block-content[data-content-type="pageBreak"] > div { + width: 100%; + height: 0; + border-top: dotted rgb(125, 121, 122) 4px; +} + +@media print { + .bn-block-content[data-content-type="pageBreak"] > div { + page-break-after: always; + } +} + /* FILES */ /* Element that wraps content for all file blocks */ @@ -336,7 +349,7 @@ NESTED BLOCKS .bn-editor[contenteditable="true"] [data-file-block] .bn-add-file-button:hover, [data-file-block] .bn-file-name-with-icon:hover, -.ProseMirror-selectednode .bn-file-name-with-icon{ +.ProseMirror-selectednode .bn-file-name-with-icon { background-color: rgb(225, 225, 225); } diff --git a/packages/core/src/extensions/SuggestionMenu/DefaultSuggestionItem.ts b/packages/core/src/extensions/SuggestionMenu/DefaultSuggestionItem.ts index f2e11d5ee..6789b7661 100644 --- a/packages/core/src/extensions/SuggestionMenu/DefaultSuggestionItem.ts +++ b/packages/core/src/extensions/SuggestionMenu/DefaultSuggestionItem.ts @@ -1,7 +1,7 @@ import type { Dictionary } from "../../i18n/dictionary.js"; export type DefaultSuggestionItem = { - key: keyof Dictionary["slash_menu"]; + key: keyof Omit; title: string; onItemClick: () => void; subtext?: string; diff --git a/packages/core/src/i18n/locales/ar.ts b/packages/core/src/i18n/locales/ar.ts index b18a20259..c4c2ad8a8 100644 --- a/packages/core/src/i18n/locales/ar.ts +++ b/packages/core/src/i18n/locales/ar.ts @@ -58,6 +58,12 @@ export const ar: Dictionary = { aliases: ["كود", "مسبق"], group: "الكتل الأساسية", }, + page_break: { + title: "فاصل الصفحة", + subtext: "فاصل الصفحة", + aliases: ["page", "break", "separator", "فاصل", "الصفحة"], + group: "الكتل الأساسية", + }, table: { title: "جدول", subtext: "يستخدم للجداول", diff --git a/packages/core/src/i18n/locales/de.ts b/packages/core/src/i18n/locales/de.ts index 6331d722e..f4b9e5f5f 100644 --- a/packages/core/src/i18n/locales/de.ts +++ b/packages/core/src/i18n/locales/de.ts @@ -56,6 +56,12 @@ export const de = { aliases: ["code", "pre"], group: "Grundlegende blöcke", }, + page_break: { + title: "Seitenumbruch", + subtext: "Seitentrenner", + aliases: ["page", "break", "separator", "seitenumbruch", "trenner"], + group: "Grundlegende Blöcke", + }, table: { title: "Tabelle", subtext: "Tabelle mit editierbaren Zellen", diff --git a/packages/core/src/i18n/locales/en.ts b/packages/core/src/i18n/locales/en.ts index e23d0e263..442f7a389 100644 --- a/packages/core/src/i18n/locales/en.ts +++ b/packages/core/src/i18n/locales/en.ts @@ -56,6 +56,12 @@ export const en = { aliases: ["code", "pre"], group: "Basic blocks", }, + page_break: { + title: "Page Break", + subtext: "Page separator", + aliases: ["page", "break", "separator"], + group: "Basic blocks", + }, table: { title: "Table", subtext: "Table with editable cells", diff --git a/packages/core/src/i18n/locales/es.ts b/packages/core/src/i18n/locales/es.ts index 2ffc5587f..0a8e11ac9 100644 --- a/packages/core/src/i18n/locales/es.ts +++ b/packages/core/src/i18n/locales/es.ts @@ -55,6 +55,12 @@ export const es = { aliases: ["code", "pre"], group: "Bloques básicos", }, + page_break: { + title: "Salto de página", + subtext: "Separador de página", + aliases: ["page", "break", "separator", "salto", "separador"], + group: "Bloques básicos", + }, table: { title: "Tabla", subtext: "Tabla con celdas editables", diff --git a/packages/core/src/i18n/locales/fr.ts b/packages/core/src/i18n/locales/fr.ts index 2dc28972a..9f694dadb 100644 --- a/packages/core/src/i18n/locales/fr.ts +++ b/packages/core/src/i18n/locales/fr.ts @@ -71,6 +71,12 @@ export const fr: Dictionary = { aliases: ["code", "pre"], group: "Blocs de base", }, + page_break: { + title: "Saut de page", + subtext: "Séparateur de page", + aliases: ["page", "break", "separator", "saut", "séparateur"], + group: "Blocs de base", + }, table: { title: "Tableau", subtext: "Utilisé pour les tableaux", diff --git a/packages/core/src/i18n/locales/hr.ts b/packages/core/src/i18n/locales/hr.ts index 5b65fc546..b7604e6ff 100644 --- a/packages/core/src/i18n/locales/hr.ts +++ b/packages/core/src/i18n/locales/hr.ts @@ -21,26 +21,38 @@ export const hr = { numbered_list: { title: "Numerirani popis", subtext: "Popis s numeriranim stavkama", - aliases: ["poredaniPopis", "stavkaPopisa", "popis", "numeriraniPopis", "numerirani popis"], + aliases: [ + "poredaniPopis", + "stavkaPopisa", + "popis", + "numeriraniPopis", + "numerirani popis", + ], group: "Osnovni blokovi", }, bullet_list: { title: "Popis s oznakama", subtext: "Popis s grafičkim oznakama", - aliases: ["neporedaniPopis", "stavkaPopisa", "popis", "popisSOznakama", "popis s oznakama"], + aliases: [ + "neporedaniPopis", + "stavkaPopisa", + "popis", + "popisSOznakama", + "popis s oznakama", + ], group: "Osnovni blokovi", }, check_list: { title: "Check lista", subtext: "Popis s kućicama za označavanje", aliases: [ - "neporedaniPopis", - "stavkaPopisa", - "popis", - "popisZaProvjeru", - "check lista", - "označeni popis", - "kućicaZaOznačavanje", + "neporedaniPopis", + "stavkaPopisa", + "popis", + "popisZaProvjeru", + "check lista", + "označeni popis", + "kućicaZaOznačavanje", ], group: "Osnovni blokovi", }, @@ -56,17 +68,23 @@ export const hr = { aliases: ["tablica"], group: "Napredno", }, + page_break: { + title: "Prijelom stranice", + subtext: "Razdjelnik stranice", + aliases: ["page", "break", "separator", "prijelom", "razdjelnik"], + group: "Osnovni blokovi", + }, image: { title: "Slika", subtext: "Slika s podesivom veličinom i natpisom", aliases: [ - "slika", - "učitavanjeSlike", - "učitaj", - "img", - "fotografija", - "medij", - "url", + "slika", + "učitavanjeSlike", + "učitaj", + "img", + "fotografija", + "medij", + "url", ], group: "Mediji", }, @@ -74,13 +92,13 @@ export const hr = { title: "Video", subtext: "Video s podesivom veličinom i natpisom", aliases: [ - "video", - "učitavanjeVidea", - "učitaj", - "mp4", - "film", - "medij", - "url", + "video", + "učitavanjeVidea", + "učitaj", + "mp4", + "film", + "medij", + "url", ], group: "Mediji", }, @@ -88,13 +106,13 @@ export const hr = { title: "Audio", subtext: "Audio s natpisom", aliases: [ - "audio", - "učitavanjeAudija", - "učitaj", - "mp3", - "zvuk", - "medij", - "url", + "audio", + "učitavanjeAudija", + "učitaj", + "mp3", + "zvuk", + "medij", + "url", ], group: "Mediji", }, @@ -157,16 +175,16 @@ export const hr = { text_title: "Tekst", background_title: "Pozadina", colors: { - default: "Zadano", - gray: "Siva", - brown: "Smeđa", - red: "Crvena", - orange: "Narančasta", - yellow: "Žuta", - green: "Zelena", - blue: "Plava", - purple: "Ljubičasta", - pink: "Ružičasta", + default: "Zadano", + gray: "Siva", + brown: "Smeđa", + red: "Crvena", + orange: "Narančasta", + yellow: "Žuta", + green: "Zelena", + blue: "Plava", + purple: "Ljubičasta", + pink: "Ružičasta", }, }, @@ -240,29 +258,29 @@ export const hr = { file: "Ukloni datoteku", } as Record, }, - file_preview_toggle: { + file_preview_toggle: { tooltip: "Prikaži/sakrij pregled", - }, - nest: { + }, + nest: { tooltip: "Ugnijezdi blok", secondary_tooltip: "Tab", - }, - unnest: { + }, + unnest: { tooltip: "Razgnijezdi blok", secondary_tooltip: "Shift+Tab", - }, - align_left: { + }, + align_left: { tooltip: "Poravnaj tekst lijevo", - }, - align_center: { + }, + align_center: { tooltip: "Poravnaj tekst po sredini", - }, - align_right: { + }, + align_right: { tooltip: "Poravnaj tekst desno", - }, - align_justify: { + }, + align_justify: { tooltip: "Poravnaj tekst obostrano", - }, + }, }, file_panel: { upload: { diff --git a/packages/core/src/i18n/locales/is.ts b/packages/core/src/i18n/locales/is.ts index 07fcb3bfb..978ba481b 100644 --- a/packages/core/src/i18n/locales/is.ts +++ b/packages/core/src/i18n/locales/is.ts @@ -50,6 +50,12 @@ export const is: Dictionary = { aliases: ["kóði", "pre"], group: "Grunnblokkar", }, + page_break: { + title: "Síðubrot", + subtext: "Síðuskil", + aliases: ["page", "break", "separator", "síðubrot", "síðuskil"], + group: "Grunnblokkir", + }, table: { title: "Tafla", subtext: "Notað fyrir töflur", diff --git a/packages/core/src/i18n/locales/ja.ts b/packages/core/src/i18n/locales/ja.ts index 2172289b0..a4c2ff04a 100644 --- a/packages/core/src/i18n/locales/ja.ts +++ b/packages/core/src/i18n/locales/ja.ts @@ -74,6 +74,12 @@ export const ja: Dictionary = { aliases: ["code", "pre", "コード", "コードブロック"], group: "基本ブロック", }, + page_break: { + title: "改ページ", + subtext: "ページ区切り", + aliases: ["page", "break", "separator", "改ページ", "区切り"], + group: "基本ブロック", + }, table: { title: "表", subtext: "表に使用", diff --git a/packages/core/src/i18n/locales/ko.ts b/packages/core/src/i18n/locales/ko.ts index cc0144481..0bf8e24a1 100644 --- a/packages/core/src/i18n/locales/ko.ts +++ b/packages/core/src/i18n/locales/ko.ts @@ -58,6 +58,12 @@ export const ko: Dictionary = { aliases: ["code", "pre"], group: "기본 블록", }, + page_break: { + title: "페이지 나누기", + subtext: "페이지 구분자", + aliases: ["page", "break", "separator", "페이지", "구분자"], + group: "기본 블록", + }, table: { title: "표", subtext: "간단한 표를 추가합니다.", diff --git a/packages/core/src/i18n/locales/nl.ts b/packages/core/src/i18n/locales/nl.ts index 152e64000..25c3426d2 100644 --- a/packages/core/src/i18n/locales/nl.ts +++ b/packages/core/src/i18n/locales/nl.ts @@ -50,6 +50,12 @@ export const nl: Dictionary = { aliases: ["code", "pre"], group: "Basisblokken", }, + page_break: { + title: "Pagina-einde", + subtext: "Paginascheiding", + aliases: ["page", "break", "separator", "pagina", "scheiding"], + group: "Basisblokken", + }, table: { title: "Tabel", subtext: "Gebruikt voor tabellen", diff --git a/packages/core/src/i18n/locales/pl.ts b/packages/core/src/i18n/locales/pl.ts index fdf53fe59..befa6ace7 100644 --- a/packages/core/src/i18n/locales/pl.ts +++ b/packages/core/src/i18n/locales/pl.ts @@ -50,6 +50,12 @@ export const pl: Dictionary = { aliases: ["kod", "pre"], group: "Podstawowe bloki", }, + page_break: { + title: "Podział strony", + subtext: "Separator strony", + aliases: ["page", "break", "separator", "podział", "separator"], + group: "Podstawowe bloki", + }, table: { title: "Tabela", subtext: "Używana do tworzenia tabel", diff --git a/packages/core/src/i18n/locales/pt.ts b/packages/core/src/i18n/locales/pt.ts index 0f435b895..6dd7dab3c 100644 --- a/packages/core/src/i18n/locales/pt.ts +++ b/packages/core/src/i18n/locales/pt.ts @@ -57,6 +57,12 @@ export const pt: Dictionary = { aliases: ["codigo", "pre"], group: "Blocos básicos", }, + page_break: { + title: "Quebra de página", + subtext: "Separador de página", + aliases: ["page", "break", "separator", "quebra", "separador"], + group: "Blocos básicos", + }, table: { title: "Tabela", subtext: "Usado para tabelas", diff --git a/packages/core/src/i18n/locales/ru.ts b/packages/core/src/i18n/locales/ru.ts index 54afd7aeb..60309d5d9 100644 --- a/packages/core/src/i18n/locales/ru.ts +++ b/packages/core/src/i18n/locales/ru.ts @@ -75,6 +75,12 @@ export const ru: Dictionary = { aliases: ["code", "pre", "блок кода"], group: "Базовые блоки", }, + page_break: { + title: "Разрыв страницы", + subtext: "Разделитель страницы", + aliases: ["page", "break", "separator", "разрыв", "разделитель"], + group: "Основные блоки", + }, table: { title: "Таблица", subtext: "Используется для таблиц", diff --git a/packages/core/src/i18n/locales/vi.ts b/packages/core/src/i18n/locales/vi.ts index 507146013..eb1dbf31a 100644 --- a/packages/core/src/i18n/locales/vi.ts +++ b/packages/core/src/i18n/locales/vi.ts @@ -57,6 +57,12 @@ export const vi: Dictionary = { aliases: ["code", "pre"], group: "Khối cơ bản", }, + page_break: { + title: "Ngắt trang", + subtext: "Phân cách trang", + aliases: ["page", "break", "separator", "ngắt", "phân cách"], + group: "Khối cơ bản", + }, table: { title: "Bảng", subtext: "Sử dụng để tạo bảng", diff --git a/packages/core/src/i18n/locales/zh.ts b/packages/core/src/i18n/locales/zh.ts index 955b9ca09..cfb32c155 100644 --- a/packages/core/src/i18n/locales/zh.ts +++ b/packages/core/src/i18n/locales/zh.ts @@ -75,6 +75,12 @@ export const zh: Dictionary = { aliases: ["code", "pre", "代码", "预格式"], group: "基础", }, + page_break: { + title: "分页符", + subtext: "页面分隔符", + aliases: ["page", "break", "separator", "分页", "分隔符"], + group: "基础", + }, table: { title: "表格", subtext: "使用表格", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7b0115ae4..072d375bc 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,6 +6,9 @@ export * from "./api/nodeUtil.js"; export * from "./api/testUtil/index.js"; export * from "./blocks/AudioBlockContent/AudioBlockContent.js"; export * from "./blocks/CodeBlockContent/CodeBlockContent.js"; +export * from "./blocks/PageBreakBlockContent/PageBreakBlockContent.js"; +export * from "./blocks/PageBreakBlockContent/getPageBreakSlashMenuItems.js"; +export * from "./blocks/PageBreakBlockContent/schema.js"; export * from "./blocks/FileBlockContent/FileBlockContent.js"; export * from "./blocks/FileBlockContent/helpers/parse/parseEmbedElement.js"; export * from "./blocks/FileBlockContent/helpers/parse/parseFigureElement.js"; diff --git a/packages/react/src/blocks/PageBreakBlockContent/getPageBreakReactSlashMenuItems.tsx b/packages/react/src/blocks/PageBreakBlockContent/getPageBreakReactSlashMenuItems.tsx new file mode 100644 index 000000000..00fc1c274 --- /dev/null +++ b/packages/react/src/blocks/PageBreakBlockContent/getPageBreakReactSlashMenuItems.tsx @@ -0,0 +1,29 @@ +import { + BlockNoteEditor, + BlockSchema, + getPageBreakSlashMenuItems, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { DefaultReactSuggestionItem } from "../../components/SuggestionMenu/types.js"; +import { TbPageBreak } from "react-icons/tb"; + +const icons = { + page_break: TbPageBreak, +}; + +export function getPageBreakReactSlashMenuItems< + BSchema extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + editor: BlockNoteEditor +): (Omit & { key: "page_break" })[] { + return getPageBreakSlashMenuItems(editor).map((item) => { + const Icon = icons[item.key]; + return { + ...item, + icon: , + }; + }); +} diff --git a/packages/react/src/editor/styles.css b/packages/react/src/editor/styles.css index 2ac35161d..1f1b3c25a 100644 --- a/packages/react/src/editor/styles.css +++ b/packages/react/src/editor/styles.css @@ -238,3 +238,7 @@ .bn-side-menu[data-url="false"] { height: 54px; } + +.bn-side-menu[data-block-type="pageBreak"] { + transform: translateY(-10px); +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 579f6bad1..dd8215d70 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -15,6 +15,7 @@ export * from "./blocks/FileBlockContent/helpers/toExternalHTML/FigureWithCaptio export * from "./blocks/FileBlockContent/helpers/toExternalHTML/LinkWithCaption.js"; export * from "./blocks/FileBlockContent/useResolveUrl.js"; export * from "./blocks/ImageBlockContent/ImageBlockContent.js"; +export * from "./blocks/PageBreakBlockContent/getPageBreakReactSlashMenuItems.js"; export * from "./blocks/VideoBlockContent/VideoBlockContent.js"; export * from "./components/FormattingToolbar/DefaultButtons/BasicTextStyleButton.js"; diff --git a/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml b/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml index ef14525b4..ccd1ff214 100644 --- a/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml +++ b/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml @@ -112,6 +112,11 @@ justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + + + + + diff --git a/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts b/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts index 5d6a9f7e3..1b457f3c3 100644 --- a/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts +++ b/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts @@ -3,6 +3,7 @@ import { COLORS_DEFAULT, DefaultBlockSchema, DefaultProps, + pageBreakSchema, StyledText, UnreachableCaseError, } from "@blocknote/core"; @@ -12,6 +13,7 @@ import { ExternalHyperlink, IParagraphOptions, ImageRun, + PageBreak, Paragraph, ParagraphChild, ShadingType, @@ -56,7 +58,7 @@ function blockPropsToStyles( }; } export const docxBlockMappingForDefaultSchema: BlockMapping< - DefaultBlockSchema, + DefaultBlockSchema & typeof pageBreakSchema.blockSchema, any, any, | Promise @@ -152,6 +154,11 @@ export const docxBlockMappingForDefaultSchema: BlockMapping< ], }); }, + pageBreak: () => { + return new Paragraph({ + children: [new PageBreak()], + }); + }, image: async (block, exporter) => { const blob = await exporter.resolveFile(block.props.url); const { width, height } = await getImageDimensions(blob); diff --git a/packages/xl-docx-exporter/src/docx/docxExporter.test.ts b/packages/xl-docx-exporter/src/docx/docxExporter.test.ts index 42f744900..ec2d3d9ee 100644 --- a/packages/xl-docx-exporter/src/docx/docxExporter.test.ts +++ b/packages/xl-docx-exporter/src/docx/docxExporter.test.ts @@ -1,4 +1,4 @@ -import { BlockNoteSchema } from "@blocknote/core"; +import { BlockNoteSchema, defaultBlockSpecs, PageBreak } from "@blocknote/core"; import { testDocument } from "@shared/testDocument.js"; import AdmZip from "adm-zip"; import { Packer, Paragraph, TextRun } from "docx"; @@ -10,7 +10,9 @@ import { DOCXExporter } from "./docxExporter.js"; describe("exporter", () => { it("should export a document", { timeout: 10000 }, async () => { const exporter = new DOCXExporter( - BlockNoteSchema.create(), + BlockNoteSchema.create({ + blockSpecs: { ...defaultBlockSpecs, pageBreak: PageBreak }, + }), docxDefaultSchemaMappings ); const doc = await exporter.toDocxJsDocument(testDocument); @@ -33,7 +35,9 @@ describe("exporter", () => { { timeout: 10000 }, async () => { const exporter = new DOCXExporter( - BlockNoteSchema.create(), + BlockNoteSchema.create({ + blockSpecs: { ...defaultBlockSpecs, pageBreak: PageBreak }, + }), docxDefaultSchemaMappings ); diff --git a/packages/xl-pdf-exporter/package.json b/packages/xl-pdf-exporter/package.json index 4a50afbfe..4e0b753e3 100644 --- a/packages/xl-pdf-exporter/package.json +++ b/packages/xl-pdf-exporter/package.json @@ -61,6 +61,7 @@ "@types/jsdom": "^21.1.7", "@types/react": "^18.0.25", "@types/react-dom": "^18.0.9", + "react-element-to-jsx-string": "^15.0.0", "eslint": "^8.10.0", "jest-image-snapshot": "^6.4.0", "pdf-to-img": "^4.2.0", diff --git a/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx b/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx index 4abe64202..74a79c182 100644 --- a/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx +++ b/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx @@ -1,608 +1,935 @@ -
- - - - - - - Welcome to this - - - demo 🙌! - - - - - + + + + + - - - Hello World nested - - - - + - - - - Hello World double nested - - - - - - - - - This paragraph has a background color - - - - - - - Paragraph - - - - - + + + + + - - Heading - - - - - + + Hello World nested + + + + - - Heading right - - - - - - - justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - - - - + + + + Hello World double nested + + + + + + + + + + + + + This paragraph has a background color + + + + + + + + + Paragraph + + + + + + + - - - - • - - - - - Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - - - - - + Heading + + + + + + + - + Heading right + + + + + + + + + justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + + + + + + + + + + + Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + + + + + + + - - - - • - - - - + + + Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - - - - - + + + + + + - - - - • - - - - + + + Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - - - - - + + + + + + - - - - 1. - - - - + + + Numbered List Item 1 - - - - - + + + + + + - - - - 2. - - - - + + + Numbered List Item 2 - - - - - + + + + - - - - - 1. - - - - - Numbered List Item Nested 1 - - - - - - - - - 2. - - - - - Numbered List Item Nested 2 - - - - - - - - - 3. - - - - - Numbered List Item Nested funky right - - - - - - - - - 4. - - - - - Numbered List Item Nested funky center - - - - - - - - - - - 1. - - - - - Numbered List Item - - - - - - - - + - - - - - - Check List Item - - - - - - - - - - Wide Cell - - - - - Table Cell - - - - - Table Cell - - - - - - - Wide Cell - - - - - Table Cell - - - - - Table Cell - - - - - - - Wide Cell - - - - - Table Cell - - - - - Table Cell - - - - - - - - - + + + Numbered List Item Nested 1 + + + + + + + - - - - - Open file - - - - - - - - - - From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg - - - - - - - - - - - - + + + Numbered List Item Nested 2 + + + + + + + - - - - - Open video file - - - - - From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm - - - - - - - + + + Numbered List Item Nested funky right + + + + + + + - - - - - Open audio file - - - - + + + Numbered List Item Nested funky center + + + + + + + + + + + + + + + Numbered List Item + + + + + + + + }> + + + Check List Item + + + + + + + + + + + + + + + - From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3 - - - - - - - - - + + + + Open file + + + + + + + + + + + + From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg + + + + + + + + + + + + + + + + - - - - - - audio.mp3 - - - - + + + Open video file + + + + + From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm + + + + + + + + + - Audio file caption - - - - - - + + + + Open audio file + + + + + From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3 + + + + + + + + + + + + + + - Inline Content: - - - - + + + + audio.mp3 + + + + + Audio file caption + + + + + + + + + Inline Content: + + + + + + + + + Styled Text + + + {' '} + + + + Link + + + + + + + +
+ + + + + - - - Styled Text - - - - - - - Link - - - - - - - - - - Table Cell 1 - - - - - Table Cell 2 - - - - - Table Cell 3 - - - - - - - Table Cell 4 - - - - - Table Cell Bold 5 - - - - - Table Cell 6 - - - - - - - Table Cell 7 - - - - - Table Cell 8 - - - - - Table Cell 9 - - - - - - - - - const helloWorld = (message) => { - - - console.log("Hello World", message); - - - }; - - - - - - - \ No newline at end of file + {`const helloWorld = (message) => {`} + + + console.log("Hello World", message); + + + {`};`} + + + + + + \ No newline at end of file diff --git a/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx b/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx index 7f90f6231..3c6a68a2f 100644 --- a/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx +++ b/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx @@ -1,618 +1,960 @@ -
- - + + - - - Header - - - - - - - Welcome to this - - - demo 🙌! - - - - - + Header + + + + + + - - - Hello World nested - - - - + - - - - Hello World double nested - - - - - - - - - This paragraph has a background color - - - - - - - Paragraph - - - - - + + + + + - - Heading - - - - - + + Hello World nested + + + + - - Heading right - - - - - - - justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - - - - + + + + Hello World double nested + + + + + + + + + + + + + This paragraph has a background color + + + + + + + + + Paragraph + + + + + + + - - - - • - - - - - Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - - - - - + Heading + + + + + + + - + Heading right + + + + + + + + + justified paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + + + + + + + + + + + Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + + + + + + + - - - - • - - - - + + + Bullet List Item. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - - - - - + + + + + + - - - - • - - - - + + + Bullet List Item right. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - - - - - + + + + + + - - - - 1. - - - - + + + Numbered List Item 1 - - - - - + + + + + + - - - - 2. - - - - + + + Numbered List Item 2 - - - - - + + + + - - - - - 1. - - - - - Numbered List Item Nested 1 - - - - - - - - - 2. - - - - - Numbered List Item Nested 2 - - - - - - - - - 3. - - - - - Numbered List Item Nested funky right - - - - - - - - - 4. - - - - - Numbered List Item Nested funky center - - - - - - - - - - - 1. - - - - - Numbered List Item - - - - - - - - + - - - - - - Check List Item - - - - - - - - - - Wide Cell - - - - - Table Cell - - - - - Table Cell - - - - - - - Wide Cell - - - - - Table Cell - - - - - Table Cell - - - - - - - Wide Cell - - - - - Table Cell - - - - - Table Cell - - - - - - - - - + + + Numbered List Item Nested 1 + + + + + + + - - - - - Open file - - - - - - - - - - From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg - - - - - - - - - - - - + + + Numbered List Item Nested 2 + + + + + + + - - - - - Open video file - - - - - From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm - - - - - - - + + + Numbered List Item Nested funky right + + + + + + + - - - - - Open audio file - - - - + + + Numbered List Item Nested funky center + + + + + + + + + + + + + + + Numbered List Item + + + + + + + + }> + + + Check List Item + + + + + + + +
+ + + + + + + - From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3 - - - - - - - - - + + + + Open file + + + + + + + + + + + + From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg + + + + + + + + + + + + + + + + - - - - - - audio.mp3 - - - - + + + Open video file + + + + + From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm + + + + + + + + + - Audio file caption - - - - - - + + + + Open audio file + + + + + From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3 + + + + + + + + + + + + + + - Inline Content: - - - - + + + + audio.mp3 + + + + + Audio file caption + + + + + + + + + Inline Content: + + + + + + + + + Styled Text + + + {' '} + + + + Link + + + + + + + +
+ + + + + - - - Styled Text - - - - - - - Link - - - - - - - - - - Table Cell 1 - - - - - Table Cell 2 - - - - - Table Cell 3 - - - - - - - Table Cell 4 - - - - - Table Cell Bold 5 - - - - - Table Cell 6 - - - - - - - Table Cell 7 - - - - - Table Cell 8 - - - - - Table Cell 9 - - - - - - - - - const helloWorld = (message) => { - - - console.log("Hello World", message); - - - }; - - - - - - - Footer - - - - - \ No newline at end of file + {`const helloWorld = (message) => {`} + + + console.log("Hello World", message); + + + {`};`} + + + + + + + Footer + + + + \ No newline at end of file diff --git a/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx b/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx index d555090c4..9e75c2d27 100644 --- a/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx +++ b/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx @@ -2,6 +2,7 @@ import { BlockMapping, DefaultBlockSchema, DefaultProps, + pageBreakSchema, StyledText, } from "@blocknote/core"; import { Image, Link, Path, Svg, Text, View } from "@react-pdf/renderer"; @@ -17,7 +18,7 @@ const PIXELS_PER_POINT = 0.75; const FONT_SIZE = 16; export const pdfBlockMappingForDefaultSchema: BlockMapping< - DefaultBlockSchema, + DefaultBlockSchema & typeof pageBreakSchema.blockSchema, any, any, React.ReactElement, @@ -100,6 +101,9 @@ export const pdfBlockMappingForDefaultSchema: BlockMapping< ); }, + pageBreak: () => { + return ; + }, audio: (block, exporter) => { return ( diff --git a/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx b/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx index e21fed3ab..0d7df1134 100644 --- a/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx +++ b/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx @@ -6,10 +6,11 @@ import { defaultBlockSpecs, defaultInlineContentSpecs, defaultStyleSpecs, + PageBreak, } from "@blocknote/core"; import { Text } from "@react-pdf/renderer"; import { testDocument } from "@shared/testDocument.js"; -import { prettyDOM, render } from "@testing-library/react"; +import reactElementToJSXString from "react-element-to-jsx-string"; import { describe, expect, it } from "vitest"; import { pdfDefaultSchemaMappings } from "./defaultSchema/index.js"; import { PDFExporter } from "./pdfExporter.js"; @@ -26,6 +27,7 @@ describe("exporter", () => { const schema = BlockNoteSchema.create({ blockSpecs: { ...defaultBlockSpecs, + pageBreak: PageBreak, extraBlock: createBlockSpec( { content: "none", @@ -155,13 +157,15 @@ describe("exporter", () => { it("should export a document", async () => { const exporter = new PDFExporter( - BlockNoteSchema.create(), + BlockNoteSchema.create({ + blockSpecs: { ...defaultBlockSpecs, pageBreak: PageBreak }, + }), pdfDefaultSchemaMappings ); const transformed = await exporter.toReactPDFDocument(testDocument); - const view = render(transformed); - const str = prettyDOM(view.container, undefined, { highlight: false }); + const str = reactElementToJSXString(transformed); + expect(str).toMatchFileSnapshot("__snapshots__/example.jsx"); // would be nice to compare pdf images, but currently doesn't work on mac os (due to node canvas installation issue) @@ -186,7 +190,9 @@ describe("exporter", () => { it("should export a document with header and footer", async () => { const exporter = new PDFExporter( - BlockNoteSchema.create(), + BlockNoteSchema.create({ + blockSpecs: { ...defaultBlockSpecs, pageBreak: PageBreak }, + }), pdfDefaultSchemaMappings ); @@ -194,8 +200,7 @@ describe("exporter", () => { header: Header, footer: Footer, }); - const view = render(transformed); - const str = prettyDOM(view.container, undefined, { highlight: false }); + const str = reactElementToJSXString(transformed); expect(str).toMatchFileSnapshot( "__snapshots__/exampleWithHeaderAndFooter.jsx" ); diff --git a/packages/xl-pdf-exporter/src/pdf/pdfExporter.tsx b/packages/xl-pdf-exporter/src/pdf/pdfExporter.tsx index 06cbf6931..aa4b03d4a 100644 --- a/packages/xl-pdf-exporter/src/pdf/pdfExporter.tsx +++ b/packages/xl-pdf-exporter/src/pdf/pdfExporter.tsx @@ -63,7 +63,6 @@ export class PDFExporter< fontSize: FONT_SIZE * PIXELS_PER_POINT, // pixels lineHeight: 1.5, }, - section: {}, block: {}, blockChildren: {}, header: {}, @@ -145,6 +144,11 @@ export class PDFExporter< numberedListIndex ); // TODO: any + if (b.type === "pageBreak") { + ret.push(self); + continue; + } + const style = this.blocknoteDefaultPropsToReactPDFStyle(b.props as any); ret.push( <> @@ -255,9 +259,7 @@ export class PDFExporter< {options.header} )} - - {await this.transformBlocks(blocks)} - + {await this.transformBlocks(blocks)} {options.footer && (