From ac10b90f7c6583ea9abb5fdaa351b9ec16c02526 Mon Sep 17 00:00:00 2001 From: Arek Nawo Date: Wed, 15 Jan 2025 13:26:10 +0100 Subject: [PATCH 1/8] feat: Page break block --- examples/01-basic/04-all-blocks/App.tsx | 1 + .../pageBreak/basic/external.html | 1 + .../pageBreak/basic/internal.html | 1 + .../__snapshots__/pageBreak/basic/markdown.md | 0 .../nodeConversions.test.ts.snap | 16 +++ .../src/api/testUtil/cases/defaultSchema.ts | 8 ++ .../PageBreakBlockContent.tsx | 48 +++++++ packages/core/src/blocks/defaultBlocks.ts | 2 + packages/core/src/editor/Block.css | 16 ++- .../getDefaultSlashMenuItems.ts | 12 ++ packages/core/src/i18n/locales/ar.ts | 6 + packages/core/src/i18n/locales/de.ts | 6 + packages/core/src/i18n/locales/en.ts | 6 + packages/core/src/i18n/locales/es.ts | 6 + packages/core/src/i18n/locales/fr.ts | 6 + packages/core/src/i18n/locales/hr.ts | 126 ++++++++++-------- packages/core/src/i18n/locales/is.ts | 6 + packages/core/src/i18n/locales/ja.ts | 6 + packages/core/src/i18n/locales/ko.ts | 6 + packages/core/src/i18n/locales/nl.ts | 6 + packages/core/src/i18n/locales/pl.ts | 6 + packages/core/src/i18n/locales/pt.ts | 6 + packages/core/src/i18n/locales/ru.ts | 6 + packages/core/src/i18n/locales/vi.ts | 6 + packages/core/src/i18n/locales/zh.ts | 6 + packages/core/src/index.ts | 1 + .../getDefaultReactSlashMenuItems.tsx | 2 + shared/testDocument.ts | 1 + 28 files changed, 264 insertions(+), 55 deletions(-) create mode 100644 packages/core/src/api/exporters/html/__snapshots__/pageBreak/basic/external.html create mode 100644 packages/core/src/api/exporters/html/__snapshots__/pageBreak/basic/internal.html create mode 100644 packages/core/src/api/exporters/markdown/__snapshots__/pageBreak/basic/markdown.md create mode 100644 packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.tsx diff --git a/examples/01-basic/04-all-blocks/App.tsx b/examples/01-basic/04-all-blocks/App.tsx index 6a34751b7..85a191c4e 100644 --- a/examples/01-basic/04-all-blocks/App.tsx +++ b/examples/01-basic/04-all-blocks/App.tsx @@ -118,6 +118,7 @@ export default function App() { ], }, }, + { type: "pageBreak" }, { type: "file", }, 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..53478e2e4 100644 --- a/packages/core/src/api/testUtil/cases/defaultSchema.ts +++ b/packages/core/src/api/testUtil/cases/defaultSchema.ts @@ -202,6 +202,14 @@ export const defaultSchemaTestCases: EditorTestCases< }, ], }, + { + name: "pageBreak/basic", + blocks: [ + { + type: "pageBreak", + }, + ], + }, { name: "file/button", blocks: [ diff --git a/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.tsx b/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.tsx new file mode 100644 index 000000000..1ba43ae16 --- /dev/null +++ b/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.tsx @@ -0,0 +1,48 @@ +import { + createBlockSpec, + CustomBlockConfig, + Props, +} from "../../schema/index.js"; + +export const pageBreakConfig = { + type: "pageBreak" as const, + propSchema: {}, + content: "none", + isFileBlock: 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/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index 81dc0e49a..d21d74c61 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -22,6 +22,7 @@ import { import { AudioBlock } from "./AudioBlockContent/AudioBlockContent.js"; import { CodeBlock } from "./CodeBlockContent/CodeBlockContent.js"; +import { PageBreak } from "./PageBreakBlockContent/PageBreakBlockContent.js"; import { FileBlock } from "./FileBlockContent/FileBlockContent.js"; import { Heading } from "./HeadingBlockContent/HeadingBlockContent.js"; import { ImageBlock } from "./ImageBlockContent/ImageBlockContent.js"; @@ -38,6 +39,7 @@ export const defaultBlockSpecs = { paragraph: Paragraph, heading: Heading, codeBlock: CodeBlock, + pageBreak: PageBreak, bulletListItem: BulletListItem, numberedListItem: NumberedListItem, checkListItem: CheckListItem, diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index 54849c2b6..45628cbb7 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -308,6 +308,20 @@ NESTED BLOCKS transition-delay: 0.1s; } +/* PAGE BREAK */ +.bn-block-content[data-content-type="pageBreak"] > div { + width: 100%; + height: 8px; + background: linear-gradient( + to right, + rgb(22 22 22) 50%, + rgb(255, 255, 255, 0) 0% + ); + background-position: center; + background-size: 16px 1.5px; + background-repeat: repeat-x; +} + /* FILES */ /* Element that wraps content for all file blocks */ @@ -336,7 +350,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/getDefaultSlashMenuItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts index fb6fbad7e..481221c39 100644 --- a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +++ b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts @@ -186,6 +186,18 @@ export function getDefaultSlashMenuItems< }); } + if (checkDefaultBlockTypeInSchema("pageBreak", editor)) { + items.push({ + onItemClick: () => { + insertOrUpdateBlock(editor, { + type: "pageBreak", + }); + }, + key: "page_break", + ...editor.dictionary.slash_menu.page_break, + }); + } + if (checkDefaultBlockTypeInSchema("table", editor)) { items.push({ onItemClick: () => { 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..1a350d211 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,6 +6,7 @@ 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/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/components/SuggestionMenu/getDefaultReactSlashMenuItems.tsx b/packages/react/src/components/SuggestionMenu/getDefaultReactSlashMenuItems.tsx index 6e7bc96be..56bbbc706 100644 --- a/packages/react/src/components/SuggestionMenu/getDefaultReactSlashMenuItems.tsx +++ b/packages/react/src/components/SuggestionMenu/getDefaultReactSlashMenuItems.tsx @@ -21,6 +21,7 @@ import { RiVolumeUpFill, RiCodeBlock, } from "react-icons/ri"; +import { TbPageBreak } from "react-icons/tb"; import { DefaultReactSuggestionItem } from "./types.js"; const icons = { @@ -38,6 +39,7 @@ const icons = { file: RiFile2Line, emoji: RiEmotionFill, code_block: RiCodeBlock, + page_break: TbPageBreak, }; export function getDefaultReactSlashMenuItems< diff --git a/shared/testDocument.ts b/shared/testDocument.ts index 281e5f9d7..18b101165 100644 --- a/shared/testDocument.ts +++ b/shared/testDocument.ts @@ -79,6 +79,7 @@ export const testDocument = partialBlocksToBlocksForTesting( type: "codeBlock", content: "Code Block\nLine 2", }, + { type: "pageBreak" }, { type: "bulletListItem", content: From c40c77371acb9706bb090ab954d028bd2803f516 Mon Sep 17 00:00:00 2001 From: Arek Nawo Date: Wed, 15 Jan 2025 15:51:33 +0100 Subject: [PATCH 2/8] feat: Add page break support to PDF & DOCX exporters --- package-lock.json | 39 + .../src/docx/__snapshots__/basic/document.xml | 5 + .../src/docx/defaultSchema/blocks.ts | 6 + packages/xl-pdf-exporter/package.json | 1 + .../src/pdf/__snapshots__/example.jsx | 1450 +++++++++------- .../exampleWithHeaderAndFooter.jsx | 1483 ++++++++++------- .../src/pdf/defaultSchema/blocks.tsx | 3 + .../src/pdf/pdfExporter.test.tsx | 9 +- .../xl-pdf-exporter/src/pdf/pdfExporter.tsx | 10 +- 9 files changed, 1852 insertions(+), 1154 deletions(-) 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/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml b/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml index 252040fe3..0781aed46 100644 --- a/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml +++ b/packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml @@ -121,6 +121,11 @@ Line 2 + + + + + diff --git a/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts b/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts index 43bfba978..aac6ba3b0 100644 --- a/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts +++ b/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts @@ -11,6 +11,7 @@ import { ExternalHyperlink, IParagraphOptions, ImageRun, + PageBreak, Paragraph, ParagraphChild, ShadingType, @@ -144,6 +145,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-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 e7158793d..e2fc94265 100644 --- a/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx +++ b/packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx @@ -1,592 +1,906 @@ -
- - - - - - - 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. - - - - - - codeBlock not implemented - - - + + + + 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. + + + + + + + + codeBlock not implemented + + + + + + + + + + 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 - - - - - From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3 - - - - - - - - - + + + Numbered List Item Nested funky center + + + + + + + + + + + + + + + Numbered List Item + + + + + + + + }> + + + Check List Item + + + + + + + + + + + + + + + - - - - - - audio.mp3 - - - - - Audio file caption - - - - - - + + + Open file + + + + + + + + + + + + From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg + + + + + + + + + + + + + + + + - Inline Content: - - - - - - + + + + Open video file + + + + + From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm + + + + + + + + + - Styled Text - - - - - + + + + Open audio file + + + + + From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3 + + + + + + + + + + + + + + - - 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 - - - - - - - - - \ No newline at end of file + + + + + audio.mp3 + + + + + Audio file caption + + + + + + + + + Inline Content: + + + + + + + + + Styled Text + + + {' '} + + + + Link + + + + + + + +
+ + + + \ 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 fc93b8c7d..b0e6f03cc 100644 --- a/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx +++ b/packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx @@ -1,602 +1,931 @@ -
- - + + - - - 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. - - - - - - codeBlock not implemented - - - + + + + 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. + + + + + + + + codeBlock not implemented + + + + + + + + + + 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 - - - - - From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3 - - - - - - - - - + + + Numbered List Item Nested funky center + + + + + + + + + + + + + + + Numbered List Item + + + + + + + + }> + + + Check List Item + + + + + + + +
+ + + + + + + - - - - - - audio.mp3 - - - - - Audio file caption - - - - - - + + + Open file + + + + + + + + + + + + From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg + + + + + + + + + + + + + + + + - Inline Content: - - - - - - + + + + Open video file + + + + + From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm + + + + + + + + + - Styled Text - - - - - + + + + Open audio file + + + + + From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3 + + + + + + + + + + + + + + - - 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 - - - - - - - - - Footer - - - - - \ No newline at end of file + + + + + audio.mp3 + + + + + Audio file caption + + + + + + + + + Inline Content: + + + + + + + + + Styled Text + + + {' '} + + + + Link + + + + + + + +
+ + + + + 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 400422d28..537c5679b 100644 --- a/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx +++ b/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx @@ -71,6 +71,9 @@ export const pdfBlockMappingForDefaultSchema: BlockMapping< codeBlock: (block) => { return {block.type + " not implemented"}; }, + 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..52b62c40e 100644 --- a/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx +++ b/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx @@ -9,7 +9,7 @@ import { } 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"; @@ -160,8 +160,8 @@ describe("exporter", () => { ); 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) @@ -194,8 +194,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 969f1ce75..db795792a 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( <> @@ -247,9 +251,7 @@ export class PDFExporter< {options.header} )} - - {await this.transformBlocks(blocks)} - + {await this.transformBlocks(blocks)} {options.footer && ( Date: Wed, 15 Jan 2025 16:32:21 +0100 Subject: [PATCH 3/8] feat: Add CSS to support Page break when printing --- packages/core/src/editor/Block.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index 45628cbb7..173f81853 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -322,6 +322,12 @@ NESTED BLOCKS background-repeat: repeat-x; } +@media print { + .bn-block-content[data-content-type="pageBreak"] > div { + page-break-after: always; + } +} + /* FILES */ /* Element that wraps content for all file blocks */ From e42d6c5f3493bd1c00d1b2d5b201351b1b2f25fa Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 17 Jan 2025 15:35:35 +0100 Subject: [PATCH 4/8] Small UX changes --- .../blocks/PageBreakBlockContent/PageBreakBlockContent.tsx | 1 + packages/core/src/editor/Block.css | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.tsx b/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.tsx index 1ba43ae16..e1e72ab59 100644 --- a/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.tsx +++ b/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.tsx @@ -9,6 +9,7 @@ export const pageBreakConfig = { propSchema: {}, content: "none", isFileBlock: false, + isSelectable: false, } satisfies CustomBlockConfig; export const pageBreakRender = () => { const pageBreak = document.createElement("div"); diff --git a/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index 173f81853..97514fd12 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -313,9 +313,9 @@ NESTED BLOCKS width: 100%; height: 8px; background: linear-gradient( - to right, - rgb(22 22 22) 50%, - rgb(255, 255, 255, 0) 0% + to right, + rgb(125, 121, 122) 50%, + rgb(125, 121, 122, 0) 0% ); background-position: center; background-size: 16px 1.5px; From b34346d5f863118ceaa6756a778372764fac54ee Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 17 Jan 2025 16:22:42 +0100 Subject: [PATCH 5/8] Removed page break from default schema --- examples/01-basic/04-all-blocks/App.tsx | 5 ++- .../src/api/testUtil/cases/defaultSchema.ts | 3 +- ...ckContent.tsx => PageBreakBlockContent.ts} | 0 .../getPageBreakSlashMenuItems.ts | 45 +++++++++++++++++++ .../blocks/PageBreakBlockContent/schema.ts | 40 +++++++++++++++++ packages/core/src/blocks/defaultBlocks.ts | 2 - .../SuggestionMenu/DefaultSuggestionItem.ts | 2 +- .../getDefaultSlashMenuItems.ts | 12 ----- packages/core/src/index.ts | 2 + .../getPageBreakReactSlashMenuItems.tsx | 29 ++++++++++++ .../getDefaultReactSlashMenuItems.tsx | 2 - packages/react/src/index.ts | 1 + .../src/docx/defaultSchema/blocks.ts | 3 +- .../src/docx/docxExporter.test.ts | 10 +++-- .../src/pdf/defaultSchema/blocks.tsx | 3 +- .../src/pdf/pdfExporter.test.tsx | 10 ++++- shared/testDocument.ts | 6 ++- 17 files changed, 148 insertions(+), 27 deletions(-) rename packages/core/src/blocks/PageBreakBlockContent/{PageBreakBlockContent.tsx => PageBreakBlockContent.ts} (100%) create mode 100644 packages/core/src/blocks/PageBreakBlockContent/getPageBreakSlashMenuItems.ts create mode 100644 packages/core/src/blocks/PageBreakBlockContent/schema.ts create mode 100644 packages/react/src/blocks/PageBreakBlockContent/getPageBreakReactSlashMenuItems.tsx diff --git a/examples/01-basic/04-all-blocks/App.tsx b/examples/01-basic/04-all-blocks/App.tsx index 85a191c4e..79dc6bae1 100644 --- a/examples/01-basic/04-all-blocks/App.tsx +++ b/examples/01-basic/04-all-blocks/App.tsx @@ -3,6 +3,7 @@ import { combineByGroup, filterSuggestionItems, locales, + withPageBreak, } from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; @@ -11,6 +12,7 @@ import { SuggestionMenuController, getDefaultReactSlashMenuItems, useCreateBlockNote, + getPageBreakReactSlashMenuItems, } from "@blocknote/react"; import { getMultiColumnSlashMenuItems, @@ -23,7 +25,7 @@ import { useMemo } from "react"; export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ - schema: withMultiColumn(BlockNoteSchema.create()), + schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())), dropCursor: multiColumnDropCursor, dictionary: { ...locales.en, @@ -193,6 +195,7 @@ export default function App() { const slashMenuItems = useMemo(() => { return combineByGroup( getDefaultReactSlashMenuItems(editor), + getPageBreakReactSlashMenuItems(editor), getMultiColumnSlashMenuItems(editor) ); }, [editor]); diff --git a/packages/core/src/api/testUtil/cases/defaultSchema.ts b/packages/core/src/api/testUtil/cases/defaultSchema.ts index 53478e2e4..2e4623bda 100644 --- a/packages/core/src/api/testUtil/cases/defaultSchema.ts +++ b/packages/core/src/api/testUtil/cases/defaultSchema.ts @@ -6,10 +6,11 @@ import { DefaultInlineContentSchema, DefaultStyleSchema, } from "../../../blocks/defaultBlocks.js"; +import { pageBreakSchema } from "../../../blocks/PageBreakBlockContent/schema.js"; import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; export const defaultSchemaTestCases: EditorTestCases< - DefaultBlockSchema, + DefaultBlockSchema & typeof pageBreakSchema.blockSchema, DefaultInlineContentSchema, DefaultStyleSchema > = { diff --git a/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.tsx b/packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.ts similarity index 100% rename from packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.tsx rename to packages/core/src/blocks/PageBreakBlockContent/PageBreakBlockContent.ts 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/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index d21d74c61..81dc0e49a 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -22,7 +22,6 @@ import { import { AudioBlock } from "./AudioBlockContent/AudioBlockContent.js"; import { CodeBlock } from "./CodeBlockContent/CodeBlockContent.js"; -import { PageBreak } from "./PageBreakBlockContent/PageBreakBlockContent.js"; import { FileBlock } from "./FileBlockContent/FileBlockContent.js"; import { Heading } from "./HeadingBlockContent/HeadingBlockContent.js"; import { ImageBlock } from "./ImageBlockContent/ImageBlockContent.js"; @@ -39,7 +38,6 @@ export const defaultBlockSpecs = { paragraph: Paragraph, heading: Heading, codeBlock: CodeBlock, - pageBreak: PageBreak, bulletListItem: BulletListItem, numberedListItem: NumberedListItem, checkListItem: CheckListItem, 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/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts index 481221c39..fb6fbad7e 100644 --- a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +++ b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts @@ -186,18 +186,6 @@ export function getDefaultSlashMenuItems< }); } - if (checkDefaultBlockTypeInSchema("pageBreak", editor)) { - items.push({ - onItemClick: () => { - insertOrUpdateBlock(editor, { - type: "pageBreak", - }); - }, - key: "page_break", - ...editor.dictionary.slash_menu.page_break, - }); - } - if (checkDefaultBlockTypeInSchema("table", editor)) { items.push({ onItemClick: () => { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1a350d211..072d375bc 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -7,6 +7,8 @@ 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/components/SuggestionMenu/getDefaultReactSlashMenuItems.tsx b/packages/react/src/components/SuggestionMenu/getDefaultReactSlashMenuItems.tsx index 56bbbc706..6e7bc96be 100644 --- a/packages/react/src/components/SuggestionMenu/getDefaultReactSlashMenuItems.tsx +++ b/packages/react/src/components/SuggestionMenu/getDefaultReactSlashMenuItems.tsx @@ -21,7 +21,6 @@ import { RiVolumeUpFill, RiCodeBlock, } from "react-icons/ri"; -import { TbPageBreak } from "react-icons/tb"; import { DefaultReactSuggestionItem } from "./types.js"; const icons = { @@ -39,7 +38,6 @@ const icons = { file: RiFile2Line, emoji: RiEmotionFill, code_block: RiCodeBlock, - page_break: TbPageBreak, }; export function getDefaultReactSlashMenuItems< 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/defaultSchema/blocks.ts b/packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts index 956c93dc7..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"; @@ -57,7 +58,7 @@ function blockPropsToStyles( }; } export const docxBlockMappingForDefaultSchema: BlockMapping< - DefaultBlockSchema, + DefaultBlockSchema & typeof pageBreakSchema.blockSchema, any, any, | Promise 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/src/pdf/defaultSchema/blocks.tsx b/packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx index 266f5d9d5..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, diff --git a/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx b/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx index 52b62c40e..0d7df1134 100644 --- a/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx +++ b/packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx @@ -6,6 +6,7 @@ import { defaultBlockSpecs, defaultInlineContentSpecs, defaultStyleSpecs, + PageBreak, } from "@blocknote/core"; import { Text } from "@react-pdf/renderer"; import { testDocument } from "@shared/testDocument.js"; @@ -26,6 +27,7 @@ describe("exporter", () => { const schema = BlockNoteSchema.create({ blockSpecs: { ...defaultBlockSpecs, + pageBreak: PageBreak, extraBlock: createBlockSpec( { content: "none", @@ -155,7 +157,9 @@ describe("exporter", () => { it("should export a document", async () => { const exporter = new PDFExporter( - BlockNoteSchema.create(), + BlockNoteSchema.create({ + blockSpecs: { ...defaultBlockSpecs, pageBreak: PageBreak }, + }), pdfDefaultSchemaMappings ); @@ -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 ); diff --git a/shared/testDocument.ts b/shared/testDocument.ts index 5194fef07..ccee20402 100644 --- a/shared/testDocument.ts +++ b/shared/testDocument.ts @@ -1,10 +1,14 @@ import { BlockNoteSchema, + defaultBlockSpecs, + PageBreak, partialBlocksToBlocksForTesting, } from "@blocknote/core"; export const testDocument = partialBlocksToBlocksForTesting( - BlockNoteSchema.create(), + BlockNoteSchema.create({ + blockSpecs: { ...defaultBlockSpecs, pageBreak: PageBreak }, + }), [ { type: "paragraph", From 5100f2ab1e6238750ff816dd30930b7a2620cc1a Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 17 Jan 2025 16:37:49 +0100 Subject: [PATCH 6/8] Fixed unit tests --- .../src/api/testUtil/cases/defaultSchema.ts | 7 +- .../src/pdf/__snapshots__/example.jsx | 1491 +++++++++------- .../exampleWithHeaderAndFooter.jsx | 1524 ++++++++++------- 3 files changed, 1848 insertions(+), 1174 deletions(-) diff --git a/packages/core/src/api/testUtil/cases/defaultSchema.ts b/packages/core/src/api/testUtil/cases/defaultSchema.ts index 2e4623bda..c08803dab 100644 --- a/packages/core/src/api/testUtil/cases/defaultSchema.ts +++ b/packages/core/src/api/testUtil/cases/defaultSchema.ts @@ -6,8 +6,12 @@ import { DefaultInlineContentSchema, DefaultStyleSchema, } from "../../../blocks/defaultBlocks.js"; -import { pageBreakSchema } from "../../../blocks/PageBreakBlockContent/schema.js"; +import { + pageBreakSchema, + withPageBreak, +} from "../../../blocks/PageBreakBlockContent/schema.js"; import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; +import { BlockNoteSchema } from "../../../editor/BlockNoteSchema"; export const defaultSchemaTestCases: EditorTestCases< DefaultBlockSchema & typeof pageBreakSchema.blockSchema, @@ -17,6 +21,7 @@ export const defaultSchemaTestCases: EditorTestCases< name: "default schema", createEditor: () => { return BlockNoteEditor.create({ + schema: withPageBreak(BlockNoteSchema.create()), uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY, }); }, 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 From 0bda3e31d405b23703ff0891796a21517653fd40 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 17 Jan 2025 16:42:45 +0100 Subject: [PATCH 7/8] Fixed lint --- packages/core/src/api/testUtil/cases/defaultSchema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/api/testUtil/cases/defaultSchema.ts b/packages/core/src/api/testUtil/cases/defaultSchema.ts index c08803dab..dfdff6278 100644 --- a/packages/core/src/api/testUtil/cases/defaultSchema.ts +++ b/packages/core/src/api/testUtil/cases/defaultSchema.ts @@ -11,7 +11,7 @@ import { withPageBreak, } from "../../../blocks/PageBreakBlockContent/schema.js"; import { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; -import { BlockNoteSchema } from "../../../editor/BlockNoteSchema"; +import { BlockNoteSchema } from "../../../editor/BlockNoteSchema.js"; export const defaultSchemaTestCases: EditorTestCases< DefaultBlockSchema & typeof pageBreakSchema.blockSchema, From 79f5574393f1c7a198f7024f57c0211a38fe1ea6 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 17 Jan 2025 18:42:14 +0100 Subject: [PATCH 8/8] Implemented PR feedback --- examples/01-basic/04-all-blocks/App.tsx | 74 +------------------ .../05-converting-blocks-to-pdf/App.tsx | 36 ++++++++- .../06-converting-blocks-to-docx/App.tsx | 35 ++++++++- packages/core/src/editor/Block.css | 11 +-- packages/react/src/editor/styles.css | 4 + 5 files changed, 74 insertions(+), 86 deletions(-) diff --git a/examples/01-basic/04-all-blocks/App.tsx b/examples/01-basic/04-all-blocks/App.tsx index 79dc6bae1..935e96d93 100644 --- a/examples/01-basic/04-all-blocks/App.tsx +++ b/examples/01-basic/04-all-blocks/App.tsx @@ -1,36 +1,11 @@ -import { - BlockNoteSchema, - combineByGroup, - filterSuggestionItems, - locales, - withPageBreak, -} from "@blocknote/core"; import "@blocknote/core/fonts/inter.css"; import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/mantine/style.css"; -import { - SuggestionMenuController, - getDefaultReactSlashMenuItems, - useCreateBlockNote, - getPageBreakReactSlashMenuItems, -} 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(withPageBreak(BlockNoteSchema.create())), - dropCursor: multiColumnDropCursor, - dictionary: { - ...locales.en, - multi_column: multiColumnLocales.en, - }, initialContent: [ { type: "paragraph", @@ -53,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", @@ -120,7 +66,6 @@ export default function App() { ], }, }, - { type: "pageBreak" }, { type: "file", }, @@ -192,21 +137,6 @@ export default function App() { ], }); - const slashMenuItems = useMemo(() => { - return combineByGroup( - getDefaultReactSlashMenuItems(editor), - getPageBreakReactSlashMenuItems(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/packages/core/src/editor/Block.css b/packages/core/src/editor/Block.css index 97514fd12..284cbeb07 100644 --- a/packages/core/src/editor/Block.css +++ b/packages/core/src/editor/Block.css @@ -311,15 +311,8 @@ NESTED BLOCKS /* PAGE BREAK */ .bn-block-content[data-content-type="pageBreak"] > div { width: 100%; - height: 8px; - background: linear-gradient( - to right, - rgb(125, 121, 122) 50%, - rgb(125, 121, 122, 0) 0% - ); - background-position: center; - background-size: 16px 1.5px; - background-repeat: repeat-x; + height: 0; + border-top: dotted rgb(125, 121, 122) 4px; } @media print { 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); +}