email-builder-utils 1.1.44 → 1.1.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -23,7 +23,7 @@ interface IBlockData {
|
|
|
23
23
|
childrenIds?: Array<string>;
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
export declare const tableCommonStyle = "border-collapse:collapse; table-layout:fixed
|
|
26
|
+
export declare const tableCommonStyle = "border-collapse:collapse; table-layout:fixed";
|
|
27
27
|
export declare function convertToHtml(blockData: IBlockData, rootData: any, cellWidthInPx: number): Promise<string>;
|
|
28
28
|
export declare function convertVideoBlock(blockData: any, cellWidthInPx: number): Promise<string>;
|
|
29
29
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsonToHTML.d.ts","sourceRoot":"","sources":["../../src/utils/jsonToHTML.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAIrC,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,KAAK,EAAE,cAAc,CAAC;QACtB,KAAK,EAAE,GAAG,CAAC;QACX,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;KAC7B,CAAC;CACH;AAYD,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"jsonToHTML.d.ts","sourceRoot":"","sources":["../../src/utils/jsonToHTML.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAIrC,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,KAAK,EAAE,cAAc,CAAC;QACtB,KAAK,EAAE,GAAG,CAAC;QACX,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;KAC7B,CAAC;CACH;AAYD,eAAO,MAAM,gBAAgB,iDAAiD,CAAC;AA2I/E,wBAAsB,aAAa,CACjC,SAAS,EAAE,UAAU,EACrB,QAAQ,EAAE,GAAG,EACb,aAAa,EAAE,MAAM,mBAwBtB;AA0lCD,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBA+K5E"}
|
package/dist/utils/jsonToHTML.js
CHANGED
|
@@ -14,7 +14,7 @@ const addPxToAttributes = [
|
|
|
14
14
|
];
|
|
15
15
|
const addPxOrPerToAttributes = ["width", "height"];
|
|
16
16
|
const allPxAttributes = [...addPxToAttributes, ...addPxOrPerToAttributes];
|
|
17
|
-
exports.tableCommonStyle = "border-collapse:collapse; table-layout:fixed
|
|
17
|
+
exports.tableCommonStyle = "border-collapse:collapse; table-layout:fixed";
|
|
18
18
|
function encodeBlockProps(props) {
|
|
19
19
|
return JSON.stringify(props)
|
|
20
20
|
.replace(/&/g, '&')
|
|
@@ -28,6 +28,38 @@ async function loadImageNaturalDimensions(imageUrl) {
|
|
|
28
28
|
img.src = imageUrl;
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
|
+
const GENERIC_FONT_FAMILIES = new Set([
|
|
32
|
+
'serif', 'sans-serif', 'monospace', 'cursive', 'fantasy',
|
|
33
|
+
'system-ui', 'ui-serif', 'ui-sans-serif', 'ui-monospace',
|
|
34
|
+
'ui-rounded', 'emoji', 'math', 'fangsong',
|
|
35
|
+
]);
|
|
36
|
+
/**
|
|
37
|
+
* Normalises a CSS font-family string so every multi-word family name is wrapped
|
|
38
|
+
* in single quotes — safe inside double-quoted HTML style attributes.
|
|
39
|
+
* Handles already-quoted names (single or double), generic keywords, and any
|
|
40
|
+
* number of comma-separated families.
|
|
41
|
+
*/
|
|
42
|
+
function sanitizeFontFamily(fontFamily) {
|
|
43
|
+
if (!fontFamily)
|
|
44
|
+
return fontFamily;
|
|
45
|
+
return fontFamily
|
|
46
|
+
.split(',')
|
|
47
|
+
.map(font => {
|
|
48
|
+
const trimmed = font.trim();
|
|
49
|
+
// Strip any surrounding quotes (single or double) from either end
|
|
50
|
+
const unquoted = trimmed.replace(/^["']|["']$/g, '').trim();
|
|
51
|
+
if (!unquoted)
|
|
52
|
+
return '';
|
|
53
|
+
// Generic families and single-token names need no quotes
|
|
54
|
+
if (GENERIC_FONT_FAMILIES.has(unquoted.toLowerCase()) || !/\s/.test(unquoted)) {
|
|
55
|
+
return unquoted;
|
|
56
|
+
}
|
|
57
|
+
// Multi-word font name: wrap in single quotes, escaping any embedded single quotes
|
|
58
|
+
return `'${unquoted.replace(/'/g, "\\'")}'`;
|
|
59
|
+
})
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join(', ');
|
|
62
|
+
}
|
|
31
63
|
function buildStyles(style, { pxChanges, perChanges }) {
|
|
32
64
|
if (!style)
|
|
33
65
|
style = {};
|
|
@@ -64,7 +96,7 @@ function buildStyles(style, { pxChanges, perChanges }) {
|
|
|
64
96
|
value = `${safePad.top}px ${safePad.right}px ${safePad.bottom}px ${safePad.left}px`;
|
|
65
97
|
}
|
|
66
98
|
if (key === "fontFamily" && typeof value === "string") {
|
|
67
|
-
value = (0, fontFallback_1.withFontFallback)(value)
|
|
99
|
+
value = sanitizeFontFamily((0, fontFallback_1.withFontFallback)(value));
|
|
68
100
|
}
|
|
69
101
|
// Wrap backgroundImage values in url() if not already wrapped — skip gradients
|
|
70
102
|
if (key === "backgroundImage" && typeof value === "string"
|
|
@@ -170,12 +202,17 @@ ${content}
|
|
|
170
202
|
function convertDividerBlockToHtml(blockData) {
|
|
171
203
|
const { style, props } = blockData.data;
|
|
172
204
|
const { hideOnMobile, hideOnDesktop } = props;
|
|
173
|
-
const { thickness, dividerColor, width, ...rest } = style;
|
|
205
|
+
const { thickness, dividerColor, width, alignment, ...rest } = style;
|
|
174
206
|
const convertedStyle = buildStyles(rest, {
|
|
175
207
|
perChanges: [],
|
|
176
208
|
pxChanges: allPxAttributes,
|
|
177
209
|
});
|
|
178
|
-
const dividerWidth = width ||
|
|
210
|
+
const dividerWidth = width || 100;
|
|
211
|
+
const alignAttr = alignment === 'center' ? 'center' : alignment === 'right' ? 'right' : 'left';
|
|
212
|
+
// Append text-align so the import parser can recover alignment via inheritance
|
|
213
|
+
const contentStyle = convertedStyle
|
|
214
|
+
? `${convertedStyle}; text-align:${alignAttr};`
|
|
215
|
+
: `text-align:${alignAttr};`;
|
|
179
216
|
// Build class name based on visibility
|
|
180
217
|
const visibilityClass = [
|
|
181
218
|
hideOnMobile ? "hide-mobile" : "",
|
|
@@ -185,6 +222,7 @@ function convertDividerBlockToHtml(blockData) {
|
|
|
185
222
|
.join(" ");
|
|
186
223
|
const dividerContent = `
|
|
187
224
|
<table
|
|
225
|
+
align="${alignAttr}"
|
|
188
226
|
width="${dividerWidth}%"
|
|
189
227
|
cellpadding="0"
|
|
190
228
|
cellspacing="0"
|
|
@@ -192,14 +230,14 @@ function convertDividerBlockToHtml(blockData) {
|
|
|
192
230
|
<tr>
|
|
193
231
|
<td
|
|
194
232
|
height="${thickness}"
|
|
195
|
-
style="font-size:1px; line-height:1px; background:${dividerColor}; width:${dividerWidth}
|
|
233
|
+
style="font-size:1px; line-height:1px; background:${dividerColor}; width:${dividerWidth}%;"
|
|
196
234
|
>
|
|
197
235
|
|
|
198
236
|
</td>
|
|
199
237
|
</tr>
|
|
200
238
|
</table>
|
|
201
239
|
`;
|
|
202
|
-
return appendOutlookSupport(dividerContent,
|
|
240
|
+
return appendOutlookSupport(dividerContent, contentStyle, visibilityClass);
|
|
203
241
|
}
|
|
204
242
|
function convertSpacerBlockToHtml(blockData) {
|
|
205
243
|
const { style, props } = blockData.data;
|
|
@@ -242,13 +280,28 @@ function convertTextBlock(blockData, cellWidthInPx) {
|
|
|
242
280
|
.replace(/<\/p>/gi, "</div>");
|
|
243
281
|
const navigateToUrl = props.navigateToUrl || "";
|
|
244
282
|
const fontSizeStyle = fontSize != null ? `font-size:${fontSize}px;` : "";
|
|
283
|
+
// Email clients apply `a { color: blue }` which overrides inherited color.
|
|
284
|
+
// Inject the block color directly onto <a> tags that don't already have one.
|
|
285
|
+
const blockTextColor = rest.color;
|
|
286
|
+
const processedText = blockTextColor
|
|
287
|
+
? sanitizedText.replace(/<a(\s[^>]*)?>/gi, (match, attrs = '') => {
|
|
288
|
+
if (/style\s*=\s*["'][^"']*\bcolor\s*:/i.test(attrs))
|
|
289
|
+
return match;
|
|
290
|
+
if (/\bstyle\s*=/i.test(attrs)) {
|
|
291
|
+
return `<a${attrs.replace(/(\bstyle\s*=\s*["'])/, `$1color:${blockTextColor};`)}>`;
|
|
292
|
+
}
|
|
293
|
+
return `<a${attrs} style="color:${blockTextColor};">`;
|
|
294
|
+
})
|
|
295
|
+
: sanitizedText;
|
|
296
|
+
const colorStyle = blockTextColor ? `color:${blockTextColor};` : '';
|
|
245
297
|
// Use display:block + width:100% so text fills the column naturally.
|
|
246
298
|
// display:inline-block with a pixel width (e.g. 400px) breaks narrow grid cells.
|
|
247
|
-
const convertedTextBox = `<div style="display:block; width:100%; box-sizing:border-box; ${fontSizeStyle}${convertedTextStyle}">${
|
|
299
|
+
const convertedTextBox = `<div style="display:block; width:100%; box-sizing:border-box; ${colorStyle}${fontSizeStyle}${convertedTextStyle}">${processedText.replaceAll(/\n/g, "<br>")}</div>`;
|
|
248
300
|
const safeCellWidth = cellWidthInPx ? Math.min(cellWidthInPx, 600) : undefined;
|
|
249
301
|
const textContent = appendOutlookSupport(convertedTextBox, styles, visibilityClass, safeCellWidth);
|
|
302
|
+
const linkColorStyle = blockTextColor ? `color:${blockTextColor};` : 'color:inherit;';
|
|
250
303
|
return navigateToUrl
|
|
251
|
-
? `<a href="${navigateToUrl}" rel="noreferrer noopener" style="
|
|
304
|
+
? `<a href="${navigateToUrl}" rel="noreferrer noopener" style="${linkColorStyle}text-decoration:none;cursor:pointer;">${textContent}</a>`
|
|
252
305
|
: textContent;
|
|
253
306
|
}
|
|
254
307
|
async function appendOutlookForImage(content, outerContainerWidth, innerContainerWidth, imageUrl, style = {}, finalWidth, finalHeight) {
|
|
@@ -435,7 +488,7 @@ function appendOutlookForButton(content, buttonStyle, navigateToUrl, text) {
|
|
|
435
488
|
const borderStyle = buttonStyle.borderStyle || "solid";
|
|
436
489
|
const bgColor = buttonStyle.buttonColor || "transparent";
|
|
437
490
|
const color = buttonStyle.color || "#ffffff";
|
|
438
|
-
const fontFamily = (0, fontFallback_1.withFontFallback)(buttonStyle.fontFamily)
|
|
491
|
+
const fontFamily = sanitizeFontFamily((0, fontFallback_1.withFontFallback)(buttonStyle.fontFamily));
|
|
439
492
|
const fontWeight = buttonStyle.fontWeight || 400;
|
|
440
493
|
const width = typeof buttonStyle.width === "number"
|
|
441
494
|
? `width="${buttonStyle.width}"`
|
|
@@ -502,7 +555,7 @@ function convertButtonBlock(blockData) {
|
|
|
502
555
|
const finalHeight = typeof height === "number" && height > 0
|
|
503
556
|
? Math.max(height, minHeight)
|
|
504
557
|
: null;
|
|
505
|
-
const safeFF = (0, fontFallback_1.withFontFallback)(fontFamily)
|
|
558
|
+
const safeFF = sanitizeFontFamily((0, fontFallback_1.withFontFallback)(fontFamily));
|
|
506
559
|
const safeColor = color || "#ffffff";
|
|
507
560
|
const bgColor = buttonColor || "transparent";
|
|
508
561
|
const bdColor = borderColor || "transparent";
|
|
@@ -684,10 +737,29 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
684
737
|
: customCssStr;
|
|
685
738
|
// Build inner table styles — when gradient/bg-image is on the outer wrapper, strip
|
|
686
739
|
// background props from the inner table so the outer <td> background shows through.
|
|
687
|
-
const
|
|
740
|
+
const innerRestStyleRaw = (rawBgImageUrl || isGradient)
|
|
688
741
|
? { ...restStyle, customCss: innerCustomCss, backgroundSize: undefined, backgroundPosition: undefined, backgroundRepeat: undefined }
|
|
689
742
|
: { ...restStyle, customCss: innerCustomCss };
|
|
690
|
-
|
|
743
|
+
// Extract border/radius props — applied via a div wrapper for non-MSO clients so that
|
|
744
|
+
// border-radius is honoured (Gmail/Outlook compose strip border-radius from <table>).
|
|
745
|
+
const { borderRadius, border, borderColor, borderWidth, borderStyle: borderStyleProp, ...innerRestStyle } = innerRestStyleRaw;
|
|
746
|
+
const divBorderParts = [];
|
|
747
|
+
if (borderRadius)
|
|
748
|
+
divBorderParts.push(`border-radius:${typeof borderRadius === 'number' ? borderRadius + 'px' : borderRadius};`, `overflow:hidden;`);
|
|
749
|
+
if (border) {
|
|
750
|
+
divBorderParts.push(`border:${border};`);
|
|
751
|
+
}
|
|
752
|
+
else if (borderWidth || borderColor || borderStyleProp) {
|
|
753
|
+
const bw = borderWidth ? (typeof borderWidth === 'number' ? borderWidth + 'px' : borderWidth) : '1px';
|
|
754
|
+
const bs = borderStyleProp || 'solid';
|
|
755
|
+
const bc = borderColor || '#000000';
|
|
756
|
+
divBorderParts.push(`border:${bw} ${bs} ${bc};`);
|
|
757
|
+
}
|
|
758
|
+
const divBorderStyle = divBorderParts.join(' ');
|
|
759
|
+
const tableBgForNonMso = divBorderStyle
|
|
760
|
+
? 'transparent'
|
|
761
|
+
: ((rawBgImageUrl || isGradient) ? undefined : backgroundColor);
|
|
762
|
+
const tableStyles = buildStyles({ backgroundColor: tableBgForNonMso, ...innerRestStyle }, {
|
|
691
763
|
perChanges: [],
|
|
692
764
|
pxChanges: allPxAttributes,
|
|
693
765
|
});
|
|
@@ -707,7 +779,15 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
707
779
|
const innerBgTransparent = (rawBgImageUrl || isGradient)
|
|
708
780
|
? 'background-color:transparent;'
|
|
709
781
|
: '';
|
|
710
|
-
const nonMsoBgAttr = !rawBgImageUrl && !isGradient && backgroundColor ? ` bgcolor="${backgroundColor}"` : '';
|
|
782
|
+
const nonMsoBgAttr = !rawBgImageUrl && !isGradient && backgroundColor && !divBorderStyle ? ` bgcolor="${backgroundColor}"` : '';
|
|
783
|
+
// When divBorderStyle is set the non-MSO <table> is transparent, so the Grid's
|
|
784
|
+
// backgroundColor must move onto the div wrapper — otherwise it vanishes in modern clients.
|
|
785
|
+
// Skip this for bg-image/gradient blocks; they apply their background via a separate wrapper.
|
|
786
|
+
const divWrapBg = divBorderStyle && backgroundColor && !rawBgImageUrl && !isGradient
|
|
787
|
+
? ` background-color:${backgroundColor};`
|
|
788
|
+
: '';
|
|
789
|
+
const divWrapOpen = divBorderStyle ? `<div style="${divBorderStyle}${divWrapBg}">` : '';
|
|
790
|
+
const divWrapClose = divBorderStyle ? `</div>` : '';
|
|
711
791
|
let html = `
|
|
712
792
|
<!--[if mso]>
|
|
713
793
|
<table border="0" cellpadding="0" cellspacing="0" width="${msoTableWidth}"${msoBgAttr}
|
|
@@ -773,7 +853,7 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
773
853
|
const childVisible = !childProps.hideOnDesktop;
|
|
774
854
|
const visibilityClass = (0, common_1.getVisibilityClass)(childProps);
|
|
775
855
|
if (childVisible) {
|
|
776
|
-
const { html: childHtml, styles } = await convertGridCellBlock(child, rootData, widthPercent, adjustedTableWidth);
|
|
856
|
+
const { html: childHtml, styles } = await convertGridCellBlock(child, rootData, widthPercent, adjustedTableWidth, Boolean(divBorderStyle));
|
|
777
857
|
// bgcolor on the cell <td> ensures background-color survives Outlook
|
|
778
858
|
// compose paste (Word/Web editors strip CSS but keep bgcolor attribute).
|
|
779
859
|
const cellBgColor = cellStyle.backgroundColor || '';
|
|
@@ -843,10 +923,11 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
843
923
|
style="border-collapse:collapse;width:${msoTableWidth}px;">
|
|
844
924
|
<tr>
|
|
845
925
|
<td width="${msoTableWidth}" bgcolor="${fallbackBgColor}" valign="top"
|
|
926
|
+
${!isGradient && rawBgImageUrl ? `background="${rawBgImageUrl}"` : ""}
|
|
846
927
|
style="
|
|
847
928
|
width:${msoTableWidth}px;
|
|
848
929
|
background-color:${fallbackBgColor};
|
|
849
|
-
${isGradient ? `background:${effectiveGradient};` : `background:url('${rawBgImageUrl}') center
|
|
930
|
+
${isGradient ? `background:${effectiveGradient};` : `background-image:url('${rawBgImageUrl}'); background-position:center center; background-size:cover; background-repeat:no-repeat;`}
|
|
850
931
|
">
|
|
851
932
|
|
|
852
933
|
<!--[if gte mso 9]>
|
|
@@ -868,12 +949,32 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
868
949
|
</tr>
|
|
869
950
|
</table>`;
|
|
870
951
|
}
|
|
952
|
+
// Wrap the entire grid (including any bg-image outer table) in a div when the block
|
|
953
|
+
// has border/radius. An unconditional <div> is used — not gated behind <!--[if !mso]>-->
|
|
954
|
+
// — so Gmail compose paste renders the border-radius reliably. Old Outlook ignores
|
|
955
|
+
// border-radius on <div> but still shows the rectangular border; new Outlook works fully.
|
|
956
|
+
if (divBorderStyle)
|
|
957
|
+
html = `${divWrapOpen}${html}${divWrapClose}`;
|
|
871
958
|
return html;
|
|
872
959
|
}
|
|
873
|
-
async function convertGridCellBlock(blockData, rootData, cellWidthPercent, parentCellWidthPx) {
|
|
960
|
+
async function convertGridCellBlock(blockData, rootData, cellWidthPercent, parentCellWidthPx, parentGridHasBorder = false) {
|
|
874
961
|
const { style = {}, childrenIds = [], props = {} } = blockData.data;
|
|
875
962
|
const visibilityClass = (0, common_1.getVisibilityClass)(props);
|
|
876
|
-
|
|
963
|
+
// Extract border + radius from style so they move to the div wrapper (not the <td>).
|
|
964
|
+
// Gmail strips border-radius from <td> but honours it on <div>. By putting border and
|
|
965
|
+
// radius on the same unconditional <div>, the rounded card border renders in all clients.
|
|
966
|
+
// The <td> keeps bgcolor (via attribute) for Old Outlook background fallback.
|
|
967
|
+
const { borderRadius: cellBorderRadius, borderWidth: cellBorderWidth, borderStyle: cellBorderStyleProp, borderColor: cellBorderColor, border: cellBorderShorthand, ...styleWithoutBorder } = style;
|
|
968
|
+
// backgroundColor must stay on the div wrapper (not the <td>) in two cases:
|
|
969
|
+
// 1. Cell has its own border-radius — the div's overflow:hidden clips the background.
|
|
970
|
+
// 2. Parent grid has a border div (divBorderStyle) — the grid's overflow:hidden clips it.
|
|
971
|
+
// In both cases, the rectangular <td> background bleeds through rounded corners if kept
|
|
972
|
+
// in CSS, creating visible corner squares. The bgcolor attribute stays for Outlook fallback.
|
|
973
|
+
const stripBgFromTd = Boolean(cellBorderRadius) || parentGridHasBorder;
|
|
974
|
+
const styleForTd = stripBgFromTd
|
|
975
|
+
? { ...styleWithoutBorder, backgroundColor: 'transparent' }
|
|
976
|
+
: styleWithoutBorder;
|
|
977
|
+
const styles = buildStyles(styleForTd, {
|
|
877
978
|
perChanges: [],
|
|
878
979
|
pxChanges: allPxAttributes,
|
|
879
980
|
});
|
|
@@ -884,7 +985,7 @@ async function convertGridCellBlock(blockData, rootData, cellWidthPercent, paren
|
|
|
884
985
|
// Subtract the cell's own padding so children receive the actual content-area width.
|
|
885
986
|
// Old Outlook honours explicit img/table width attributes — if a child is sized to the
|
|
886
987
|
// full column width (ignoring padding) it overflows and expands the column.
|
|
887
|
-
const cellPad =
|
|
988
|
+
const cellPad = styleWithoutBorder?.padding || {};
|
|
888
989
|
const cellPadLeft = Number.isFinite(cellPad.left) ? cellPad.left : 0;
|
|
889
990
|
const cellPadRight = Number.isFinite(cellPad.right) ? cellPad.right : 0;
|
|
890
991
|
const contentWidthPx = Math.max(cellWidthPx - cellPadLeft - cellPadRight, 20);
|
|
@@ -896,22 +997,35 @@ async function convertGridCellBlock(blockData, rootData, cellWidthPercent, paren
|
|
|
896
997
|
parts.push(await convertToHtml(child, rootData, safeCellWidthPx));
|
|
897
998
|
}
|
|
898
999
|
}
|
|
899
|
-
const borderRadius =
|
|
900
|
-
const bgColor =
|
|
901
|
-
//
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1000
|
+
const borderRadius = cellBorderRadius || 0;
|
|
1001
|
+
const bgColor = styleWithoutBorder?.backgroundColor || "transparent";
|
|
1002
|
+
// Build border CSS for the div wrapper.
|
|
1003
|
+
// When the parent grid already has a divBorderStyle wrapper (border + border-radius +
|
|
1004
|
+
// overflow:hidden), the cell must NOT duplicate the same border/radius — that causes
|
|
1005
|
+
// two concentric borders of the same colour (double-border). The grid's wrapper div
|
|
1006
|
+
// already provides the visual container; the cell div only needs background-color.
|
|
1007
|
+
const cellDivBorderParts = [];
|
|
1008
|
+
if (!parentGridHasBorder) {
|
|
1009
|
+
if (borderRadius)
|
|
1010
|
+
cellDivBorderParts.push(`border-radius:${typeof borderRadius === 'number' ? borderRadius + 'px' : borderRadius};`, `overflow:hidden;`);
|
|
1011
|
+
if (cellBorderShorthand) {
|
|
1012
|
+
cellDivBorderParts.push(`border:${cellBorderShorthand};`);
|
|
1013
|
+
}
|
|
1014
|
+
else if (cellBorderWidth || cellBorderColor || cellBorderStyleProp) {
|
|
1015
|
+
const bw = cellBorderWidth ? (typeof cellBorderWidth === 'number' ? cellBorderWidth + 'px' : cellBorderWidth) : '1px';
|
|
1016
|
+
const bs = cellBorderStyleProp || 'solid';
|
|
1017
|
+
const bc = cellBorderColor || '#000000';
|
|
1018
|
+
cellDivBorderParts.push(`border:${bw} ${bs} ${bc};`);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
const cellDivBorderStyle = cellDivBorderParts.join(' ');
|
|
1022
|
+
// Unconditional div — visible to all clients (Gmail, Outlook new/old, Apple Mail).
|
|
1023
|
+
// background-color on the div covers modern clients; bgcolor on <td> covers Old Outlook.
|
|
1024
|
+
const divStyleParts = [`background-color:${bgColor};`];
|
|
1025
|
+
if (cellDivBorderStyle)
|
|
1026
|
+
divStyleParts.push(cellDivBorderStyle);
|
|
1027
|
+
const divStyleStr = divStyleParts.join(' ');
|
|
1028
|
+
const wrapped = `<div style="${divStyleStr}">${parts.join("")}</div>`;
|
|
915
1029
|
return {
|
|
916
1030
|
html: wrapped,
|
|
917
1031
|
styles,
|
|
@@ -1140,16 +1254,6 @@ async function convertShapeBlock(blockData) {
|
|
|
1140
1254
|
justify: "justify",
|
|
1141
1255
|
};
|
|
1142
1256
|
const textAlignStyle = textAlignMap[textAlign] || "center";
|
|
1143
|
-
const flexJustify = textAlign === "left"
|
|
1144
|
-
? "flex-start"
|
|
1145
|
-
: textAlign === "right"
|
|
1146
|
-
? "flex-end"
|
|
1147
|
-
: "center";
|
|
1148
|
-
const flexAlign = verticalAlign === "top"
|
|
1149
|
-
? "flex-start"
|
|
1150
|
-
: verticalAlign === "bottom"
|
|
1151
|
-
? "flex-end"
|
|
1152
|
-
: "center";
|
|
1153
1257
|
// --- Text styling ---
|
|
1154
1258
|
const textSizeStyle = `font-size:${fontSize}px;line-height:1.3;word-break:break-word;overflow-wrap:break-word;color:${color};`;
|
|
1155
1259
|
// ============================
|
|
@@ -1162,13 +1266,22 @@ async function convertShapeBlock(blockData) {
|
|
|
1162
1266
|
<div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
|
|
1163
1267
|
border:${borderWidth}px ${borderStyle} ${borderColor};
|
|
1164
1268
|
border-radius:${resolvedBorderRadius};
|
|
1165
|
-
background:${finalBackgroundColor}
|
|
1269
|
+
background-color:${finalBackgroundColor};
|
|
1270
|
+
background-image:url('${imageUrl}');
|
|
1271
|
+
background-position:center center;
|
|
1272
|
+
background-size:cover;
|
|
1273
|
+
background-repeat:no-repeat;
|
|
1166
1274
|
overflow:hidden;${alignmentStyle}${customCss || ""}">
|
|
1167
|
-
<
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1275
|
+
<table border="0" cellpadding="0" cellspacing="0" width="${resolvedWidthPx}"
|
|
1276
|
+
style="width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;border-collapse:collapse;">
|
|
1277
|
+
<tr>
|
|
1278
|
+
<td align="${textAlignStyle}" valign="${verticalAlign}"
|
|
1279
|
+
width="${resolvedWidthPx}" height="${resolvedHeightPx}"
|
|
1280
|
+
style="padding:6px;vertical-align:${verticalAlign};text-align:${textAlignStyle};overflow:hidden;box-sizing:border-box;">
|
|
1281
|
+
<div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">${text}</div>
|
|
1282
|
+
</td>
|
|
1283
|
+
</tr>
|
|
1284
|
+
</table>
|
|
1172
1285
|
</div>`;
|
|
1173
1286
|
}
|
|
1174
1287
|
// --- Case 2: Image only ---
|
|
@@ -1187,15 +1300,20 @@ async function convertShapeBlock(blockData) {
|
|
|
1187
1300
|
else {
|
|
1188
1301
|
nonMsoContent = `
|
|
1189
1302
|
<div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
|
|
1190
|
-
background:${finalBackgroundColor};
|
|
1303
|
+
background-color:${finalBackgroundColor};
|
|
1191
1304
|
border:${borderWidth}px ${borderStyle} ${borderColor};
|
|
1192
1305
|
border-radius:${resolvedBorderRadius};
|
|
1193
1306
|
overflow:hidden;${alignmentStyle}${customCss || ""}">
|
|
1194
|
-
<
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1307
|
+
<table border="0" cellpadding="0" cellspacing="0" width="${resolvedWidthPx}"
|
|
1308
|
+
style="width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;border-collapse:collapse;">
|
|
1309
|
+
<tr>
|
|
1310
|
+
<td align="${textAlignStyle}" valign="${verticalAlign}"
|
|
1311
|
+
width="${resolvedWidthPx}" height="${resolvedHeightPx}"
|
|
1312
|
+
style="padding:8px;vertical-align:${verticalAlign};text-align:${textAlignStyle};box-sizing:border-box;">
|
|
1313
|
+
<div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">${text || ""}</div>
|
|
1314
|
+
</td>
|
|
1315
|
+
</tr>
|
|
1316
|
+
</table>
|
|
1199
1317
|
</div>`;
|
|
1200
1318
|
}
|
|
1201
1319
|
// Outlook (VML) fallback
|
|
@@ -1377,7 +1495,7 @@ function convertVerticalDividerBlockToHtml(blockData) {
|
|
|
1377
1495
|
});
|
|
1378
1496
|
return `
|
|
1379
1497
|
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
|
|
1380
|
-
style="${exports.tableCommonStyle} max-width:600px;" class="${visibilityClass}" data-block-type="vdivider" data-block-props="${vDividerProps}">
|
|
1498
|
+
style="${exports.tableCommonStyle}; max-width:600px;" class="${visibilityClass}" data-block-type="vdivider" data-block-props="${vDividerProps}">
|
|
1381
1499
|
<tr>
|
|
1382
1500
|
<td style="${outerStyles}; text-align:center; vertical-align:middle;">
|
|
1383
1501
|
<!--[if mso | IE]>
|