建自定義郵件節(jié)點(diǎn))
如何用 EmailNode 的 renderToReactEmail 為 React Email Editor 創(chuàng)建自定義郵件節(jié)點(diǎn)【免費(fèi)下載鏈接】react-email Build and send emails using React項(xiàng)目地址: https://gitcode.com/GitHub_Trending/re/react-email這篇文章解決的問題是在 react-email 的react-email/editor基于 TipTap 的郵件編輯器中內(nèi)置的StarterKit只覆蓋了一組郵件場景需要的節(jié)點(diǎn)當(dāng)你需要一種內(nèi)置節(jié)點(diǎn)之外的結(jié)構(gòu)——比如帶高亮底色的 Callout 提示塊——并且希望它在編輯器里能輸入、能粘貼識別、還能在導(dǎo)出時(shí)序列化成郵件 HTML就需要用EmailNode創(chuàng)建一個(gè)自定義擴(kuò)展并實(shí)現(xiàn)其中唯一的新增必選方法renderToReactEmail()。完成后的結(jié)果是編輯器里可以插入自定義節(jié)點(diǎn)調(diào)用composeReactEmail導(dǎo)出時(shí)該節(jié)點(diǎn)會(huì)走你的renderToReactEmail渲染邏輯出現(xiàn)在最終html輸出中。適用前提來自編輯器文檔的 Prerequisites 說明React 18使用支持 package exports 的打包器Vite、Next.js、Webpack 5 等react-email/editor包要求 Node 20見 packages/editor/package.json 的engines字段。安裝編輯器包npm install react-email/editor文檔同時(shí)給出 yarn / pnpm / bun 的等價(jià)命令yarn add/pnpm add/bun add react-email/editor。本文后續(xù)都基于低層級路徑EditorProvider 擴(kuò)展數(shù)組因?yàn)樽远x節(jié)點(diǎn)必須顯式加入 extensions 數(shù)組如果你使用獨(dú)立的EmailEditor組件文檔建議改走低層級路徑來獲得對擴(kuò)展列表的完全控制見 getting-started.mdx 的 Lower-level setup。EmailNode 與 renderToReactEmail 的工作機(jī)制EmailNode擴(kuò)展了 TipTap 的Node類額外要求一個(gè)renderToReactEmail()方法它決定節(jié)點(diǎn)在通過composeReactEmail導(dǎo)出為郵件 HTML 時(shí)如何被序列化見 email-node.mdx。創(chuàng)建擴(kuò)展的配置對象接受所有標(biāo)準(zhǔn) TipTap Node 選項(xiàng)加上renderToReactEmail。文檔中三個(gè)方法的分工parseHTML()識別粘貼或?qū)氲?HTML讓已有內(nèi)容能被解析回該節(jié)點(diǎn)renderHTML()控制節(jié)點(diǎn)在編輯器內(nèi)的外觀renderToReactEmail()控制導(dǎo)出時(shí)該節(jié)點(diǎn)渲染成什么 React Email 結(jié)構(gòu)。renderToReactEmail收到的 props來自 email-node.mdx 的 props 表PropType說明childrenReact.ReactNode該節(jié)點(diǎn)序列化后的子內(nèi)容styleReact.CSSProperties該節(jié)點(diǎn)解析后的主題樣式無主題時(shí)為空對象nodeNodeProseMirror 節(jié)點(diǎn)實(shí)例extensionEmailNode擴(kuò)展實(shí)例可用于訪問options導(dǎo)出的樣式解析優(yōu)先級高者勝出來自 compose-react-email.mdx節(jié)點(diǎn)屬性上的內(nèi)聯(lián)樣式當(dāng)前主題通過getNodeStyles()提供的主題樣式各擴(kuò)展renderToReactEmail()中寫死的默認(rèn)樣式。因此在渲染器中通常是{...style}展開主題樣式再疊加固定樣式。一個(gè)關(guān)鍵限制composeReactEmail遍歷文檔時(shí)未注冊、或不是EmailNode的節(jié)點(diǎn)類型會(huì)渲染為null。所以自定義節(jié)點(diǎn)必須用EmailNode.create或EmailNode.from創(chuàng)建并注冊否則導(dǎo)出時(shí)會(huì)直接消失。創(chuàng)建自定義 Callout 節(jié)點(diǎn)下面是文檔給出的完整示例一個(gè)渲染為高亮塊的 Callout 節(jié)點(diǎn)示例代碼來自 custom-extensions.mdximport { EmailNode } from react-email/editor/core; import { mergeAttributes } from tiptap/core; const Callout EmailNode.create({ name: callout, group: block, content: inline*, parseHTML() { return [{ tag: div[data-callout] }]; }, renderHTML({ HTMLAttributes }) { return [ div, mergeAttributes(HTMLAttributes, { data-callout: , style: padding: 12px 16px; background: #f4f4f5; border-left: 3px solid #1c1c1c; border-radius: 4px; margin: 8px 0;, }), 0, ]; }, renderToReactEmail({ children, style }) { return ( div style{{ ...style, padding: 12px 16px, backgroundColor: #f4f4f5, borderLeft: 3px solid #1c1c1c, borderRadius: 4px, margin: 8px 0, }} {children} /div ); }, });三個(gè)方法要分別保持一致parseHTML用div[data-callout]作為導(dǎo)入識別特征renderHTML輸出相同的data-callout屬性和編輯器內(nèi)樣式renderToReactEmail則把同樣的視覺效果用 React 樣式對象寫出供render()生成郵件 HTML。注冊擴(kuò)展并插入自定義節(jié)點(diǎn)把自定義擴(kuò)展加入 extensions 數(shù)組與StarterKit并列const extensions [StarterKit, Callout];程序化插入使用編輯器的insertContent命令。文檔示例是一個(gè)工具欄按鈕import { useCurrentEditor } from tiptap/react; function Toolbar() { const { editor } useCurrentEditor(); if (!editor) return null; return ( button onClick{() editor .chain() .focus() .insertContent({ type: callout, content: [{ type: text, text: New callout }], }) .run() } Insert Callout /button ); }完整編輯器組合把節(jié)點(diǎn)、工具欄和 BubbleMenu 組合進(jìn)EditorProvider同樣來自 custom-extensions.mdximport { EmailNode } from react-email/editor/core; import { StarterKit } from react-email/editor/extensions; import { BubbleMenu } from react-email/editor/ui; import { mergeAttributes } from tiptap/core; import { EditorProvider, useCurrentEditor } from tiptap/react; import { Info } from lucide-react; const Callout EmailNode.create({ name: callout, group: block, content: inline*, parseHTML() { return [{ tag: div[data-callout] }]; }, renderHTML({ HTMLAttributes }) { return [ div, mergeAttributes(HTMLAttributes, { data-callout: , style: padding: 12px 16px; background: #f4f4f5; border-left: 3px solid #1c1c1c; border-radius: 4px; margin: 8px 0;, }), 0, ]; }, renderToReactEmail({ children, style }) { return ( div style{{ ...style, padding: 12px 16px, backgroundColor: #f4f4f5, borderLeft: 3px solid #1c1c1c, borderRadius: 4px, margin: 8px 0, }} {children} /div ); }, }); const extensions [StarterKit, Callout]; const content { type: doc, content: [ { type: paragraph, content: [ { type: text, text: This editor includes a custom Callout node. Use the toolbar to insert one., }, ], }, { type: callout, content: [ { type: text, text: This is a callout block — a custom extension! }, ], }, ], }; function Toolbar() { const { editor } useCurrentEditor(); if (!editor) return null; return ( button onClick{() editor .chain() .focus() .insertContent({ type: callout, content: [{ type: text, text: New callout }], }) .run() } Info size{16} / Insert Callout /button ); } export function MyEditor() { return ( EditorProvider extensions{extensions} content{content} slotBefore{Toolbar /} BubbleMenu / /EditorProvider ); }注意content用的是 TipTap JSON 格式編輯器同時(shí)支持 HTML 字符串與 TipTap JSON 兩種初始內(nèi)容格式見 getting-started.mdx 的 Content format。使用低層級 UI 組件時(shí)記得導(dǎo)入主題 CSS例如import react-email/editor/themes/default.css;否則編輯器無樣式。驗(yàn)證導(dǎo)出結(jié)果自定義節(jié)點(diǎn)是否真正參與郵件序列化以導(dǎo)出輸出為準(zhǔn)。composeReactEmail的簽名與返回值來自 compose-react-email.mdximport { composeReactEmail } from react-email/editor/core; async function composeReactEmail(params: { editor: Editor; preview: string | null; }): Promise{ html: string; text: string };它讀取editor.getJSON()遍歷每個(gè)節(jié)點(diǎn)和 mark調(diào)用各擴(kuò)展的renderToReactEmail()應(yīng)用主題樣式包上郵件模板BaseTemplate最后用 react-email 的render()同時(shí)產(chǎn)出html與純文本text。文檔給出的帶導(dǎo)出面板的驗(yàn)證方式示例結(jié)果見 email-export.mdximport { composeReactEmail } from react-email/editor/core; import { useCurrentEditor } from tiptap/react; import { useState } from react; function ExportPanel() { const { editor } useCurrentEditor(); const [html, setHtml] useState(); const [exporting, setExporting] useState(false); const handleExport async () { if (!editor) return; setExporting(true); const result await composeReactEmail({ editor, preview: null }); setHtml(result.html); setExporting(false); }; return ( div button onClick{handleExport} disabled{exporting} {exporting ? Exporting... : Export HTML} /button {html ( textarea readOnly value{html} rows{16} style{{ width: 100%, fontFamily: monospace }} / )} /div ); }把ExportPanel作為EditorProvider的子組件與Toolbar一樣需要在 provider 內(nèi)使用useCurrentEditor。導(dǎo)出后的檢查點(diǎn)html中應(yīng)出現(xiàn) Callout 節(jié)點(diǎn)renderToReactEmail寫出的結(jié)構(gòu)與內(nèi)聯(lián)樣式如padding: 12px 16px、border-left: 3px solid #1c1c1c并且內(nèi)容被BaseTemplate包裹默認(rèn)模板包含 viewport meta 與Preview占位若文檔中存在未注冊的節(jié)點(diǎn)類型它們在html中不會(huì)有任何輸出渲染為null這是排查導(dǎo)出后內(nèi)容缺失的依據(jù)如果 extensions 數(shù)組里配置了EmailTheming主題樣式會(huì)自動(dòng)注入到每個(gè)節(jié)點(diǎn)沒有SerializerPlugin時(shí)style默認(rèn)為空對象、使用內(nèi)置DefaultBaseTemplate。復(fù)用已有 TipTap 擴(kuò)展可選分支不想從零寫節(jié)點(diǎn)時(shí)EmailNode.from()可以把現(xiàn)有 TipTap 節(jié)點(diǎn)包一層郵件序列化能力第二個(gè)參數(shù)就是renderToReactEmail渲染器示例同樣來自 custom-extensions.mdximport { EmailNode } from react-email/editor/core; import { Node } from tiptap/core; const MyTipTapNode Node.create({ /* ... */ }); const MyEmailNode EmailNode.from(MyTipTapNode, ({ children, style }) { return div style{style}{children}/div; });EmailMark.from是 mark行內(nèi)樣式如高亮的等價(jià)方法。EmailNode與EmailMark同時(shí)支持 TipTap 標(biāo)準(zhǔn)的.configure()和.extend()而且.extend時(shí)也可以直接覆蓋renderToReactEmail例如文檔示例中給Paragraph加鍵盤快捷鍵的同時(shí)重寫其導(dǎo)出渲染const CustomParagraph Paragraph.extend({ renderToReactEmail({ children, style }) { return p style{{ ...style, lineHeight: 1.8 }}{children}/p; }, });邊界與注意事項(xiàng)自定義節(jié)點(diǎn)必須走EditorProvider 顯式 extensions 數(shù)組這條路renderToReactEmail只在composeReactEmail及EmailEditorref 的getEmailHTML/getEmailText/getEmail三者底層都是它導(dǎo)出時(shí)被調(diào)用。styleprop 在無主題時(shí)是空對象主題樣式、內(nèi)聯(lián)樣式與擴(kuò)展默認(rèn)樣式的合并優(yōu)先級以第 3 節(jié)列表為準(zhǔn)渲染器里先展開...style再疊加固定樣式是文檔給出的合并方式。depth只在列表節(jié)點(diǎn)bulletList、orderedList內(nèi)遞增用于區(qū)分嵌套段落listParagraph與頂層段落paragraph的主題鍵如果你的自定義節(jié)點(diǎn)只在列表外使用這一點(diǎn)不影響其渲染。文檔中的 EmailNode 參考 與 composeReactEmail 參考 給出了create/from/configure/extend的完整 API 細(xì)節(jié)可作為擴(kuò)展開發(fā)時(shí)的查閱入口。【免費(fèi)下載鏈接】react-email Build and send emails using React項(xiàng)目地址: https://gitcode.com/GitHub_Trending/re/react-email創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考