ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

如何用 EmailNode 的 renderToReactEmail 为 React Email Editor 创建自定义邮件节点

如何用 EmailNode 的 renderToReactEmail 为 React Email Editor 创建自定义邮件节点 如何用 EmailNode 的 renderToReactEmail 为 React Email Editor 创建自定义邮件节点【免费下载链接】react-email Build and send emails using React项目地址: https://gitcode.com/GitHub_Trending/re/react-email这篇文章解决的问题是在 react-email 的react-email/editor基于 TipTap 的邮件编辑器中内置的StarterKit只覆盖了一组邮件场景需要的节点当你需要一种内置节点之外的结构——比如带高亮底色的 Callout 提示块——并且希望它在编辑器里能输入、能粘贴识别、还能在导出时序列化成邮件 HTML就需要用EmailNode创建一个自定义扩展并实现其中唯一的新增必选方法renderToReactEmail()。完成后的结果是编辑器里可以插入自定义节点调用composeReactEmail导出时该节点会走你的renderToReactEmail渲染逻辑出现在最终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文档同时给出 yarn / pnpm / bun 的等价命令yarn add/pnpm add/bun add react-email/editor。本文后续都基于低层级路径EditorProvider 扩展数组因为自定义节点必须显式加入 extensions 数组如果你使用独立的EmailEditor组件文档建议改走低层级路径来获得对扩展列表的完全控制见 getting-started.mdx 的 Lower-level setup。EmailNode 与 renderToReactEmail 的工作机制EmailNode扩展了 TipTap 的Node类额外要求一个renderToReactEmail()方法它决定节点在通过composeReactEmail导出为邮件 HTML 时如何被序列化见 email-node.mdx。创建扩展的配置对象接受所有标准 TipTap Node 选项加上renderToReactEmail。文档中三个方法的分工parseHTML()识别粘贴或导入的 HTML让已有内容能被解析回该节点renderHTML()控制节点在编辑器内的外观renderToReactEmail()控制导出时该节点渲染成什么 React Email 结构。renderToReactEmail收到的 props来自 email-node.mdx 的 props 表PropType说明childrenReact.ReactNode该节点序列化后的子内容styleReact.CSSProperties该节点解析后的主题样式无主题时为空对象nodeNodeProseMirror 节点实例extensionEmailNode扩展实例可用于访问options导出的样式解析优先级高者胜出来自 compose-react-email.mdx节点属性上的内联样式当前主题通过getNodeStyles()提供的主题样式各扩展renderToReactEmail()中写死的默认样式。因此在渲染器中通常是{...style}展开主题样式再叠加固定样式。一个关键限制composeReactEmail遍历文档时未注册、或不是EmailNode的节点类型会渲染为null。所以自定义节点必须用EmailNode.create或EmailNode.from创建并注册否则导出时会直接消失。创建自定义 Callout 节点下面是文档给出的完整示例一个渲染为高亮块的 Callout 节点示例代码来自 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 ); }, });三个方法要分别保持一致parseHTML用div[data-callout]作为导入识别特征renderHTML输出相同的data-callout属性和编辑器内样式renderToReactEmail则把同样的视觉效果用 React 样式对象写出供render()生成邮件 HTML。注册扩展并插入自定义节点把自定义扩展加入 extensions 数组与StarterKit并列const extensions [StarterKit, Callout];程序化插入使用编辑器的insertContent命令。文档示例是一个工具栏按钮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 ); }完整编辑器组合把节点、工具栏和 BubbleMenu 组合进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 格式编辑器同时支持 HTML 字符串与 TipTap JSON 两种初始内容格式见 getting-started.mdx 的 Content format。使用低层级 UI 组件时记得导入主题 CSS例如import react-email/editor/themes/default.css;否则编辑器无样式。验证导出结果自定义节点是否真正参与邮件序列化以导出输出为准。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()遍历每个节点和 mark调用各扩展的renderToReactEmail()应用主题样式包上邮件模板BaseTemplate最后用 react-email 的render()同时产出html与纯文本text。文档给出的带导出面板的验证方式示例结果见 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 内使用useCurrentEditor。导出后的检查点html中应出现 Callout 节点renderToReactEmail写出的结构与内联样式如padding: 12px 16px、border-left: 3px solid #1c1c1c并且内容被BaseTemplate包裹默认模板包含 viewport meta 与Preview占位若文档中存在未注册的节点类型它们在html中不会有任何输出渲染为null这是排查导出后内容缺失的依据如果 extensions 数组里配置了EmailTheming主题样式会自动注入到每个节点没有SerializerPlugin时style默认为空对象、使用内置DefaultBaseTemplate。复用已有 TipTap 扩展可选分支不想从零写节点时EmailNode.from()可以把现有 TipTap 节点包一层邮件序列化能力第二个参数就是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行内样式如高亮的等价方法。EmailNode与EmailMark同时支持 TipTap 标准的.configure()和.extend()而且.extend时也可以直接覆盖renderToReactEmail例如文档示例中给Paragraph加键盘快捷键的同时重写其导出渲染const CustomParagraph Paragraph.extend({ renderToReactEmail({ children, style }) { return p style{{ ...style, lineHeight: 1.8 }}{children}/p; }, });边界与注意事项自定义节点必须走EditorProvider 显式 extensions 数组这条路renderToReactEmail只在composeReactEmail及EmailEditorref 的getEmailHTML/getEmailText/getEmail三者底层都是它导出时被调用。styleprop 在无主题时是空对象主题样式、内联样式与扩展默认样式的合并优先级以第 3 节列表为准渲染器里先展开...style再叠加固定样式是文档给出的合并方式。depth只在列表节点bulletList、orderedList内递增用于区分嵌套段落listParagraph与顶层段落paragraph的主题键如果你的自定义节点只在列表外使用这一点不影响其渲染。文档中的 EmailNode 参考 与 composeReactEmail 参考 给出了create/from/configure/extend的完整 API 细节可作为扩展开发时的查阅入口。【免费下载链接】react-email Build and send emails using React项目地址: https://gitcode.com/GitHub_Trending/re/react-email创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表