11import {
2+ BlockMapping ,
23 DefaultBlockSchema ,
34 mapTableCell ,
45 pageBreakSchema ,
56 StyledText ,
67} from "@blocknote/core" ;
7- import { BlockMapping } from "@blocknote/core/src/exporter/mapping.js" ;
88import {
99 CodeBlock ,
1010 dracula ,
@@ -15,27 +15,168 @@ import {
1515 Text ,
1616} from "@react-email/components" ;
1717
18- export const reactEmailBlockMappingForDefaultSchema : BlockMapping <
18+ // Define TextProps type based on React Email Text component
19+ type TextProps = React . ComponentPropsWithoutRef < typeof Text > ;
20+
21+ // Define the styles interface for configurable Text components
22+ export interface ReactEmailTextStyles {
23+ paragraph ?: Partial < TextProps > ;
24+ bulletListItem ?: Partial < TextProps > ;
25+ toggleListItem ?: Partial < TextProps > ;
26+ numberedListItem ?: Partial < TextProps > ;
27+ checkListItem ?: Partial < TextProps > ;
28+ quote ?: Partial < TextProps > ;
29+ tableError ?: Partial < TextProps > ;
30+ tableCell ?: Partial < TextProps > ;
31+ caption ?: Partial < TextProps > ;
32+ heading1 ?: Partial < TextProps > ;
33+ heading2 ?: Partial < TextProps > ;
34+ heading3 ?: Partial < TextProps > ;
35+ heading4 ?: Partial < TextProps > ;
36+ heading5 ?: Partial < TextProps > ;
37+ heading6 ?: Partial < TextProps > ;
38+ codeBlock ?: Partial < React . ComponentProps < typeof CodeBlock > > ;
39+ }
40+
41+ const defaultTextStyle : TextProps [ "style" ] = {
42+ fontSize : 16 ,
43+ lineHeight : 1.5 ,
44+ margin : 3 ,
45+ } ;
46+
47+ // Default styles for Text components
48+ export const defaultReactEmailTextStyles = {
49+ paragraph : {
50+ style : defaultTextStyle ,
51+ } ,
52+ bulletListItem : {
53+ style : defaultTextStyle ,
54+ } ,
55+ toggleListItem : {
56+ style : defaultTextStyle ,
57+ } ,
58+ numberedListItem : {
59+ style : defaultTextStyle ,
60+ } ,
61+ checkListItem : {
62+ style : defaultTextStyle ,
63+ } ,
64+ quote : {
65+ style : defaultTextStyle ,
66+ } ,
67+ tableError : {
68+ style : defaultTextStyle ,
69+ } ,
70+ tableCell : {
71+ style : defaultTextStyle ,
72+ } ,
73+ caption : {
74+ style : defaultTextStyle ,
75+ } ,
76+ heading1 : {
77+ style : {
78+ fontSize : 48 ,
79+ margin : 3 ,
80+ } ,
81+ } ,
82+ heading2 : {
83+ style : {
84+ fontSize : 36 ,
85+ margin : 3 ,
86+ } ,
87+ } ,
88+ heading3 : {
89+ style : {
90+ fontSize : 24 ,
91+ margin : 3 ,
92+ } ,
93+ } ,
94+ heading4 : {
95+ style : {
96+ fontSize : 20 ,
97+ margin : 3 ,
98+ } ,
99+ } ,
100+ heading5 : {
101+ style : {
102+ fontSize : 18 ,
103+ margin : 3 ,
104+ } ,
105+ } ,
106+ heading6 : {
107+ style : {
108+ fontSize : 16 ,
109+ margin : 3 ,
110+ } ,
111+ } ,
112+ codeBlock : {
113+ style : defaultTextStyle ,
114+ } ,
115+ } satisfies ReactEmailTextStyles ;
116+
117+ export const createReactEmailBlockMappingForDefaultSchema = (
118+ textStyles : ReactEmailTextStyles = defaultReactEmailTextStyles ,
119+ ) : BlockMapping <
19120 DefaultBlockSchema & typeof pageBreakSchema . blockSchema ,
20121 any ,
21122 any ,
22123 React . ReactElement < any > ,
23124 React . ReactElement < typeof Link > | React . ReactElement < HTMLSpanElement >
24- > = {
125+ > => ( {
25126 paragraph : ( block , t ) => {
26- return < Text > { t . transformInlineContent ( block . content ) } </ Text > ;
127+ return (
128+ < Text
129+ { ...textStyles . paragraph }
130+ style = { {
131+ ...defaultReactEmailTextStyles . paragraph . style ,
132+ ...textStyles . paragraph ?. style ,
133+ } }
134+ >
135+ { t . transformInlineContent ( block . content ) }
136+ </ Text >
137+ ) ;
27138 } ,
28139 bulletListItem : ( block , t ) => {
29140 // Return only the <li> for grouping in the exporter
30- return < Text > { t . transformInlineContent ( block . content ) } </ Text > ;
141+ return (
142+ < Text
143+ { ...textStyles . bulletListItem }
144+ style = { {
145+ ...defaultReactEmailTextStyles . bulletListItem . style ,
146+ ...textStyles . bulletListItem ?. style ,
147+ } }
148+ >
149+ { t . transformInlineContent ( block . content ) }
150+ </ Text >
151+ ) ;
31152 } ,
32153 toggleListItem : ( block , t ) => {
33154 // Return only the <li> for grouping in the exporter
34- return < Text > { t . transformInlineContent ( block . content ) } </ Text > ;
155+ return (
156+ < Text
157+ { ...textStyles . toggleListItem }
158+ style = { {
159+ ...defaultReactEmailTextStyles . toggleListItem . style ,
160+ ...textStyles . toggleListItem ?. style ,
161+ } }
162+ >
163+ { t . transformInlineContent ( block . content ) }
164+ </ Text >
165+ ) ;
35166 } ,
36167 numberedListItem : ( block , t , _nestingLevel ) => {
37168 // Return only the <li> for grouping in the exporter
38- return < Text > { t . transformInlineContent ( block . content ) } </ Text > ;
169+ return (
170+ < Text
171+ { ...textStyles . numberedListItem }
172+ style = { {
173+ ...defaultReactEmailTextStyles . numberedListItem . style ,
174+ ...textStyles . numberedListItem ?. style ,
175+ } }
176+ >
177+ { t . transformInlineContent ( block . content ) }
178+ </ Text >
179+ ) ;
39180 } ,
40181 checkListItem : ( block , t ) => {
41182 // Render a checkbox using inline SVG for better appearance in email
@@ -85,15 +226,28 @@ export const reactEmailBlockMappingForDefaultSchema: BlockMapping<
85226 </ svg >
86227 ) ;
87228 return (
88- < Text >
229+ < Text
230+ { ...textStyles . checkListItem }
231+ style = { {
232+ ...defaultReactEmailTextStyles . checkListItem . style ,
233+ ...textStyles . checkListItem ?. style ,
234+ } }
235+ >
89236 { checkboxSvg }
90237 < span > { t . transformInlineContent ( block . content ) } </ span >
91238 </ Text >
92239 ) ;
93240 } ,
94241 heading : ( block , t ) => {
95242 return (
96- < Heading as = { `h${ block . props . level } ` } >
243+ < Heading
244+ as = { `h${ block . props . level } ` }
245+ { ...textStyles [ `heading${ block . props . level } ` ] }
246+ style = { {
247+ ...defaultReactEmailTextStyles [ `heading${ block . props . level } ` ] . style ,
248+ ...textStyles [ `heading${ block . props . level } ` ] ?. style ,
249+ } }
250+ >
97251 { t . transformInlineContent ( block . content ) }
98252 </ Heading >
99253 ) ;
@@ -108,6 +262,11 @@ export const reactEmailBlockMappingForDefaultSchema: BlockMapping<
108262 fontFamily = "'CommitMono', monospace"
109263 language = { block . props . language as PrismLanguage }
110264 theme = { dracula }
265+ { ...textStyles . codeBlock }
266+ style = { {
267+ ...defaultReactEmailTextStyles . codeBlock . style ,
268+ ...textStyles . codeBlock ?. style ,
269+ } }
111270 />
112271 ) ;
113272 } ,
@@ -136,7 +295,11 @@ export const reactEmailBlockMappingForDefaultSchema: BlockMapping<
136295 defaultText = "Open audio file"
137296 icon = { icon }
138297 />
139- < Caption caption = { block . props . caption } width = { previewWidth } />
298+ < Caption
299+ caption = { block . props . caption }
300+ width = { previewWidth }
301+ textStyles = { textStyles }
302+ />
140303 </ div >
141304 ) ;
142305 } ,
@@ -165,7 +328,11 @@ export const reactEmailBlockMappingForDefaultSchema: BlockMapping<
165328 defaultText = "Open video file"
166329 icon = { icon }
167330 />
168- < Caption caption = { block . props . caption } width = { previewWidth } />
331+ < Caption
332+ caption = { block . props . caption }
333+ width = { previewWidth }
334+ textStyles = { textStyles }
335+ />
169336 </ div >
170337 ) ;
171338 } ,
@@ -194,7 +361,11 @@ export const reactEmailBlockMappingForDefaultSchema: BlockMapping<
194361 defaultText = "Open file"
195362 icon = { icon }
196363 />
197- < Caption caption = { block . props . caption } width = { previewWidth } />
364+ < Caption
365+ caption = { block . props . caption }
366+ width = { previewWidth }
367+ textStyles = { textStyles }
368+ />
198369 </ div >
199370 ) ;
200371 } ,
@@ -211,7 +382,7 @@ export const reactEmailBlockMappingForDefaultSchema: BlockMapping<
211382 // Render table using standard HTML table elements for email compatibility
212383 const table = block . content ;
213384 if ( ! table || typeof table !== "object" || ! Array . isArray ( table . rows ) ) {
214- return < Text > Table data not available</ Text > ;
385+ return < Text { ... textStyles . tableError } > Table data not available</ Text > ;
215386 }
216387 const headerRowsCount = ( table . headerRows as number ) ?? 0 ;
217388 const headerColsCount = ( table . headerCols as number ) ?? 0 ;
@@ -258,6 +429,8 @@ export const reactEmailBlockMappingForDefaultSchema: BlockMapping<
258429 normalizedCell . props . textColor !== "default"
259430 ? normalizedCell . props . textColor
260431 : "inherit" ,
432+ ...defaultReactEmailTextStyles . tableCell . style ,
433+ ...textStyles . tableCell ?. style ,
261434 } }
262435 { ...( ( normalizedCell . props . colspan || 1 ) > 1 && {
263436 colSpan : normalizedCell . props . colspan || 1 ,
@@ -280,6 +453,7 @@ export const reactEmailBlockMappingForDefaultSchema: BlockMapping<
280453 // Render block quote with a left border and subtle background for email compatibility
281454 return (
282455 < Text
456+ { ...textStyles . quote }
283457 style = { {
284458 borderLeft : "4px solid #bdbdbd" ,
285459 background : "#f9f9f9" ,
@@ -288,6 +462,8 @@ export const reactEmailBlockMappingForDefaultSchema: BlockMapping<
288462 fontStyle : "italic" ,
289463 color : "#555" ,
290464 display : "block" ,
465+ ...defaultReactEmailTextStyles . quote . style ,
466+ ...textStyles . quote ?. style ,
291467 } }
292468 >
293469 { t . transformInlineContent ( block . content ) }
@@ -306,7 +482,11 @@ export const reactEmailBlockMappingForDefaultSchema: BlockMapping<
306482 />
307483 ) ;
308484 } ,
309- } ;
485+ } ) ;
486+
487+ // Export the original mapping for backward compatibility
488+ export const reactEmailBlockMappingForDefaultSchema =
489+ createReactEmailBlockMappingForDefaultSchema ( ) ;
310490
311491// Helper for file-like blocks (audio, video, file)
312492function FileLink ( {
@@ -338,12 +518,30 @@ function FileLink({
338518 ) ;
339519}
340520
341- function Caption ( { caption, width } : { caption ?: string ; width ?: number } ) {
521+ function Caption ( {
522+ caption,
523+ width,
524+ textStyles,
525+ } : {
526+ caption ?: string ;
527+ width ?: number ;
528+ textStyles : ReactEmailTextStyles ;
529+ } ) {
342530 if ( ! caption ) {
343531 return null ;
344532 }
345533 return (
346- < Text style = { { width, fontSize : 13 , color : "#888" , margin : "4px 0 0 0" } } >
534+ < Text
535+ { ...textStyles . caption }
536+ style = { {
537+ width,
538+ fontSize : 13 ,
539+ color : "#888" ,
540+ margin : "4px 0 0 0" ,
541+ ...defaultReactEmailTextStyles . caption . style ,
542+ ...textStyles . caption ?. style ,
543+ } }
544+ >
347545 { caption }
348546 </ Text >
349547 ) ;
0 commit comments