Exporting documents to Email (HTML)
This example exports the current document (all blocks) as an HTML file for use in emails, and downloads it to your computer.
Try it out: Edit the document and click "Download email .html" at the top to download the HTML file.
import { BlockNoteSchema, COLORS_DARK_MODE_DEFAULT, COLORS_DEFAULT, combineByGroup, withPageBreak,} from "@blocknote/core";import { filterSuggestionItems } from "@blocknote/core/extensions";import "@blocknote/core/fonts/inter.css";import { BlockNoteView } from "@blocknote/mantine";import "@blocknote/mantine/style.css";import { SuggestionMenuController, getDefaultReactSlashMenuItems, getPageBreakReactSlashMenuItems, useBlockNoteContext, useCreateBlockNote, usePrefersColorScheme,} from "@blocknote/react";import { ReactEmailExporter, reactEmailDefaultSchemaMappings,} from "@blocknote/xl-email-exporter";import { useEffect, useMemo, useState } from "react";import "./styles.css";export default function App() { // Stores the editor's contents as an email HTML string for download and // displaying the email. const [emailDocument, setEmailDocument] = useState<string>(""); // Creates a new editor instance. const editor = useCreateBlockNote({ // Adds support for page breaks. schema: withPageBreak(BlockNoteSchema.create()), // Adds support for advanced table features. tables: { splitCells: true, cellBackgroundColor: true, cellTextColor: true, headers: true, }, // Sets initial editor content. initialContent: [ { type: "paragraph", content: [ { type: "text", text: "Welcome to this ", styles: { italic: true, }, }, { type: "text", text: "demo!", styles: { italic: true, bold: true, }, }, ], children: [ { type: "paragraph", content: "Hello World nested", children: [ { type: "paragraph", content: "Hello World double nested", }, ], }, ], }, { type: "paragraph", content: [ { type: "text", text: "This paragraph has a background color", styles: { bold: true }, }, ], props: { backgroundColor: "red", }, }, { type: "divider" }, { type: "paragraph", content: [ { type: "text", text: "This one too, but it's blue", styles: { italic: true }, }, ], props: { backgroundColor: "blue", }, }, { type: "paragraph", content: "Paragraph", }, { type: "heading", content: "Heading", }, { type: "heading", content: "Heading right", props: { textAlignment: "right", }, }, { type: "paragraph", content: "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.", props: { textAlignment: "justify", }, }, { type: "bulletListItem", content: "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.", children: [ { type: "bulletListItem", content: "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.", }, { type: "bulletListItem", content: "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.", props: { textAlignment: "right", }, }, { type: "numberedListItem", content: "Numbered List Item 1", }, { type: "numberedListItem", content: "Numbered List Item 2", children: [ { type: "numberedListItem", content: "Numbered List Item Nested 1", }, { type: "numberedListItem", content: "Numbered List Item Nested 2", }, { type: "numberedListItem", content: "Numbered List Item Nested funky right", props: { textAlignment: "right", backgroundColor: "red", textColor: "blue", }, }, { type: "numberedListItem", content: "Numbered List Item Nested funky center", props: { textAlignment: "center", backgroundColor: "red", textColor: "blue", }, }, ], }, ], }, { type: "numberedListItem", content: "Numbered List Item", }, { type: "checkListItem", content: "Check List Item", }, { type: "table", content: { type: "tableContent", rows: [ { cells: ["Table Cell", "Table Cell", "Table Cell"], }, { cells: ["Table Cell", "Table Cell", "Table Cell"], }, { cells: ["Table Cell", "Table Cell", "Table Cell"], }, ], }, }, { type: "pageBreak", }, { type: "file", }, { type: "image", props: { url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", caption: "From https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", }, }, { type: "image", props: { previewWidth: 200, url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", textAlignment: "right", }, }, { type: "video", props: { url: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", caption: "From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", }, }, { type: "audio", props: { url: "https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", caption: "From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", }, }, { type: "paragraph", }, { type: "paragraph", content: [ { type: "text", text: "Inline Content:", styles: { bold: true }, }, ], }, { type: "paragraph", content: [ { type: "text", text: "Styled Text", styles: { bold: true, italic: true, textColor: "red", backgroundColor: "blue", }, }, { type: "text", text: " ", styles: {}, }, { type: "link", content: "Link", href: "https://www.blocknotejs.org", }, ], }, { type: "table", content: { type: "tableContent", rows: [ { cells: ["Table Cell 1", "Table Cell 2", "Table Cell 3"], }, { cells: [ "Table Cell 4", [ { type: "text", text: "Table Cell Bold 5", styles: { bold: true, }, }, ], "Table Cell 6", ], }, { cells: ["Table Cell 7", "Table Cell 8", "Table Cell 9"], }, ], }, }, { type: "codeBlock", props: { language: "javascript", }, content: `const helloWorld = (message) => { console.log("Hello World", message);};`, }, ], }); // Additional Slash Menu items for page breaks. const getSlashMenuItems = useMemo( () => async (query: string) => filterSuggestionItems( combineByGroup( getDefaultReactSlashMenuItems(editor), getPageBreakReactSlashMenuItems(editor), ), query, ), [editor], ); const existingContext = useBlockNoteContext(); const systemColorScheme = usePrefersColorScheme(); // Exports the editor document to email whenever it changes. const onChange = async () => { const colorScheme = existingContext?.colorSchemePreference || systemColorScheme; const exporter = new ReactEmailExporter( editor.schema, reactEmailDefaultSchemaMappings, { colors: colorScheme === "dark" ? COLORS_DARK_MODE_DEFAULT : COLORS_DEFAULT, }, ); const emailHtml = await exporter.toReactEmailDocument(editor.document); setEmailDocument(emailHtml); }; // Exports the inital editor document to PDF. useEffect(() => { onChange(); }, []); // Downloads the email in HTML format. const onDownloadClick = async () => { const blob = new Blob([emailDocument], { type: "text/html" }); const link = document.createElement("a"); link.href = window.URL.createObjectURL(blob); link.download = "My Document (blocknote export).html"; document.body.appendChild(link); link.dispatchEvent( new MouseEvent("click", { bubbles: true, cancelable: true, view: window, }), ); link.remove(); window.URL.revokeObjectURL(link.href); }; // Renders the editor instance and Email view. return ( <div className="views"> <div className="view-wrapper"> <div className="view-label">Editor Input</div> <div className="view"> <BlockNoteView editor={editor} slashMenu={false} onChange={onChange}> <SuggestionMenuController triggerCharacter={"/"} getItems={getSlashMenuItems} /> </BlockNoteView> </div> </div> <div className="view-wrapper"> <div className="view-label"> Email Output <span className="view-label-download" onClick={onDownloadClick}> Download </span> </div> <div className="view"> <div className="email" dangerouslySetInnerHTML={{ __html: emailDocument }} /> </div> </div> </div> );}.views { container-name: views; container-type: inline-size; display: flex; flex-direction: row; flex-wrap: wrap; gap: 8px; height: 100%; padding: 8px;}.view-wrapper { display: flex; flex-direction: column; height: calc(50% - 4px); width: 100%;}@container views (width > 1024px) { .view-wrapper { height: 100%; width: calc(50% - 4px); }}.view-label { color: #0090ff; display: flex; font-size: 12px; font-weight: bold; justify-content: space-between; margin-inline: 16px;}.view-label-download { cursor: pointer; text-decoration: underline;}.view { border: solid #0090ff 1px; border-radius: 16px; flex: 1; height: 0; padding: 8px;}.view .bn-container { height: 100%; margin: 0; max-width: none; padding: 0;}.view .bn-editor { height: 100%; overflow: auto;}.view .email { height: 100%; overflow: auto;}