email-builder-utils 1.1.48 → 1.1.50
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.
- package/dist/utils/blocks/button.d.ts +0 -27
- package/dist/utils/blocks/button.d.ts.map +1 -1
- package/dist/utils/blocks/button.js +70 -20
- package/dist/utils/blocks/grid.d.ts.map +1 -1
- package/dist/utils/blocks/grid.js +129 -26
- package/dist/utils/blocks/image.d.ts.map +1 -1
- package/dist/utils/blocks/image.js +43 -5
- package/dist/utils/blocks/shape.js +9 -9
- package/dist/utils/blocks/text.d.ts.map +1 -1
- package/dist/utils/blocks/text.js +56 -22
- package/dist/utils/buildStyles.d.ts.map +1 -1
- package/dist/utils/buildStyles.js +24 -6
- package/dist/utils/common.d.ts +1 -0
- package/dist/utils/common.d.ts.map +1 -1
- package/dist/utils/common.js +14 -0
- package/dist/utils/convertJsonToHtml.d.ts.map +1 -1
- package/dist/utils/convertJsonToHtml.js +33 -1
- package/dist/utils/jsonToHTML.d.ts +0 -1
- package/dist/utils/jsonToHTML.d.ts.map +1 -1
- package/dist/utils/jsonToHTML.js +2 -3
- package/dist/utils/outlookSupport.d.ts +1 -0
- package/dist/utils/outlookSupport.d.ts.map +1 -1
- package/dist/utils/outlookSupport.js +43 -6
- package/package.json +1 -1
|
@@ -1,29 +1,2 @@
|
|
|
1
|
-
export declare function appendOutlookForButton(buttonData: {
|
|
2
|
-
style: any;
|
|
3
|
-
text: string;
|
|
4
|
-
navigateToUrl: string;
|
|
5
|
-
}): {
|
|
6
|
-
innerContent: string;
|
|
7
|
-
computed: {
|
|
8
|
-
fs: any;
|
|
9
|
-
fontWeight: any;
|
|
10
|
-
containerAlign: any;
|
|
11
|
-
padTop: any;
|
|
12
|
-
padRight: any;
|
|
13
|
-
padBottom: any;
|
|
14
|
-
padLeft: any;
|
|
15
|
-
explicitWidth: any;
|
|
16
|
-
vmlWidth: number;
|
|
17
|
-
safeColor: any;
|
|
18
|
-
bgColor: any;
|
|
19
|
-
safeFF: string;
|
|
20
|
-
finalHeight: any;
|
|
21
|
-
explicitHeight: any;
|
|
22
|
-
bw: any;
|
|
23
|
-
br: any;
|
|
24
|
-
bdColor: any;
|
|
25
|
-
bdStyle: any;
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
1
|
export declare function convertButtonBlock(blockData: any): string;
|
|
29
2
|
//# sourceMappingURL=button.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/utils/blocks/button.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/utils/blocks/button.ts"],"names":[],"mappings":"AA6IA,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,GAAG,UAuFhD"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.appendOutlookForButton = appendOutlookForButton;
|
|
4
3
|
exports.convertButtonBlock = convertButtonBlock;
|
|
5
4
|
const buildStyles_1 = require("../buildStyles");
|
|
6
5
|
const fontFallback_1 = require("../fontFallback");
|
|
@@ -26,27 +25,55 @@ function appendOutlookForButton(buttonData) {
|
|
|
26
25
|
const fontWeight = style.fontWeight || 400;
|
|
27
26
|
const containerAlign = style.alignment || style.textAlign || "left";
|
|
28
27
|
const explicitWidth = typeof style.width === "number" && style.width > 0 ? style.width : 0;
|
|
28
|
+
// VML requires explicit pixel width — estimate from text + padding when not set.
|
|
29
|
+
// Slightly conservative multiplier to reduce clipping risk in Outlook.
|
|
29
30
|
const estimatedTextWidth = Math.ceil((text || "").length * fs * 0.62);
|
|
30
31
|
const vmlWidthBase = explicitWidth || Math.max(120, estimatedTextWidth + padLeft + padRight + bw * 2);
|
|
32
|
+
// For pill buttons (large radius), ensure enough width so the curved ends don't encroach.
|
|
31
33
|
const minPillWidth = br > 0 ? Math.ceil(finalHeight * 2) : 0;
|
|
32
34
|
const vmlWidth = Math.max(vmlWidthBase, minPillWidth);
|
|
33
35
|
const borderCss = bw > 0 ? `border:${bw}px ${bdStyle} ${bdColor};` : "";
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
// Empty href triggers New Outlook's link sanitizer to strip surrounding styles.
|
|
37
|
+
const safeHref = navigateToUrl && navigateToUrl.trim() ? navigateToUrl : "#";
|
|
38
|
+
// ── Non-MSO fallback ──
|
|
39
|
+
// All visual box styling (bg, border, radius, width, height) lives on the <td>.
|
|
40
|
+
// New Outlook (and many web mail clients) strip these from <a> inline styles,
|
|
41
|
+
// treating anchors as inline text. The <td> is a block container and is preserved.
|
|
42
|
+
// The <a> fills the cell so the entire button is the click target — typography only.
|
|
43
|
+
// Down-level-hidden conditional comments hide this branch from classic Outlook
|
|
44
|
+
// (which renders the VML below), so both engines see exactly one button.
|
|
45
|
+
const tdWidthCss = explicitWidth ? `width:${explicitWidth}px;` : "";
|
|
46
|
+
const tdHeightCss = explicitHeight > 0 ? `height:${finalHeight}px;` : "";
|
|
47
|
+
const tdWidthAttr = explicitWidth ? ` width="${explicitWidth}"` : "";
|
|
48
|
+
const tdHeightAttr = explicitHeight > 0 ? ` height="${finalHeight}"` : "";
|
|
49
|
+
// <a> fills the cell. When height is explicit, line-height = inner height for vertical
|
|
50
|
+
// centering. When not, padding provides the vertical sizing (matches canvas behavior).
|
|
51
|
+
const anchorBoxStyles = explicitHeight > 0
|
|
52
|
+
? `display:block;line-height:${finalHeight - 2 * bw}px;padding:0 ${padRight}px;`
|
|
53
|
+
: `display:block;padding:${padTop}px ${padRight}px ${padBottom}px ${padLeft}px;line-height:${fs}px;`;
|
|
54
|
+
const tableAlignAttr = ` align="${containerAlign}"`;
|
|
55
|
+
const tableMargin = containerAlign === 'center' ? 'margin:0 auto;'
|
|
56
|
+
: containerAlign === 'right' ? 'margin-left:auto;margin-right:0;'
|
|
57
|
+
: '';
|
|
38
58
|
const nonMsoAnchor = `<!--[if !mso]><!-->
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
59
|
+
<table border="0" cellpadding="0" cellspacing="0" role="presentation"${tableAlignAttr} style="border-collapse:separate;${tableMargin}">
|
|
60
|
+
<tr>
|
|
61
|
+
<td bgcolor="${bgColor}" align="center" valign="middle"${tdWidthAttr}${tdHeightAttr} style="background-color:${bgColor};border-radius:${br}px;${borderCss}${tdWidthCss}${tdHeightCss}box-sizing:border-box;mso-padding-alt:0;text-align:center;">
|
|
62
|
+
<a href="${safeHref}" target="_blank" rel="noreferrer noopener"
|
|
63
|
+
style="${anchorBoxStyles}color:${safeColor};font-family:${safeFF};font-size:${fs}px;font-weight:${fontWeight};text-decoration:none;text-align:center;white-space:nowrap;-webkit-text-size-adjust:none;box-sizing:border-box;">${text}</a>
|
|
64
|
+
</td>
|
|
65
|
+
</tr>
|
|
66
|
+
</table>
|
|
42
67
|
<!--<![endif]-->`;
|
|
68
|
+
// ── MSO: VML bulletproof button (classic Outlook / Word engine) ──
|
|
69
|
+
// VML arcsize is a percentage of half the shorter side. Clamp to 50% (pill).
|
|
43
70
|
const arcSizePct = br > 0 ? Math.min(Math.round((br / (finalHeight / 2)) * 100), 50) : 0;
|
|
44
71
|
const strokeAttrs = bw > 0
|
|
45
72
|
? `stroke="true" strokecolor="${bdColor}" strokeweight="${bw}px"`
|
|
46
73
|
: `stroke="false"`;
|
|
47
74
|
const msoButton = `<!--[if mso]>
|
|
48
75
|
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word"
|
|
49
|
-
href="${
|
|
76
|
+
href="${safeHref}"
|
|
50
77
|
style="height:${finalHeight}px;v-text-anchor:middle;width:${vmlWidth}px;"
|
|
51
78
|
arcsize="${arcSizePct}%"
|
|
52
79
|
${strokeAttrs}
|
|
@@ -63,12 +90,24 @@ function appendOutlookForButton(buttonData) {
|
|
|
63
90
|
return {
|
|
64
91
|
innerContent,
|
|
65
92
|
computed: {
|
|
66
|
-
fs,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
93
|
+
fs,
|
|
94
|
+
fontWeight,
|
|
95
|
+
containerAlign,
|
|
96
|
+
padTop,
|
|
97
|
+
padRight,
|
|
98
|
+
padBottom,
|
|
99
|
+
padLeft,
|
|
100
|
+
explicitWidth,
|
|
101
|
+
vmlWidth,
|
|
102
|
+
safeColor,
|
|
103
|
+
bgColor,
|
|
104
|
+
safeFF,
|
|
105
|
+
finalHeight,
|
|
106
|
+
explicitHeight,
|
|
107
|
+
bw,
|
|
108
|
+
br,
|
|
109
|
+
bdColor,
|
|
110
|
+
bdStyle,
|
|
72
111
|
},
|
|
73
112
|
};
|
|
74
113
|
}
|
|
@@ -79,9 +118,20 @@ function convertButtonBlock(blockData) {
|
|
|
79
118
|
const visibilityClass = (0, common_1.getVisibilityClass)(props);
|
|
80
119
|
const { innerContent, computed } = appendOutlookForButton({
|
|
81
120
|
style: {
|
|
82
|
-
fontFamily,
|
|
83
|
-
|
|
84
|
-
|
|
121
|
+
fontFamily,
|
|
122
|
+
fontSize,
|
|
123
|
+
fontWeight,
|
|
124
|
+
textAlign,
|
|
125
|
+
borderColor,
|
|
126
|
+
borderRadius,
|
|
127
|
+
borderWidth,
|
|
128
|
+
borderStyle,
|
|
129
|
+
buttonPadding,
|
|
130
|
+
color,
|
|
131
|
+
buttonColor,
|
|
132
|
+
width,
|
|
133
|
+
height,
|
|
134
|
+
alignment,
|
|
85
135
|
},
|
|
86
136
|
text: text || "",
|
|
87
137
|
navigateToUrl: navigateToUrl || "",
|
|
@@ -118,9 +168,9 @@ function convertButtonBlock(blockData) {
|
|
|
118
168
|
hideOnMobile: Boolean(props.hideOnMobile),
|
|
119
169
|
});
|
|
120
170
|
return `
|
|
121
|
-
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" class="${visibilityClass}" data-block-type="button" data-block-props="${buttonBlockProps}">
|
|
171
|
+
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" class="${visibilityClass}" data-block-type="button" data-block-props="${buttonBlockProps}" style="border-collapse:collapse;table-layout:fixed;">
|
|
122
172
|
<tr>
|
|
123
|
-
<td align="${computed.containerAlign}"
|
|
173
|
+
<td align="${computed.containerAlign}"${(0, common_1.buildOutlookBgAttr)(containerBg)}
|
|
124
174
|
style="padding:${padding?.top || 0}px ${padding?.right || 0}px ${padding?.bottom || 0}px ${padding?.left || 0}px;background-color:${containerBg || 'transparent'};">
|
|
125
175
|
${innerContent}
|
|
126
176
|
</td>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../src/utils/blocks/grid.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../src/utils/blocks/grid.ts"],"names":[],"mappings":"AAMA,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,GAAG,EACd,QAAQ,EAAE,GAAG,EACb,aAAa,EAAE,MAAM,mBAgTtB;AAED,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,GAAG,EACd,QAAQ,EAAE,GAAG,EACb,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,EAAE,MAAM,EACzB,mBAAmB,UAAQ;;;GAwG5B"}
|
|
@@ -11,12 +11,17 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
11
11
|
const { columns = 1, cellWidths = [], responsive = true } = props;
|
|
12
12
|
const { columnGap = 0, backgroundImage, backgroundColor, ...restStyle } = style;
|
|
13
13
|
const gridVisibilityClass = (0, common_1.getVisibilityClass)(props);
|
|
14
|
+
// Detect gradient — check both backgroundImage prop and customCss (gradient may land in
|
|
15
|
+
// customCss when the block was built via CSS shorthand or custom CSS input).
|
|
14
16
|
const bgImageStr = typeof backgroundImage === "string" ? backgroundImage : '';
|
|
15
17
|
const customCssStr = restStyle.customCss || '';
|
|
18
|
+
// Extract gradient string from customCss if not already in backgroundImage
|
|
16
19
|
const gradientInCustomCss = !bgImageStr.includes('gradient(') && customCssStr.includes('gradient(')
|
|
17
20
|
? (customCssStr.match(/(?:linear|radial|conic)-gradient\([^)]+(?:\([^)]*\)[^)]*)*\)/)?.[0] || '')
|
|
18
21
|
: '';
|
|
19
|
-
const effectiveGradient = bgImageStr.includes('gradient(')
|
|
22
|
+
const effectiveGradient = bgImageStr.includes('gradient(')
|
|
23
|
+
? bgImageStr
|
|
24
|
+
: gradientInCustomCss;
|
|
20
25
|
const isGradient = Boolean(effectiveGradient);
|
|
21
26
|
const parsedGradient = isGradient ? (0, gradientUtils_1.parseGradient)(effectiveGradient) : null;
|
|
22
27
|
const fallbackBgColor = backgroundColor ||
|
|
@@ -26,12 +31,18 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
26
31
|
const rawBgImageUrl = !isGradient && bgImageStr
|
|
27
32
|
? bgImageStr.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "")
|
|
28
33
|
: null;
|
|
34
|
+
// When gradient came from customCss, strip background-image from customCss so it
|
|
35
|
+
// doesn't duplicate into the inner table style (the outer <td> wrapper carries it).
|
|
29
36
|
const innerCustomCss = gradientInCustomCss
|
|
30
37
|
? customCssStr.replace(/background-image\s*:[^;]+;?/gi, '').trim()
|
|
31
38
|
: customCssStr;
|
|
39
|
+
// Build inner table styles — when gradient/bg-image is on the outer wrapper, strip
|
|
40
|
+
// background props from the inner table so the outer <td> background shows through.
|
|
32
41
|
const innerRestStyleRaw = (rawBgImageUrl || isGradient)
|
|
33
42
|
? { ...restStyle, customCss: innerCustomCss, backgroundSize: undefined, backgroundPosition: undefined, backgroundRepeat: undefined }
|
|
34
43
|
: { ...restStyle, customCss: innerCustomCss };
|
|
44
|
+
// Extract border/radius props — applied via a div wrapper for non-MSO clients so that
|
|
45
|
+
// border-radius is honoured (Gmail/Outlook compose strip border-radius from <table>).
|
|
35
46
|
const { borderRadius, border, borderColor, borderWidth, borderStyle: borderStyleProp, ...innerRestStyle } = innerRestStyleRaw;
|
|
36
47
|
const divBorderParts = [];
|
|
37
48
|
if (borderRadius)
|
|
@@ -50,16 +61,31 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
50
61
|
? 'transparent'
|
|
51
62
|
: ((rawBgImageUrl || isGradient) ? undefined : backgroundColor);
|
|
52
63
|
const tableStyles = (0, buildStyles_1.buildStyles)({ backgroundColor: tableBgForNonMso, ...innerRestStyle }, {
|
|
53
|
-
perChanges: [],
|
|
64
|
+
perChanges: [],
|
|
65
|
+
pxChanges: buildStyles_1.allPxAttributes,
|
|
54
66
|
});
|
|
55
67
|
const total = childrenIds.length;
|
|
56
68
|
const visualRows = Math.ceil(total / columns);
|
|
69
|
+
// OUTLOOK FIX: Use explicit pixel width for Old Outlook (Word engine)
|
|
57
70
|
const msoTableWidth = Math.min(cellWidthInPx, 600);
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
// When a background image/gradient is present, the background is applied on an outer
|
|
72
|
+
// wrapper <td> (see bottom of function). The inner grid tables must be clean.
|
|
73
|
+
// When no background, the MSO table gets bgcolor for solid-color sections.
|
|
74
|
+
const msoBgColor = !rawBgImageUrl && !isGradient
|
|
75
|
+
? (backgroundColor || '')
|
|
76
|
+
: '';
|
|
77
|
+
const msoBgAttr = (0, common_1.buildOutlookBgAttr)(msoBgColor);
|
|
60
78
|
const msoBgStyle = msoBgColor ? `background-color:${msoBgColor};` : '';
|
|
61
|
-
|
|
62
|
-
const
|
|
79
|
+
// Inner tables must be explicitly transparent when outer <td> carries the background.
|
|
80
|
+
const innerBgTransparent = (rawBgImageUrl || isGradient)
|
|
81
|
+
? 'background-color:transparent;'
|
|
82
|
+
: '';
|
|
83
|
+
const nonMsoBgAttr = !rawBgImageUrl && !isGradient && !divBorderStyle
|
|
84
|
+
? (0, common_1.buildOutlookBgAttr)(backgroundColor || '')
|
|
85
|
+
: '';
|
|
86
|
+
// When divBorderStyle is set the non-MSO <table> is transparent, so the Grid's
|
|
87
|
+
// backgroundColor must move onto the div wrapper — otherwise it vanishes in modern clients.
|
|
88
|
+
// Skip this for bg-image/gradient blocks; they apply their background via a separate wrapper.
|
|
63
89
|
const divWrapBg = divBorderStyle && backgroundColor && !rawBgImageUrl && !isGradient
|
|
64
90
|
? ` background-color:${backgroundColor};`
|
|
65
91
|
: '';
|
|
@@ -72,14 +98,15 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
72
98
|
class="${gridVisibilityClass}">
|
|
73
99
|
<![endif]-->
|
|
74
100
|
<!--[if !mso]><!-->
|
|
75
|
-
<table border="0" cellpadding="0" cellspacing="0" width="100%"
|
|
101
|
+
<table border="0" cellpadding="0" cellspacing="0" width="100%" align="center"
|
|
76
102
|
role="presentation"${nonMsoBgAttr}
|
|
77
|
-
style="border-collapse:collapse; ${innerBgTransparent}${tableStyles}; max-width:600px;"
|
|
103
|
+
style="border-collapse:collapse; table-layout:fixed; ${innerBgTransparent}${tableStyles}; max-width:600px;"
|
|
78
104
|
class="${gridVisibilityClass}">
|
|
79
105
|
<!--<![endif]-->
|
|
80
106
|
`;
|
|
81
107
|
for (let r = 0; r < visualRows; r++) {
|
|
82
108
|
html += "<tr>";
|
|
109
|
+
// COUNT visible cells and find last visible column index
|
|
83
110
|
let visibleCells = 0;
|
|
84
111
|
let lastVisibleCol = 0;
|
|
85
112
|
const rowIds = [];
|
|
@@ -88,12 +115,14 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
88
115
|
const id = childrenIds[idx] ?? null;
|
|
89
116
|
rowIds.push(id);
|
|
90
117
|
const child = id ? rootData[id] : null;
|
|
91
|
-
|
|
118
|
+
const isHidden = child?.data?.props?.hideOnDesktop;
|
|
119
|
+
if (!isHidden) {
|
|
92
120
|
visibleCells++;
|
|
93
121
|
lastVisibleCol = c;
|
|
94
122
|
}
|
|
95
123
|
}
|
|
96
124
|
const safeWidth = visibleCells > 0 ? 100 / visibleCells : 100 / columns;
|
|
125
|
+
// Reserve pixel space for spacer tds between visible cells (N-1 gaps for N visible cells)
|
|
97
126
|
const totalGapPx = columnGap * Math.max(visibleCells - 1, 0);
|
|
98
127
|
const adjustedTableWidth = Math.max(msoTableWidth - totalGapPx, 1);
|
|
99
128
|
let totalWidth = 0;
|
|
@@ -101,19 +130,24 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
101
130
|
for (let c = 0; c < columns; c++) {
|
|
102
131
|
const id = rowIds[c];
|
|
103
132
|
let widthPercent = cellWidths[c] ?? safeWidth;
|
|
104
|
-
if (widthPercent <= 0 || widthPercent > 100)
|
|
133
|
+
if (widthPercent <= 0 || widthPercent > 100) {
|
|
105
134
|
widthPercent = safeWidth;
|
|
135
|
+
}
|
|
106
136
|
cellWidthPercents.push(widthPercent);
|
|
107
137
|
if (id) {
|
|
108
138
|
const child = rootData[id];
|
|
109
|
-
|
|
139
|
+
const isHidden = child?.data?.props?.hideOnDesktop;
|
|
140
|
+
if (!isHidden) {
|
|
110
141
|
totalWidth += widthPercent;
|
|
142
|
+
}
|
|
111
143
|
}
|
|
112
144
|
}
|
|
113
145
|
const scaleFactor = totalWidth > 0 && totalWidth < 100 ? 100 / totalWidth : 1;
|
|
114
146
|
for (let c = 0; c < columns; c++) {
|
|
115
147
|
const id = rowIds[c];
|
|
116
|
-
let widthPercent =
|
|
148
|
+
let widthPercent = cellWidthPercents[c] * scaleFactor;
|
|
149
|
+
widthPercent = Math.min(widthPercent, 100);
|
|
150
|
+
// Cell pixel width is a share of the gap-adjusted table width
|
|
117
151
|
const cellWidthPx = Math.round((widthPercent / 100) * adjustedTableWidth);
|
|
118
152
|
if (id) {
|
|
119
153
|
const child = rootData[id];
|
|
@@ -123,34 +157,40 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
123
157
|
const visibilityClass = (0, common_1.getVisibilityClass)(childProps);
|
|
124
158
|
if (childVisible) {
|
|
125
159
|
const { html: childHtml, styles } = await convertGridCellBlock(child, rootData, widthPercent, adjustedTableWidth, Boolean(divBorderStyle));
|
|
160
|
+
// bgcolor on the cell <td> ensures background-color survives Outlook
|
|
161
|
+
// compose paste (Word/Web editors strip CSS but keep bgcolor attribute).
|
|
126
162
|
const cellBgColor = cellStyle.backgroundColor || '';
|
|
127
|
-
const cellBgAttr =
|
|
163
|
+
const cellBgAttr = (0, common_1.buildOutlookBgAttr)(cellBgColor);
|
|
128
164
|
html += `
|
|
129
165
|
<td
|
|
130
|
-
width="${
|
|
166
|
+
width="${Math.max(1, Math.round(widthPercent))}%"${cellBgAttr}
|
|
131
167
|
class="${[responsive ? "stack-column" : "", visibilityClass].filter(Boolean).join(" ")}"
|
|
132
|
-
style="width:${
|
|
168
|
+
style="width:${widthPercent}%;vertical-align:${verticalAlign};word-break:break-word;${styles}"
|
|
133
169
|
>
|
|
134
170
|
${childHtml}
|
|
135
171
|
</td>`;
|
|
172
|
+
// Spacer td between columns — uses width attribute only (no inline style width) so
|
|
173
|
+
// Outlook mobile (which strips <style>) treats it proportionally, not as a fixed blocker.
|
|
174
|
+
// col-gap-spacer class hides it when columns stack via CSS media query.
|
|
136
175
|
if (columnGap > 0 && c !== lastVisibleCol) {
|
|
137
|
-
html += `<td width="${columnGap}" style="
|
|
176
|
+
html += `<td width="${columnGap}" class="col-gap-spacer" style="font-size:0;line-height:0;padding:0;" aria-hidden="true"></td>`;
|
|
138
177
|
}
|
|
139
178
|
}
|
|
140
179
|
}
|
|
141
180
|
else {
|
|
142
181
|
html += `
|
|
143
|
-
<td width="${
|
|
182
|
+
<td width="${Math.max(1, Math.round(widthPercent))}%"
|
|
144
183
|
${responsive ? 'class="stack-column"' : ""}
|
|
145
|
-
style="width:${
|
|
184
|
+
style="width:${widthPercent}%;vertical-align:top;">
|
|
146
185
|
</td>`;
|
|
147
186
|
if (columnGap > 0 && c !== lastVisibleCol) {
|
|
148
|
-
html += `<td width="${columnGap}" style="
|
|
187
|
+
html += `<td width="${columnGap}" class="col-gap-spacer" style="font-size:0;line-height:0;padding:0;" aria-hidden="true"></td>`;
|
|
149
188
|
}
|
|
150
189
|
}
|
|
151
190
|
}
|
|
152
191
|
html += "</tr>";
|
|
153
192
|
}
|
|
193
|
+
// Close both MSO and non-MSO tables
|
|
154
194
|
html += `
|
|
155
195
|
<!--[if mso]>
|
|
156
196
|
</table>
|
|
@@ -159,6 +199,21 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
159
199
|
</table>
|
|
160
200
|
<!--<![endif]-->
|
|
161
201
|
`;
|
|
202
|
+
// ── Background image: canonical multi-client approach ────────────────────
|
|
203
|
+
//
|
|
204
|
+
// Problem: `background-image` on a <table> element is stripped by:
|
|
205
|
+
// • New Outlook Mac / Windows (Chromium-based app)
|
|
206
|
+
// • Outlook.com
|
|
207
|
+
// • Old Outlook (Word engine) — ignores CSS entirely
|
|
208
|
+
//
|
|
209
|
+
// Solution: wrap the grid in an outer <table><tr><td> where the <td> carries
|
|
210
|
+
// the background. Different clients pick it up via different mechanisms:
|
|
211
|
+
//
|
|
212
|
+
// background="" attribute on <td> → Yahoo Mail, older webmail
|
|
213
|
+
// CSS background-image on <td> → Gmail, Apple Mail, new Outlook Mac ✓
|
|
214
|
+
// VML v:rect inside the <td> → Old Outlook (Word engine) ✓
|
|
215
|
+
//
|
|
216
|
+
// The inner grid tables have NO background so the outer <td> bg shows through.
|
|
162
217
|
if (rawBgImageUrl || isGradient) {
|
|
163
218
|
const vmlFill = isGradient
|
|
164
219
|
? (() => {
|
|
@@ -170,9 +225,9 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
170
225
|
: `<v:fill type="frame" src="${rawBgImageUrl}" color="${fallbackBgColor}" />`;
|
|
171
226
|
html = `
|
|
172
227
|
<table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation"
|
|
173
|
-
style="border-collapse:collapse;width:100%;max-width:${msoTableWidth}px;">
|
|
228
|
+
style="border-collapse:collapse;table-layout:fixed;width:100%;max-width:${msoTableWidth}px;">
|
|
174
229
|
<tr>
|
|
175
|
-
<td width="100%"
|
|
230
|
+
<td width="100%"${(0, common_1.buildOutlookBgAttr)(fallbackBgColor)} valign="top"
|
|
176
231
|
${!isGradient && rawBgImageUrl ? `background="${rawBgImageUrl}"` : ""}
|
|
177
232
|
style="
|
|
178
233
|
width:100%;max-width:${msoTableWidth}px;
|
|
@@ -199,32 +254,68 @@ async function convertGridBlock(blockData, rootData, cellWidthInPx) {
|
|
|
199
254
|
</tr>
|
|
200
255
|
</table>`;
|
|
201
256
|
}
|
|
257
|
+
// Wrap the entire grid (including any bg-image outer table) in a div when the block
|
|
258
|
+
// has border/radius. An unconditional <div> is used — not gated behind <!--[if !mso]>-->
|
|
259
|
+
// — so Gmail compose paste renders the border-radius reliably. Old Outlook ignores
|
|
260
|
+
// border-radius on <div> but still shows the rectangular border; new Outlook works fully.
|
|
202
261
|
if (divBorderStyle)
|
|
203
262
|
html = `${divWrapOpen}${html}${divWrapClose}`;
|
|
204
263
|
return html;
|
|
205
264
|
}
|
|
206
265
|
async function convertGridCellBlock(blockData, rootData, cellWidthPercent, parentCellWidthPx, parentGridHasBorder = false) {
|
|
207
266
|
const { style = {}, childrenIds = [], props = {} } = blockData.data;
|
|
267
|
+
const visibilityClass = (0, common_1.getVisibilityClass)(props);
|
|
268
|
+
// Extract border + radius from style so they move to the div wrapper (not the <td>).
|
|
269
|
+
// Gmail strips border-radius from <td> but honours it on <div>. By putting border and
|
|
270
|
+
// radius on the same unconditional <div>, the rounded card border renders in all clients.
|
|
271
|
+
// The <td> keeps bgcolor (via attribute) for Old Outlook background fallback.
|
|
208
272
|
const { borderRadius: cellBorderRadius, borderWidth: cellBorderWidth, borderStyle: cellBorderStyleProp, borderColor: cellBorderColor, border: cellBorderShorthand, ...styleWithoutBorder } = style;
|
|
273
|
+
// backgroundColor must stay on the div wrapper (not the <td>) in two cases:
|
|
274
|
+
// 1. Cell has its own border-radius — the div's overflow:hidden clips the background.
|
|
275
|
+
// 2. Parent grid has a border div (divBorderStyle) — the grid's overflow:hidden clips it.
|
|
276
|
+
// In both cases, the rectangular <td> background bleeds through rounded corners if kept
|
|
277
|
+
// in CSS, creating visible corner squares. The bgcolor attribute stays for Outlook fallback.
|
|
209
278
|
const stripBgFromTd = Boolean(cellBorderRadius) || parentGridHasBorder;
|
|
279
|
+
// When stripping bg from <td>, omit backgroundColor entirely rather than setting 'transparent'.
|
|
280
|
+
// An explicit background-color:transparent in CSS can defeat the bgcolor HTML attribute in
|
|
281
|
+
// Old Outlook (Word engine), leaving the cell with no background at all.
|
|
210
282
|
const styleForTd = stripBgFromTd
|
|
211
|
-
? { ...styleWithoutBorder, backgroundColor:
|
|
283
|
+
? { ...styleWithoutBorder, backgroundColor: undefined }
|
|
212
284
|
: styleWithoutBorder;
|
|
213
|
-
|
|
285
|
+
// Outlook treats <td width="..."> as content width and then adds horizontal padding,
|
|
286
|
+
// which can push the total row width beyond the parent table and shift columns.
|
|
287
|
+
// Keep sizing styles on <td>, but move padding into the inner div wrapper.
|
|
288
|
+
const styleForTdWithoutPadding = { ...styleForTd, padding: undefined };
|
|
289
|
+
const styles = (0, buildStyles_1.buildStyles)(styleForTdWithoutPadding, {
|
|
290
|
+
perChanges: [],
|
|
291
|
+
pxChanges: buildStyles_1.allPxAttributes,
|
|
292
|
+
});
|
|
214
293
|
const parts = [];
|
|
294
|
+
// OUTLOOK FIX: Calculate the actual cell width in pixels based on percentage
|
|
295
|
+
// If parent is 600px and cell is 50%, cell width should be 300px, not 600px
|
|
215
296
|
const cellWidthPx = Math.round((cellWidthPercent / 100) * parentCellWidthPx);
|
|
297
|
+
// Subtract the cell's own padding so children receive the actual content-area width.
|
|
298
|
+
// Old Outlook honours explicit img/table width attributes — if a child is sized to the
|
|
299
|
+
// full column width (ignoring padding) it overflows and expands the column.
|
|
216
300
|
const cellPad = styleWithoutBorder?.padding || {};
|
|
217
301
|
const cellPadLeft = Number.isFinite(cellPad.left) ? cellPad.left : 0;
|
|
218
302
|
const cellPadRight = Number.isFinite(cellPad.right) ? cellPad.right : 0;
|
|
219
303
|
const contentWidthPx = Math.max(cellWidthPx - cellPadLeft - cellPadRight, 20);
|
|
304
|
+
// OUTLOOK FIX: Ensure cell width is reasonable and capped at 600px
|
|
220
305
|
const safeCellWidthPx = Math.min(contentWidthPx, 600);
|
|
221
306
|
for (const childId of childrenIds) {
|
|
222
307
|
const child = rootData[childId];
|
|
223
|
-
if (child)
|
|
308
|
+
if (child) {
|
|
224
309
|
parts.push(await (0, jsonToHTML_1.convertToHtml)(child, rootData, safeCellWidthPx));
|
|
310
|
+
}
|
|
225
311
|
}
|
|
226
312
|
const borderRadius = cellBorderRadius || 0;
|
|
227
313
|
const bgColor = styleWithoutBorder?.backgroundColor || "transparent";
|
|
314
|
+
// Build border CSS for the div wrapper.
|
|
315
|
+
// When the parent grid already has a divBorderStyle wrapper (border + border-radius +
|
|
316
|
+
// overflow:hidden), the cell must NOT duplicate the same border/radius — that causes
|
|
317
|
+
// two concentric borders of the same colour (double-border). The grid's wrapper div
|
|
318
|
+
// already provides the visual container; the cell div only needs background-color.
|
|
228
319
|
const cellDivBorderParts = [];
|
|
229
320
|
if (!parentGridHasBorder) {
|
|
230
321
|
if (borderRadius)
|
|
@@ -240,9 +331,21 @@ async function convertGridCellBlock(blockData, rootData, cellWidthPercent, paren
|
|
|
240
331
|
}
|
|
241
332
|
}
|
|
242
333
|
const cellDivBorderStyle = cellDivBorderParts.join(' ');
|
|
334
|
+
// Unconditional div — visible to all clients (Gmail, Outlook new/old, Apple Mail).
|
|
335
|
+
// background-color on the div covers modern clients; bgcolor on <td> covers Old Outlook.
|
|
243
336
|
const divStyleParts = [`background-color:${bgColor};`];
|
|
337
|
+
const formatPad = (value) => (typeof value === 'number' ? `${value}px` : (value || '0'));
|
|
338
|
+
const cellPadTop = formatPad(cellPad.top);
|
|
339
|
+
const cellPadBottom = formatPad(cellPad.bottom);
|
|
340
|
+
const cellPadLeftCss = formatPad(cellPad.left);
|
|
341
|
+
const cellPadRightCss = formatPad(cellPad.right);
|
|
342
|
+
divStyleParts.push(`padding:${cellPadTop} ${cellPadRightCss} ${cellPadBottom} ${cellPadLeftCss};`);
|
|
244
343
|
if (cellDivBorderStyle)
|
|
245
344
|
divStyleParts.push(cellDivBorderStyle);
|
|
246
|
-
const
|
|
247
|
-
|
|
345
|
+
const divStyleStr = divStyleParts.join(' ');
|
|
346
|
+
const wrapped = `<div style="${divStyleStr}">${parts.join("")}</div>`;
|
|
347
|
+
return {
|
|
348
|
+
html: wrapped,
|
|
349
|
+
styles,
|
|
350
|
+
};
|
|
248
351
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../src/utils/blocks/image.ts"],"names":[],"mappings":"AAIA,wBAAsB,uBAAuB,
|
|
1
|
+
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../src/utils/blocks/image.ts"],"names":[],"mappings":"AAIA,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM;;;;;GAoB5B;AAED,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBAiH5E"}
|
|
@@ -29,27 +29,65 @@ async function convertImageBlock(blockData, cellWidthInPx) {
|
|
|
29
29
|
const { altText, imageUrl, navigateToUrl } = props;
|
|
30
30
|
const visibilityClass = (0, common_1.getVisibilityClass)(props);
|
|
31
31
|
const { width, height, objectFit, borderRadius, borderWidth, borderColor, borderStyle, ...containerStyle } = style;
|
|
32
|
-
|
|
32
|
+
// Add border styles to container for fallback clients
|
|
33
|
+
const containerStyles = (0, buildStyles_1.buildStyles)({
|
|
34
|
+
...containerStyle,
|
|
35
|
+
}, { perChanges: [], pxChanges: buildStyles_1.addPxToAttributes });
|
|
36
|
+
// OUTLOOK FIX: Ensure cellWidthInPx never exceeds 600px
|
|
33
37
|
const safeCellWidth = Math.min(cellWidthInPx, 600);
|
|
38
|
+
// Parse width percentage (default 100%)
|
|
34
39
|
const widthPercent = typeof width === "string" && width.includes("%")
|
|
35
40
|
? parseInt(width.replace("%", ""))
|
|
36
|
-
: typeof width === "number"
|
|
41
|
+
: typeof width === "number"
|
|
42
|
+
? width
|
|
43
|
+
: 100;
|
|
44
|
+
// OUTLOOK FIX: Calculate inner container width based on safe cell width
|
|
37
45
|
const paddingLeft = style?.padding?.left || 0;
|
|
38
46
|
const paddingRight = style?.padding?.right || 0;
|
|
39
47
|
const availableWidth = safeCellWidth - paddingLeft - paddingRight;
|
|
40
48
|
const innerContainerWidth = Math.round((widthPercent / 100) * availableWidth);
|
|
49
|
+
// Get image dimensions and calculate scaled sizes
|
|
41
50
|
const { originalWidth, originalHeight, scaledWidth, scaledHeight } = await computeScaledDimensions(imageUrl, innerContainerWidth);
|
|
51
|
+
// OUTLOOK FIX: For Outlook, we need exact pixel dimensions
|
|
52
|
+
// Calculate final dimensions that respect both original size and container
|
|
42
53
|
const finalWidth = Math.min(scaledWidth, innerContainerWidth, originalWidth);
|
|
43
54
|
const finalHeight = Math.round((finalWidth / originalWidth) * originalHeight);
|
|
44
|
-
|
|
45
|
-
const
|
|
55
|
+
// Build image styles for modern email clients (non-Outlook)
|
|
56
|
+
const imageTagStyles = (0, buildStyles_1.buildStyles)({
|
|
57
|
+
borderStyle,
|
|
58
|
+
borderRadius: borderRadius,
|
|
59
|
+
borderColor,
|
|
60
|
+
borderWidth,
|
|
61
|
+
}, {
|
|
62
|
+
perChanges: [],
|
|
63
|
+
pxChanges: buildStyles_1.addPxToAttributes,
|
|
64
|
+
});
|
|
65
|
+
// OUTLOOK RENDERING CONTRACT
|
|
66
|
+
// width/height HTML attributes → Old Outlook Word engine (CSS is lower priority in Word)
|
|
67
|
+
// width/height inline CSS → New Outlook, OWA, and all CSS-capable clients
|
|
68
|
+
// max-width:100% → prevents overflow in any client that ignores pixel constraints
|
|
69
|
+
// height:auto !important → @media query (convertJsonToHtml.ts) overrides this on mobile
|
|
70
|
+
// so proportional scaling is preserved on small screens
|
|
71
|
+
// -ms-interpolation-mode → inline fallback; the <style> block is stripped by
|
|
72
|
+
// Outlook during forward/reply MIME rewriting
|
|
73
|
+
const imageElement = `<img src="${imageUrl}" alt="${altText || "Image"}" border="0" width="${finalWidth}" height="${finalHeight}" style="${imageTagStyles}; display:block; width:${finalWidth}px; height:${finalHeight}px; max-width:100%; line-height:0; -ms-interpolation-mode:bicubic;" />`;
|
|
46
74
|
const percentWidth = typeof width === "string" && width.endsWith("%")
|
|
47
75
|
? width
|
|
48
|
-
: typeof width === "number"
|
|
76
|
+
: typeof width === "number"
|
|
77
|
+
? `${width}%`
|
|
78
|
+
: "100%";
|
|
79
|
+
// Non-MSO wrapper: display:block removes the phantom inline-baseline gap that
|
|
80
|
+
// display:inline-block creates in Gmail / Apple Mail / Yahoo between images.
|
|
81
|
+
// margin handles alignment since text-align won't move block elements.
|
|
49
82
|
const imgTextAlign = containerStyle.textAlign || "left";
|
|
50
83
|
const imgMargin = imgTextAlign === "center" ? "margin:0 auto;" :
|
|
51
84
|
imgTextAlign === "right" ? "margin-left:auto; margin-right:0;" : "";
|
|
85
|
+
// OUTLOOK FIX: Use finalWidth (the actual displayed size) as max-width so the div
|
|
86
|
+
// doesn't claim more space than the image occupies. originalWidth is the natural
|
|
87
|
+
// image size (e.g. 636px for the Beefree logo rendered at 35px) which was
|
|
88
|
+
// misleadingly large and could confuse some rendering engines.
|
|
52
89
|
const nonMsoWrapper = `<div style="display:block; width:${percentWidth}; max-width:${finalWidth}px; line-height:0; font-size:0; ${imgMargin}">${imageElement}</div>`;
|
|
90
|
+
// OUTLOOK FIX: Generate VML with corrected dimensions
|
|
53
91
|
const outlookImage = await (0, outlookSupport_1.appendOutlookForImage)(nonMsoWrapper, safeCellWidth, innerContainerWidth, imageUrl, style, finalWidth, finalHeight);
|
|
54
92
|
const imageContent = (0, outlookSupport_1.appendOutlookSupport)(outlookImage, containerStyles, visibilityClass, safeCellWidth);
|
|
55
93
|
return navigateToUrl
|
|
@@ -147,7 +147,7 @@ async function convertShapeBlock(blockData) {
|
|
|
147
147
|
// --- Case 1: Image + Text ---
|
|
148
148
|
if (imageUrl && text) {
|
|
149
149
|
nonMsoContent = `
|
|
150
|
-
<div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
|
|
150
|
+
<div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;max-width:100%;box-sizing:border-box;
|
|
151
151
|
border:${borderWidth}px ${borderStyle} ${borderColor};
|
|
152
152
|
border-radius:${resolvedBorderRadius};
|
|
153
153
|
background-color:${finalBackgroundColor};
|
|
@@ -156,11 +156,11 @@ async function convertShapeBlock(blockData) {
|
|
|
156
156
|
background-size:cover;
|
|
157
157
|
background-repeat:no-repeat;
|
|
158
158
|
overflow:hidden;${alignmentStyle}${customCss || ""}">
|
|
159
|
-
<table border="0" cellpadding="0" cellspacing="0" width="
|
|
160
|
-
style="width
|
|
159
|
+
<table border="0" cellpadding="0" cellspacing="0" width="100%"
|
|
160
|
+
style="width:100%;height:${resolvedHeightPx}px;border-collapse:collapse;">
|
|
161
161
|
<tr>
|
|
162
162
|
<td align="${textAlignStyle}" valign="${verticalAlign}"
|
|
163
|
-
|
|
163
|
+
height="${resolvedHeightPx}"
|
|
164
164
|
style="padding:6px;vertical-align:${verticalAlign};text-align:${textAlignStyle};overflow:hidden;box-sizing:border-box;">
|
|
165
165
|
<div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">${text}</div>
|
|
166
166
|
</td>
|
|
@@ -171,7 +171,7 @@ async function convertShapeBlock(blockData) {
|
|
|
171
171
|
// --- Case 2: Image only ---
|
|
172
172
|
else if (imageUrl) {
|
|
173
173
|
nonMsoContent = `
|
|
174
|
-
<div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
|
|
174
|
+
<div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;max-width:100%;box-sizing:border-box;
|
|
175
175
|
border:${borderWidth}px ${borderStyle} ${borderColor};
|
|
176
176
|
border-radius:${resolvedBorderRadius};
|
|
177
177
|
overflow:hidden;${alignmentStyle}${customCss || ""}">
|
|
@@ -183,16 +183,16 @@ async function convertShapeBlock(blockData) {
|
|
|
183
183
|
// --- Case 3: Text only ---
|
|
184
184
|
else {
|
|
185
185
|
nonMsoContent = `
|
|
186
|
-
<div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
|
|
186
|
+
<div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;max-width:100%;box-sizing:border-box;
|
|
187
187
|
background-color:${finalBackgroundColor};
|
|
188
188
|
border:${borderWidth}px ${borderStyle} ${borderColor};
|
|
189
189
|
border-radius:${resolvedBorderRadius};
|
|
190
190
|
overflow:hidden;${alignmentStyle}${customCss || ""}">
|
|
191
|
-
<table border="0" cellpadding="0" cellspacing="0" width="
|
|
192
|
-
style="width
|
|
191
|
+
<table border="0" cellpadding="0" cellspacing="0" width="100%"
|
|
192
|
+
style="width:100%;height:${resolvedHeightPx}px;border-collapse:collapse;">
|
|
193
193
|
<tr>
|
|
194
194
|
<td align="${textAlignStyle}" valign="${verticalAlign}"
|
|
195
|
-
|
|
195
|
+
height="${resolvedHeightPx}"
|
|
196
196
|
style="padding:8px;vertical-align:${verticalAlign};text-align:${textAlignStyle};box-sizing:border-box;">
|
|
197
197
|
<div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">${text || ""}</div>
|
|
198
198
|
</td>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../src/utils/blocks/text.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../src/utils/blocks/text.ts"],"names":[],"mappings":"AASA,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,CAAC,EAAE,MAAM,UAkLtE"}
|
|
@@ -8,42 +8,69 @@ const common_1 = require("../common");
|
|
|
8
8
|
function convertTextBlock(blockData, cellWidthInPx) {
|
|
9
9
|
const { style, props } = blockData.data;
|
|
10
10
|
const visibilityClass = (0, common_1.getVisibilityClass)(props);
|
|
11
|
-
const { width, backgroundColor, padding, borderRadius, borderStyle, borderColor, borderWidth, textContainerBackgroundColor, textContainerPadding, fontSize, backgroundImage, whiteSpace: _whiteSpace,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
11
|
+
const { width, backgroundColor, padding, borderRadius, borderStyle, borderColor, borderWidth, textContainerBackgroundColor, textContainerPadding, fontSize, backgroundImage, whiteSpace: _whiteSpace, // strip from outer td — pre-wrap on a td preserves editor whitespace
|
|
12
|
+
...rest } = style;
|
|
13
|
+
// Detect background image or gradient (may live in backgroundImage or customCss).
|
|
14
|
+
// Same multi-client approach as Grid block: outer wrapper <td> carries the background
|
|
15
|
+
// via CSS (Gmail/New Outlook), background attribute (Yahoo), and VML (Old Outlook).
|
|
16
|
+
const bgImageStr = typeof backgroundImage === "string" ? backgroundImage : "";
|
|
17
|
+
const customCssStr = rest.customCss || "";
|
|
18
|
+
const gradientInCustomCss = !bgImageStr.includes("gradient(") && customCssStr.includes("gradient(")
|
|
19
|
+
? customCssStr.match(/(?:linear|radial|conic)-gradient\([^)]+(?:\([^)]*\)[^)]*)*\)/)?.[0] || ""
|
|
20
|
+
: "";
|
|
21
|
+
const effectiveGradient = bgImageStr.includes("gradient(")
|
|
22
|
+
? bgImageStr
|
|
23
|
+
: gradientInCustomCss;
|
|
18
24
|
const isGradient = Boolean(effectiveGradient);
|
|
19
25
|
const parsedGradient = isGradient ? (0, gradientUtils_1.parseGradient)(effectiveGradient) : null;
|
|
20
26
|
const rawBgImageUrl = !isGradient && bgImageStr
|
|
21
|
-
? bgImageStr.replace(/^url\(['"]?/,
|
|
27
|
+
? bgImageStr.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "")
|
|
22
28
|
: null;
|
|
23
29
|
const hasBgImage = Boolean(rawBgImageUrl || isGradient);
|
|
24
30
|
const fallbackBgColor = textContainerBackgroundColor ||
|
|
25
31
|
parsedGradient?.fallback ||
|
|
26
32
|
(0, gradientUtils_1.extractCssFallbackColor)(customCssStr) ||
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
33
|
+
"#ffffff";
|
|
34
|
+
// Text box decoration styles (border, background, padding) — no width
|
|
35
|
+
const textBoxStyle = {
|
|
36
|
+
backgroundColor,
|
|
37
|
+
padding,
|
|
38
|
+
borderRadius,
|
|
39
|
+
borderStyle,
|
|
40
|
+
borderColor,
|
|
41
|
+
borderWidth,
|
|
42
|
+
};
|
|
43
|
+
const convertedTextStyle = (0, buildStyles_1.buildStyles)(textBoxStyle, {
|
|
44
|
+
perChanges: [],
|
|
45
|
+
pxChanges: buildStyles_1.allPxAttributes,
|
|
46
|
+
});
|
|
47
|
+
// Strip gradient from customCss when it is hoisted to the outer bg wrapper.
|
|
30
48
|
const innerCustomCss = gradientInCustomCss
|
|
31
|
-
? customCssStr.replace(/background-image\s*:[^;]+;?/gi,
|
|
49
|
+
? customCssStr.replace(/background-image\s*:[^;]+;?/gi, "").trim()
|
|
32
50
|
: customCssStr;
|
|
33
|
-
const restForStyles = gradientInCustomCss
|
|
51
|
+
const restForStyles = gradientInCustomCss
|
|
52
|
+
? { ...rest, customCss: innerCustomCss }
|
|
53
|
+
: rest;
|
|
54
|
+
// Outer td styles: strip container background when a bg-image wrapper is present
|
|
55
|
+
// so the outer wrapper's background is not double-applied.
|
|
34
56
|
const styles = (0, buildStyles_1.buildStyles)({
|
|
35
57
|
padding: textContainerPadding,
|
|
36
58
|
backgroundColor: hasBgImage ? undefined : textContainerBackgroundColor,
|
|
37
59
|
...restForStyles,
|
|
38
|
-
}, {
|
|
60
|
+
}, {
|
|
61
|
+
perChanges: [],
|
|
62
|
+
pxChanges: buildStyles_1.allPxAttributes,
|
|
63
|
+
});
|
|
39
64
|
const sanitizedText = (props.text ?? "")
|
|
40
65
|
.replace(/<p(\s[^>]*)?>/gi, (_, attrs) => `<div${attrs || ""}>`)
|
|
41
66
|
.replace(/<\/p>/gi, "</div>");
|
|
42
67
|
const navigateToUrl = props.navigateToUrl || "";
|
|
43
68
|
const fontSizeStyle = fontSize != null ? `font-size:${fontSize}px;` : "";
|
|
69
|
+
// Email clients apply `a { color: blue }` which overrides inherited color.
|
|
70
|
+
// Inject the block color directly onto <a> tags that don't already have one.
|
|
44
71
|
const blockTextColor = rest.color;
|
|
45
72
|
const processedText = blockTextColor
|
|
46
|
-
? sanitizedText.replace(/<a(\s[^>]*)?>/gi, (match, attrs =
|
|
73
|
+
? sanitizedText.replace(/<a(\s[^>]*)?>/gi, (match, attrs = "") => {
|
|
47
74
|
if (/style\s*=\s*["'][^"']*\bcolor\s*:/i.test(attrs))
|
|
48
75
|
return match;
|
|
49
76
|
if (/\bstyle\s*=/i.test(attrs)) {
|
|
@@ -52,17 +79,24 @@ function convertTextBlock(blockData, cellWidthInPx) {
|
|
|
52
79
|
return `<a${attrs} style="color:${blockTextColor};">`;
|
|
53
80
|
})
|
|
54
81
|
: sanitizedText;
|
|
55
|
-
const colorStyle = blockTextColor ? `color:${blockTextColor};` :
|
|
82
|
+
const colorStyle = blockTextColor ? `color:${blockTextColor};` : "";
|
|
83
|
+
// Use display:block + width:100% so text fills the column naturally.
|
|
84
|
+
// display:inline-block with a pixel width (e.g. 400px) breaks narrow grid cells.
|
|
56
85
|
const convertedTextBox = `<div style="display:block; width:100%; box-sizing:border-box; ${colorStyle}${fontSizeStyle}${convertedTextStyle}">${processedText.replaceAll(/\n/g, "<br>")}</div>`;
|
|
57
|
-
const safeCellWidth = cellWidthInPx
|
|
58
|
-
|
|
59
|
-
|
|
86
|
+
const safeCellWidth = cellWidthInPx
|
|
87
|
+
? Math.min(cellWidthInPx, 600)
|
|
88
|
+
: undefined;
|
|
89
|
+
// When a bg-image wrapper is present, visibilityClass moves to the outer table.
|
|
90
|
+
const textContent = (0, outlookSupport_1.appendOutlookSupport)(convertedTextBox, styles, hasBgImage ? "" : visibilityClass, safeCellWidth);
|
|
91
|
+
const linkColorStyle = blockTextColor
|
|
92
|
+
? `color:${blockTextColor};`
|
|
93
|
+
: "color:inherit;";
|
|
60
94
|
if (hasBgImage) {
|
|
61
95
|
const msoWidth = cellWidthInPx ? Math.min(cellWidthInPx, 600) : 600;
|
|
62
96
|
const vmlFill = isGradient
|
|
63
97
|
? (() => {
|
|
64
98
|
const vmlAngle = (0, gradientUtils_1.cssAngleToVml)(parsedGradient?.angle || 180);
|
|
65
|
-
const c1 = parsedGradient?.fallback ||
|
|
99
|
+
const c1 = parsedGradient?.fallback || "#ffffff";
|
|
66
100
|
const c2 = parsedGradient?.colors[parsedGradient.colors.length - 1] || c1;
|
|
67
101
|
return `<v:fill type="gradient" color="${c1}" color2="${c2}" angle="${vmlAngle}" />`;
|
|
68
102
|
})()
|
|
@@ -74,8 +108,8 @@ function convertTextBlock(blockData, cellWidthInPx) {
|
|
|
74
108
|
<table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation"
|
|
75
109
|
style="border-collapse:collapse;width:100%;max-width:${msoWidth}px;" class="${visibilityClass}">
|
|
76
110
|
<tr>
|
|
77
|
-
<td width="100%"
|
|
78
|
-
${!isGradient && rawBgImageUrl ? `background="${rawBgImageUrl}"` :
|
|
111
|
+
<td width="100%"${(0, common_1.buildOutlookBgAttr)(fallbackBgColor)} valign="top"
|
|
112
|
+
${!isGradient && rawBgImageUrl ? `background="${rawBgImageUrl}"` : ""}
|
|
79
113
|
style="width:100%;max-width:${msoWidth}px;background-color:${fallbackBgColor};${bgCss}">
|
|
80
114
|
|
|
81
115
|
<!--[if gte mso 9]>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildStyles.d.ts","sourceRoot":"","sources":["../../src/utils/buildStyles.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,UAA8C,CAAC;AAC7E,eAAO,MAAM,sBAAsB,UAAsB,CAAC;AAC1D,eAAO,MAAM,eAAe,UAAoD,CAAC;AAEjF,eAAO,MAAM,gBAAgB,iDAAiD,CAAC;AAQ/E,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"buildStyles.d.ts","sourceRoot":"","sources":["../../src/utils/buildStyles.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,UAA8C,CAAC;AAC7E,eAAO,MAAM,sBAAsB,UAAsB,CAAC;AAC1D,eAAO,MAAM,eAAe,UAAoD,CAAC;AAEjF,eAAO,MAAM,gBAAgB,iDAAiD,CAAC;AAQ/E,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAkB7D;AACD,wBAAgB,WAAW,CACzB,KAAK,EAAE,GAAG,EACV,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,UAqFzE"}
|
|
@@ -20,12 +20,15 @@ function sanitizeFontFamily(fontFamily) {
|
|
|
20
20
|
.split(',')
|
|
21
21
|
.map(font => {
|
|
22
22
|
const trimmed = font.trim();
|
|
23
|
+
// Strip any surrounding quotes (single or double) from either end
|
|
23
24
|
const unquoted = trimmed.replace(/^["']|["']$/g, '').trim();
|
|
24
25
|
if (!unquoted)
|
|
25
26
|
return '';
|
|
27
|
+
// Generic families and single-token names need no quotes
|
|
26
28
|
if (GENERIC_FONT_FAMILIES.has(unquoted.toLowerCase()) || !/\s/.test(unquoted)) {
|
|
27
29
|
return unquoted;
|
|
28
30
|
}
|
|
31
|
+
// Multi-word font name: wrap in single quotes, escaping any embedded single quotes
|
|
29
32
|
return `'${unquoted.replace(/'/g, "\\'")}'`;
|
|
30
33
|
})
|
|
31
34
|
.filter(Boolean)
|
|
@@ -35,18 +38,28 @@ function buildStyles(style, { pxChanges, perChanges }) {
|
|
|
35
38
|
if (!style)
|
|
36
39
|
style = {};
|
|
37
40
|
const stylesObj = {};
|
|
38
|
-
const INVALID_KEYS = [
|
|
39
|
-
"columns", "cellWidths", "cellWidth", "childWidth",
|
|
40
|
-
"visibility", "hideOnMobile", "hideOnDesktop", "label", "alignment",
|
|
41
|
-
];
|
|
42
41
|
Object.entries(style).forEach(([key, value]) => {
|
|
43
42
|
if (key === "customCss")
|
|
44
43
|
return;
|
|
44
|
+
const INVALID_KEYS = [
|
|
45
|
+
"columns",
|
|
46
|
+
"cellWidths",
|
|
47
|
+
"cellWidth",
|
|
48
|
+
"childWidth",
|
|
49
|
+
"visibility",
|
|
50
|
+
"hideOnMobile",
|
|
51
|
+
"hideOnDesktop",
|
|
52
|
+
"label",
|
|
53
|
+
"alignment",
|
|
54
|
+
];
|
|
45
55
|
if (INVALID_KEYS.includes(key))
|
|
46
56
|
return;
|
|
57
|
+
// Prevent null/undefined/"" from leaking into CSS
|
|
47
58
|
if (value === undefined || value === null || value === "")
|
|
48
59
|
return;
|
|
49
|
-
|
|
60
|
+
// FIX 1 — SANITIZE padding objects
|
|
61
|
+
if ((key === "padding" || key === "buttonPadding") &&
|
|
62
|
+
typeof value === "object") {
|
|
50
63
|
const pad = value;
|
|
51
64
|
const safePad = {
|
|
52
65
|
top: Number.isFinite(pad.top) ? pad.top : 0,
|
|
@@ -59,21 +72,26 @@ function buildStyles(style, { pxChanges, perChanges }) {
|
|
|
59
72
|
if (key === "fontFamily" && typeof value === "string") {
|
|
60
73
|
value = sanitizeFontFamily((0, fontFallback_1.withFontFallback)(value));
|
|
61
74
|
}
|
|
75
|
+
// Wrap backgroundImage values in url() if not already wrapped — skip gradients
|
|
62
76
|
if (key === "backgroundImage" && typeof value === "string"
|
|
63
77
|
&& !String(value).startsWith("url(")
|
|
64
78
|
&& !String(value).toLowerCase().includes("gradient(")) {
|
|
65
79
|
value = `url('${value}')`;
|
|
66
80
|
}
|
|
81
|
+
// lineHeight: values >= 4 are pixel values; smaller values are unitless multipliers (e.g. 1.5)
|
|
67
82
|
if (key === "lineHeight" && typeof value === "number") {
|
|
68
83
|
stylesObj["line-height"] = value >= 4 ? `${value}px` : String(value);
|
|
69
84
|
return;
|
|
70
85
|
}
|
|
71
86
|
const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
87
|
+
// FIX 2 — Sanitize invalid px/per values
|
|
72
88
|
if (pxChanges.includes(key)) {
|
|
73
89
|
if (typeof value === "number") {
|
|
74
|
-
|
|
90
|
+
const rounded = Math.round(value * 100) / 100;
|
|
91
|
+
stylesObj[cssKey] = `${rounded}px`;
|
|
75
92
|
}
|
|
76
93
|
else if (typeof value === "string" && value.includes("null")) {
|
|
94
|
+
// Skip invalid styles
|
|
77
95
|
return;
|
|
78
96
|
}
|
|
79
97
|
else {
|
package/dist/utils/common.d.ts
CHANGED
|
@@ -5,4 +5,5 @@ export declare function getVisibilityClass(props?: {
|
|
|
5
5
|
hideOnMobile?: boolean;
|
|
6
6
|
}): string;
|
|
7
7
|
export declare function encodeBlockPropsAttr(props: Record<string, any>): string;
|
|
8
|
+
export declare function buildOutlookBgAttr(color: string): string;
|
|
8
9
|
//# sourceMappingURL=common.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/utils/common.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAqBvD,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAIrD,CAAC;AAGF,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE;IACzC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,UASA;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAQvE"}
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/utils/common.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAqBvD,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAIrD,CAAC;AAGF,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE;IACzC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,UASA;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAQvE;AAYD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGxD"}
|
package/dist/utils/common.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.extractVimeoId = exports.extractYouTubeId = void 0;
|
|
4
4
|
exports.getVisibilityClass = getVisibilityClass;
|
|
5
5
|
exports.encodeBlockPropsAttr = encodeBlockPropsAttr;
|
|
6
|
+
exports.buildOutlookBgAttr = buildOutlookBgAttr;
|
|
6
7
|
const extractYouTubeId = (url) => {
|
|
7
8
|
try {
|
|
8
9
|
const u = new URL(url);
|
|
@@ -53,3 +54,16 @@ function encodeBlockPropsAttr(props) {
|
|
|
53
54
|
.replace(/</g, "<")
|
|
54
55
|
.replace(/>/g, ">");
|
|
55
56
|
}
|
|
57
|
+
function toOutlookBgColor(color) {
|
|
58
|
+
if (!color || color === 'transparent')
|
|
59
|
+
return '';
|
|
60
|
+
const m = color.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*[\d.]+)?\s*\)/);
|
|
61
|
+
if (m) {
|
|
62
|
+
return '#' + [m[1], m[2], m[3]].map(n => parseInt(n).toString(16).padStart(2, '0')).join('');
|
|
63
|
+
}
|
|
64
|
+
return color;
|
|
65
|
+
}
|
|
66
|
+
function buildOutlookBgAttr(color) {
|
|
67
|
+
const normalized = toOutlookBgColor(color);
|
|
68
|
+
return normalized ? ` bgcolor="${normalized}"` : '';
|
|
69
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convertJsonToHtml.d.ts","sourceRoot":"","sources":["../../src/utils/convertJsonToHtml.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,GAAU,UAAU,GAAG,
|
|
1
|
+
{"version":3,"file":"convertJsonToHtml.d.ts","sourceRoot":"","sources":["../../src/utils/convertJsonToHtml.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,GAAU,UAAU,GAAG,oBAqNpD,CAAC"}
|
|
@@ -86,8 +86,14 @@ const convertJsonToHtml = async (jsonData) => {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
@media only screen and (max-width: 600px) {
|
|
89
|
+
body {
|
|
90
|
+
overflow-x: hidden !important;
|
|
91
|
+
}
|
|
92
|
+
|
|
89
93
|
.email-container {
|
|
90
94
|
width: 100% !important;
|
|
95
|
+
max-width: 100% !important;
|
|
96
|
+
overflow-x: hidden !important;
|
|
91
97
|
}
|
|
92
98
|
|
|
93
99
|
.stack-column,
|
|
@@ -95,6 +101,29 @@ const convertJsonToHtml = async (jsonData) => {
|
|
|
95
101
|
display: block !important;
|
|
96
102
|
width: 100% !important;
|
|
97
103
|
max-width: 100% !important;
|
|
104
|
+
/* Prevents horizontal padding from adding to 100% width once display:block is applied */
|
|
105
|
+
box-sizing: border-box !important;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Images: drop the inline pixel height so aspect ratio is preserved as width shrinks,
|
|
109
|
+
and cap width to the container. Matches the canvas ImageBlock (height:auto, max-width:100%). */
|
|
110
|
+
img {
|
|
111
|
+
height: auto !important;
|
|
112
|
+
max-width: 100% !important;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Safety net for clients that keep inline max-width:600px on nested wrappers */
|
|
116
|
+
table[style*="max-width:600px"] {
|
|
117
|
+
width: 100% !important;
|
|
118
|
+
max-width: 100% !important;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Column-gap spacer tds have no stacking behaviour — collapse them when columns stack */
|
|
122
|
+
.col-gap-spacer {
|
|
123
|
+
display: none !important;
|
|
124
|
+
width: 0 !important;
|
|
125
|
+
max-height: 0 !important;
|
|
126
|
+
overflow: hidden !important;
|
|
98
127
|
}
|
|
99
128
|
|
|
100
129
|
.hide-mobile {
|
|
@@ -128,6 +157,7 @@ const convertJsonToHtml = async (jsonData) => {
|
|
|
128
157
|
<table
|
|
129
158
|
role="presentation"
|
|
130
159
|
class="email-container"
|
|
160
|
+
align="center"
|
|
131
161
|
bgcolor="${canvasColor || '#ffffff'}"
|
|
132
162
|
cellpadding="0"
|
|
133
163
|
cellspacing="0"
|
|
@@ -138,6 +168,8 @@ const convertJsonToHtml = async (jsonData) => {
|
|
|
138
168
|
table-layout: fixed;
|
|
139
169
|
width: 100%;
|
|
140
170
|
max-width: 600px;
|
|
171
|
+
margin: 0 auto;
|
|
172
|
+
overflow: hidden;
|
|
141
173
|
background-color: ${canvasColor || '#ffffff'};
|
|
142
174
|
${textColor ? `color: ${textColor};` : ''}
|
|
143
175
|
${borderWidth ? `border: ${borderWidth}px ${borderStyle || 'solid'} ${borderColor || 'transparent'};` : ''}
|
|
@@ -145,7 +177,7 @@ const convertJsonToHtml = async (jsonData) => {
|
|
|
145
177
|
>
|
|
146
178
|
<tbody>
|
|
147
179
|
<tr>
|
|
148
|
-
<td style="padding: ${top}px ${right}px ${bottom}px ${left}px;">
|
|
180
|
+
<td style="padding: ${top}px ${right}px ${bottom}px ${left}px; max-width: 600px; overflow: hidden; box-sizing: border-box; word-break: break-word; overflow-wrap: anywhere;">
|
|
149
181
|
${blocksHtml.join("")}
|
|
150
182
|
</td>
|
|
151
183
|
</tr>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsonToHTML.d.ts","sourceRoot":"","sources":["../../src/utils/jsonToHTML.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"jsonToHTML.d.ts","sourceRoot":"","sources":["../../src/utils/jsonToHTML.ts"],"names":[],"mappings":"AAaA,wBAAsB,aAAa,CAAC,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,mBAuBvF"}
|
package/dist/utils/jsonToHTML.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.tableCommonStyle = void 0;
|
|
4
3
|
exports.convertToHtml = convertToHtml;
|
|
5
4
|
const types_1 = require("../types");
|
|
6
5
|
const text_1 = require("./blocks/text");
|
|
@@ -10,8 +9,8 @@ const grid_1 = require("./blocks/grid");
|
|
|
10
9
|
const dividers_1 = require("./blocks/dividers");
|
|
11
10
|
const video_1 = require("./blocks/video");
|
|
12
11
|
const shape_1 = require("./blocks/shape");
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
// Update: Use Gmail Mobile friendly tableCommonStyle
|
|
13
|
+
// export const tableCommonStyle = "border-collapse:collapse; table-layout:fixed; width:100%; max-width:600px; margin:0 auto;";
|
|
15
14
|
async function convertToHtml(blockData, rootData, cellWidthInPx) {
|
|
16
15
|
switch (blockData.type) {
|
|
17
16
|
case types_1.BlockType.TEXT:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare function toOutlookBgColor(color: string): string;
|
|
1
2
|
export declare function appendOutlookSupport(content: string, contentStyle: string, className?: string, msoWidth?: number): string;
|
|
2
3
|
export declare function appendOutlookForImage(content: string, outerContainerWidth: number, innerContainerWidth: number, imageUrl: string, style?: any, finalWidth?: number, finalHeight?: number): Promise<string>;
|
|
3
4
|
export declare function loadImageNaturalDimensions(imageUrl: string): Promise<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outlookSupport.d.ts","sourceRoot":"","sources":["../../src/utils/outlookSupport.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"outlookSupport.d.ts","sourceRoot":"","sources":["../../src/utils/outlookSupport.ts"],"names":[],"mappings":"AAKA,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOtD;AAOD,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,UAyClB;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,mBAAmB,EAAE,MAAM,EAC3B,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,GAAQ,EACf,UAAU,CAAC,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,mBA2FrB;AAED,wBAAsB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAO7G"}
|
|
@@ -1,26 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toOutlookBgColor = toOutlookBgColor;
|
|
3
4
|
exports.appendOutlookSupport = appendOutlookSupport;
|
|
4
5
|
exports.appendOutlookForImage = appendOutlookForImage;
|
|
5
6
|
exports.loadImageNaturalDimensions = loadImageNaturalDimensions;
|
|
6
7
|
const buildStyles_1 = require("./buildStyles");
|
|
8
|
+
const common_1 = require("./common");
|
|
9
|
+
// Converts rgba(r,g,b,a) → #rrggbb for use in HTML bgcolor attributes.
|
|
10
|
+
// Old Outlook's bgcolor attribute only accepts solid hex or named colors — rgba is silently ignored.
|
|
11
|
+
function toOutlookBgColor(color) {
|
|
12
|
+
if (!color || color === 'transparent')
|
|
13
|
+
return '';
|
|
14
|
+
const m = color.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*[\d.]+)?\s*\)/);
|
|
15
|
+
if (m) {
|
|
16
|
+
return '#' + [m[1], m[2], m[3]].map(n => parseInt(n).toString(16).padStart(2, '0')).join('');
|
|
17
|
+
}
|
|
18
|
+
return color;
|
|
19
|
+
}
|
|
20
|
+
function extractBgColor(styleStr) {
|
|
21
|
+
const m = styleStr.match(/background-color\s*:\s*([^;]+)/i);
|
|
22
|
+
return m ? m[1].trim() : '';
|
|
23
|
+
}
|
|
7
24
|
function appendOutlookSupport(content, contentStyle, className, msoWidth) {
|
|
8
25
|
const visibilityClass = className || "";
|
|
9
26
|
const shouldHideInOutlook = visibilityClass.includes("hide-desktop");
|
|
27
|
+
// Old Outlook (Word engine) often ignores CSS background-color on deeply-nested <td>
|
|
28
|
+
// elements. The bgcolor HTML attribute is the reliable fallback for solid colors.
|
|
29
|
+
// rgba() is stripped from CSS by the Word engine — convert to hex first.
|
|
30
|
+
const rawBg = extractBgColor(contentStyle);
|
|
31
|
+
const bgAttr = (0, common_1.buildOutlookBgAttr)(rawBg);
|
|
10
32
|
if (shouldHideInOutlook) {
|
|
11
33
|
return `
|
|
12
34
|
<!--[if !mso]><!-->
|
|
13
|
-
<table data-ebr-role="wrapper" role="presentation"
|
|
35
|
+
<table data-ebr-role="wrapper" role="presentation" width="100%" style="${buildStyles_1.tableCommonStyle}" class="${visibilityClass}"><tr><td${bgAttr} style="${contentStyle}">${content}</td></tr></table>
|
|
14
36
|
<!--<![endif]-->
|
|
15
37
|
`;
|
|
16
38
|
}
|
|
39
|
+
// When an explicit pixel width is provided (e.g. inside a column cell), use dual MSO/non-MSO
|
|
40
|
+
// tables. Old Outlook (Word engine) ignores max-width and can resolve width="100%" to the
|
|
41
|
+
// full email width (600px) rather than the column width, causing images/buttons to expand.
|
|
17
42
|
if (msoWidth) {
|
|
18
43
|
return `
|
|
19
44
|
<!--[if mso]>
|
|
20
|
-
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="${msoWidth}" style="border-collapse:collapse;width:${msoWidth}px;"><tr><td width="${msoWidth}" style="${contentStyle}">
|
|
45
|
+
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="${msoWidth}" style="border-collapse:collapse;width:${msoWidth}px;"><tr><td width="${msoWidth}"${bgAttr} style="${contentStyle}">
|
|
21
46
|
<![endif]-->
|
|
22
47
|
<!--[if !mso]><!-->
|
|
23
|
-
<table data-ebr-role="wrapper" role="presentation" width="100%" border="0" cellpadding="0" cellspacing="0" style="${buildStyles_1.tableCommonStyle}; max-width:600px;" class="${visibilityClass}"><tr><td width="100%" style="${contentStyle}">
|
|
48
|
+
<table data-ebr-role="wrapper" role="presentation" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" style="${buildStyles_1.tableCommonStyle}; max-width:600px;" class="${visibilityClass}"><tr><td width="100%"${bgAttr} style="${contentStyle}">
|
|
24
49
|
<!--<![endif]-->
|
|
25
50
|
${content}
|
|
26
51
|
<!--[if mso]></td></tr></table><![endif]-->
|
|
@@ -30,13 +55,15 @@ ${content}
|
|
|
30
55
|
`;
|
|
31
56
|
}
|
|
32
57
|
return `
|
|
33
|
-
<table data-ebr-role="wrapper" role="presentation" width="100%" border="0" cellpadding="0" cellspacing="0" style="${buildStyles_1.tableCommonStyle}; max-width:600px;" class="${visibilityClass}"><tr><td width="100%" style="${contentStyle}">${content}</td></tr></table>
|
|
58
|
+
<table data-ebr-role="wrapper" role="presentation" align="center" width="100%" border="0" cellpadding="0" cellspacing="0" style="${buildStyles_1.tableCommonStyle}; max-width:600px;" class="${visibilityClass}"><tr><td width="100%"${bgAttr} style="${contentStyle}">${content}</td></tr></table>
|
|
34
59
|
`;
|
|
35
60
|
}
|
|
36
61
|
async function appendOutlookForImage(content, outerContainerWidth, innerContainerWidth, imageUrl, style = {}, finalWidth, finalHeight) {
|
|
62
|
+
// OUTLOOK FIX: Use provided dimensions or calculate from image
|
|
37
63
|
let vmlWidth;
|
|
38
64
|
let vmlHeight;
|
|
39
65
|
if (finalWidth && finalHeight) {
|
|
66
|
+
// Use pre-calculated dimensions (preferred for accuracy)
|
|
40
67
|
vmlWidth = finalWidth;
|
|
41
68
|
vmlHeight = finalHeight;
|
|
42
69
|
}
|
|
@@ -60,12 +87,18 @@ async function appendOutlookForImage(content, outerContainerWidth, innerContaine
|
|
|
60
87
|
const borderColor = style?.borderColor || "transparent";
|
|
61
88
|
const borderRadius = parseInt(style?.borderRadius) || 0;
|
|
62
89
|
const useRoundRect = borderRadius > 0;
|
|
63
|
-
const arcsize = useRoundRect
|
|
90
|
+
const arcsize = useRoundRect
|
|
91
|
+
? Math.min(borderRadius / vmlHeight, 1).toFixed(2)
|
|
92
|
+
: "";
|
|
64
93
|
const borderAttributes = borderWidth > 0
|
|
65
94
|
? `strokeweight="${borderWidth}px" strokecolor="${borderColor}"`
|
|
66
95
|
: `stroked="false"`;
|
|
96
|
+
// OUTLOOK FIX: For Outlook 2019+ (version 2512), VML type="frame" causes stretching
|
|
97
|
+
// Solution: Use simple IMG tag with fixed dimensions for Outlook, only use VML for border radius
|
|
67
98
|
let outlookImage;
|
|
68
99
|
if (useRoundRect && borderRadius > 0) {
|
|
100
|
+
// Use VML for border radius - wrap in table to constrain width for Old Outlook (Word engine)
|
|
101
|
+
// Use aspect="atmost" to prevent image from stretching beyond its bounds
|
|
69
102
|
outlookImage = `<!--[if mso]>
|
|
70
103
|
<table border="0" cellpadding="0" cellspacing="0" width="${vmlWidth}" style="width:${vmlWidth}px;">
|
|
71
104
|
<tr>
|
|
@@ -84,7 +117,11 @@ async function appendOutlookForImage(content, outerContainerWidth, innerContaine
|
|
|
84
117
|
<![endif]-->`;
|
|
85
118
|
}
|
|
86
119
|
else {
|
|
87
|
-
|
|
120
|
+
// For images without border radius, wrap in a table with explicit width for Old Outlook (Word engine)
|
|
121
|
+
// This prevents stretching/overflow in Outlook 2007-2019 and Outlook Classic
|
|
122
|
+
const borderStyleAttr = borderWidth > 0
|
|
123
|
+
? `border: ${borderWidth}px solid ${borderColor};`
|
|
124
|
+
: '';
|
|
88
125
|
outlookImage = `<!--[if mso]>
|
|
89
126
|
<table border="0" cellpadding="0" cellspacing="0" width="${vmlWidth}" style="width:${vmlWidth}px;">
|
|
90
127
|
<tr>
|