email-builder-utils 1.1.49 → 1.1.52
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 +89 -39
- package/dist/utils/blocks/dividers.js +30 -30
- package/dist/utils/blocks/grid.d.ts.map +1 -1
- package/dist/utils/blocks/grid.js +192 -106
- package/dist/utils/blocks/image.d.ts.map +1 -1
- package/dist/utils/blocks/image.js +42 -4
- package/dist/utils/blocks/shape.js +89 -89
- package/dist/utils/blocks/text.d.ts.map +1 -1
- package/dist/utils/blocks/text.js +80 -49
- package/dist/utils/blocks/video.js +56 -56
- 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 +183 -153
- package/dist/utils/outlookSupport.d.ts.map +1 -1
- package/dist/utils/outlookSupport.js +81 -57
- package/package.json +34 -34
|
@@ -33,23 +33,23 @@ function buildVMLShape({ shape, widthPx, heightPx, imageUrl, backgroundColor, bo
|
|
|
33
33
|
const hAlign = hAlignMap[textAlign] || "center";
|
|
34
34
|
const safeFontSize = Math.max(Math.round(textSize), 10);
|
|
35
35
|
const textboxMarkup = text && !msoHasBakedText
|
|
36
|
-
? `<v:textbox inset="6pt,6pt,6pt,6pt" style="mso-fit-shape-to-text:false;">
|
|
37
|
-
<div style="display:table;width:100%;height:100%;">
|
|
38
|
-
<div style="display:table-cell;vertical-align:${vAlign};text-align:${hAlign};padding:0 6px;">
|
|
39
|
-
<div style="color:${textColor};font-family:Arial, sans-serif;font-size:${safeFontSize}px;line-height:1.3;word-wrap:break-word;">
|
|
40
|
-
${text}
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
36
|
+
? `<v:textbox inset="6pt,6pt,6pt,6pt" style="mso-fit-shape-to-text:false;">
|
|
37
|
+
<div style="display:table;width:100%;height:100%;">
|
|
38
|
+
<div style="display:table-cell;vertical-align:${vAlign};text-align:${hAlign};padding:0 6px;">
|
|
39
|
+
<div style="color:${textColor};font-family:Arial, sans-serif;font-size:${safeFontSize}px;line-height:1.3;word-wrap:break-word;">
|
|
40
|
+
${text}
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
44
|
</v:textbox>`
|
|
45
45
|
: `<v:textbox inset="0,0,0,0"><div style="display:none;">.</div></v:textbox>`;
|
|
46
|
-
return `
|
|
47
|
-
<v:${tag} xmlns:v="urn:schemas-microsoft-com:vml"
|
|
48
|
-
style="width:${widthPx}px;height:${heightPx}px;display:inline-block;"
|
|
49
|
-
${borderAttrs}
|
|
50
|
-
fill="true" fillcolor="${fillColor}"${extraAttr}>
|
|
51
|
-
${fillMarkup}
|
|
52
|
-
${textboxMarkup}
|
|
46
|
+
return `
|
|
47
|
+
<v:${tag} xmlns:v="urn:schemas-microsoft-com:vml"
|
|
48
|
+
style="width:${widthPx}px;height:${heightPx}px;display:inline-block;"
|
|
49
|
+
${borderAttrs}
|
|
50
|
+
fill="true" fillcolor="${fillColor}"${extraAttr}>
|
|
51
|
+
${fillMarkup}
|
|
52
|
+
${textboxMarkup}
|
|
53
53
|
</v:${tag}>`;
|
|
54
54
|
}
|
|
55
55
|
function appendOutlookForShape(content, outerContainerWidth, innerContainerWidth, opts, visibilityClass) {
|
|
@@ -70,28 +70,28 @@ function appendOutlookForShape(content, outerContainerWidth, innerContainerWidth
|
|
|
70
70
|
const valign = opts.verticalAlign || "middle";
|
|
71
71
|
const shouldHideInOutlook = visibilityClass.includes("hide-desktop");
|
|
72
72
|
if (shouldHideInOutlook) {
|
|
73
|
-
return `<!--[if !mso]><!-->
|
|
74
|
-
<table align="${align}" border="0" cellpadding="0" cellspacing="0"
|
|
75
|
-
style="width:${widthPx}px;height:${heightPx}px;border-collapse:collapse;" class="${visibilityClass}">
|
|
76
|
-
<tr>
|
|
77
|
-
<td valign="${valign}"
|
|
78
|
-
style="padding:${pad.top || 0}px ${pad.right || 0}px ${pad.bottom || 0}px ${pad.left || 0}px;">
|
|
79
|
-
${vml}
|
|
80
|
-
</td>
|
|
81
|
-
</tr>
|
|
82
|
-
</table>
|
|
73
|
+
return `<!--[if !mso]><!-->
|
|
74
|
+
<table align="${align}" border="0" cellpadding="0" cellspacing="0"
|
|
75
|
+
style="width:${widthPx}px;height:${heightPx}px;border-collapse:collapse;" class="${visibilityClass}">
|
|
76
|
+
<tr>
|
|
77
|
+
<td valign="${valign}"
|
|
78
|
+
style="padding:${pad.top || 0}px ${pad.right || 0}px ${pad.bottom || 0}px ${pad.left || 0}px;">
|
|
79
|
+
${vml}
|
|
80
|
+
</td>
|
|
81
|
+
</tr>
|
|
82
|
+
</table>
|
|
83
83
|
<!--<![endif]-->`;
|
|
84
84
|
}
|
|
85
|
-
return `<!--[if mso]>
|
|
86
|
-
<table align="${align}" border="0" cellpadding="0" cellspacing="0"
|
|
87
|
-
style="width:${widthPx}px;height:${heightPx}px;border-collapse:collapse;" class="${visibilityClass}">
|
|
88
|
-
<tr>
|
|
89
|
-
<td valign="${valign}"
|
|
90
|
-
style="padding:${pad.top || 0}px ${pad.right || 0}px ${pad.bottom || 0}px ${pad.left || 0}px;">
|
|
91
|
-
${vml}
|
|
92
|
-
</td>
|
|
93
|
-
</tr>
|
|
94
|
-
</table>
|
|
85
|
+
return `<!--[if mso]>
|
|
86
|
+
<table align="${align}" border="0" cellpadding="0" cellspacing="0"
|
|
87
|
+
style="width:${widthPx}px;height:${heightPx}px;border-collapse:collapse;" class="${visibilityClass}">
|
|
88
|
+
<tr>
|
|
89
|
+
<td valign="${valign}"
|
|
90
|
+
style="padding:${pad.top || 0}px ${pad.right || 0}px ${pad.bottom || 0}px ${pad.left || 0}px;">
|
|
91
|
+
${vml}
|
|
92
|
+
</td>
|
|
93
|
+
</tr>
|
|
94
|
+
</table>
|
|
95
95
|
<![endif]-->`;
|
|
96
96
|
}
|
|
97
97
|
async function convertShapeBlock(blockData) {
|
|
@@ -146,58 +146,58 @@ async function convertShapeBlock(blockData) {
|
|
|
146
146
|
let nonMsoContent = "";
|
|
147
147
|
// --- Case 1: Image + Text ---
|
|
148
148
|
if (imageUrl && text) {
|
|
149
|
-
nonMsoContent = `
|
|
150
|
-
<div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
|
|
151
|
-
border:${borderWidth}px ${borderStyle} ${borderColor};
|
|
152
|
-
border-radius:${resolvedBorderRadius};
|
|
153
|
-
background-color:${finalBackgroundColor};
|
|
154
|
-
background-image:url('${imageUrl}');
|
|
155
|
-
background-position:center center;
|
|
156
|
-
background-size:cover;
|
|
157
|
-
background-repeat:no-repeat;
|
|
158
|
-
overflow:hidden;${alignmentStyle}${customCss || ""}">
|
|
159
|
-
<table border="0" cellpadding="0" cellspacing="0" width="
|
|
160
|
-
style="width
|
|
161
|
-
<tr>
|
|
162
|
-
<td align="${textAlignStyle}" valign="${verticalAlign}"
|
|
163
|
-
|
|
164
|
-
style="padding:6px;vertical-align:${verticalAlign};text-align:${textAlignStyle};overflow:hidden;box-sizing:border-box;">
|
|
165
|
-
<div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">${text}</div>
|
|
166
|
-
</td>
|
|
167
|
-
</tr>
|
|
168
|
-
</table>
|
|
149
|
+
nonMsoContent = `
|
|
150
|
+
<div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;max-width:100%;box-sizing:border-box;
|
|
151
|
+
border:${borderWidth}px ${borderStyle} ${borderColor};
|
|
152
|
+
border-radius:${resolvedBorderRadius};
|
|
153
|
+
background-color:${finalBackgroundColor};
|
|
154
|
+
background-image:url('${imageUrl}');
|
|
155
|
+
background-position:center center;
|
|
156
|
+
background-size:cover;
|
|
157
|
+
background-repeat:no-repeat;
|
|
158
|
+
overflow:hidden;${alignmentStyle}${customCss || ""}">
|
|
159
|
+
<table border="0" cellpadding="0" cellspacing="0" width="100%"
|
|
160
|
+
style="width:100%;height:${resolvedHeightPx}px;border-collapse:collapse;">
|
|
161
|
+
<tr>
|
|
162
|
+
<td align="${textAlignStyle}" valign="${verticalAlign}"
|
|
163
|
+
height="${resolvedHeightPx}"
|
|
164
|
+
style="padding:6px;vertical-align:${verticalAlign};text-align:${textAlignStyle};overflow:hidden;box-sizing:border-box;">
|
|
165
|
+
<div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">${text}</div>
|
|
166
|
+
</td>
|
|
167
|
+
</tr>
|
|
168
|
+
</table>
|
|
169
169
|
</div>`;
|
|
170
170
|
}
|
|
171
171
|
// --- Case 2: Image only ---
|
|
172
172
|
else if (imageUrl) {
|
|
173
|
-
nonMsoContent = `
|
|
174
|
-
<div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
|
|
175
|
-
border:${borderWidth}px ${borderStyle} ${borderColor};
|
|
176
|
-
border-radius:${resolvedBorderRadius};
|
|
177
|
-
overflow:hidden;${alignmentStyle}${customCss || ""}">
|
|
178
|
-
<img src="${imageUrl}" alt="${text || "shape image"}"
|
|
179
|
-
width="${resolvedWidthPx}" height="${resolvedHeightPx}"
|
|
180
|
-
style="width:100%;height:100%;object-fit:cover;border-radius:${resolvedBorderRadius};display:block;" />
|
|
173
|
+
nonMsoContent = `
|
|
174
|
+
<div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;max-width:100%;box-sizing:border-box;
|
|
175
|
+
border:${borderWidth}px ${borderStyle} ${borderColor};
|
|
176
|
+
border-radius:${resolvedBorderRadius};
|
|
177
|
+
overflow:hidden;${alignmentStyle}${customCss || ""}">
|
|
178
|
+
<img src="${imageUrl}" alt="${text || "shape image"}"
|
|
179
|
+
width="${resolvedWidthPx}" height="${resolvedHeightPx}"
|
|
180
|
+
style="width:100%;height:100%;object-fit:cover;border-radius:${resolvedBorderRadius};display:block;" />
|
|
181
181
|
</div>`;
|
|
182
182
|
}
|
|
183
183
|
// --- Case 3: Text only ---
|
|
184
184
|
else {
|
|
185
|
-
nonMsoContent = `
|
|
186
|
-
<div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;
|
|
187
|
-
background-color:${finalBackgroundColor};
|
|
188
|
-
border:${borderWidth}px ${borderStyle} ${borderColor};
|
|
189
|
-
border-radius:${resolvedBorderRadius};
|
|
190
|
-
overflow:hidden;${alignmentStyle}${customCss || ""}">
|
|
191
|
-
<table border="0" cellpadding="0" cellspacing="0" width="
|
|
192
|
-
style="width
|
|
193
|
-
<tr>
|
|
194
|
-
<td align="${textAlignStyle}" valign="${verticalAlign}"
|
|
195
|
-
|
|
196
|
-
style="padding:8px;vertical-align:${verticalAlign};text-align:${textAlignStyle};box-sizing:border-box;">
|
|
197
|
-
<div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">${text || ""}</div>
|
|
198
|
-
</td>
|
|
199
|
-
</tr>
|
|
200
|
-
</table>
|
|
185
|
+
nonMsoContent = `
|
|
186
|
+
<div style="display:inline-block;width:${resolvedWidthPx}px;height:${resolvedHeightPx}px;max-width:100%;box-sizing:border-box;
|
|
187
|
+
background-color:${finalBackgroundColor};
|
|
188
|
+
border:${borderWidth}px ${borderStyle} ${borderColor};
|
|
189
|
+
border-radius:${resolvedBorderRadius};
|
|
190
|
+
overflow:hidden;${alignmentStyle}${customCss || ""}">
|
|
191
|
+
<table border="0" cellpadding="0" cellspacing="0" width="100%"
|
|
192
|
+
style="width:100%;height:${resolvedHeightPx}px;border-collapse:collapse;">
|
|
193
|
+
<tr>
|
|
194
|
+
<td align="${textAlignStyle}" valign="${verticalAlign}"
|
|
195
|
+
height="${resolvedHeightPx}"
|
|
196
|
+
style="padding:8px;vertical-align:${verticalAlign};text-align:${textAlignStyle};box-sizing:border-box;">
|
|
197
|
+
<div style="${textSizeStyle}text-align:${textAlignStyle};max-width:90%;overflow:hidden;">${text || ""}</div>
|
|
198
|
+
</td>
|
|
199
|
+
</tr>
|
|
200
|
+
</table>
|
|
201
201
|
</div>`;
|
|
202
202
|
}
|
|
203
203
|
// Outlook (VML) fallback
|
|
@@ -242,15 +242,15 @@ async function convertShapeBlock(blockData) {
|
|
|
242
242
|
hideOnMobile: Boolean(props.hideOnMobile),
|
|
243
243
|
});
|
|
244
244
|
// Combine into table wrapper
|
|
245
|
-
return `
|
|
246
|
-
<table width="100%" style="border-collapse:collapse;table-layout:fixed;max-width:600px;" class="${visibilityClass}" data-block-type="shape" data-block-props="${shapeBlockProps}">
|
|
247
|
-
<tr>
|
|
248
|
-
<td style="padding:${padding.top || 0}px ${padding.right || 0}px ${padding.bottom || 0}px ${padding.left || 0}px;text-align:${alignment};">
|
|
249
|
-
${outlookContent}
|
|
250
|
-
<!--[if !mso]><!-->
|
|
251
|
-
${nonMsoContent}
|
|
252
|
-
<!--<![endif]-->
|
|
253
|
-
</td>
|
|
254
|
-
</tr>
|
|
245
|
+
return `
|
|
246
|
+
<table width="100%" style="border-collapse:collapse;table-layout:fixed;max-width:600px;" class="${visibilityClass}" data-block-type="shape" data-block-props="${shapeBlockProps}">
|
|
247
|
+
<tr>
|
|
248
|
+
<td style="padding:${padding.top || 0}px ${padding.right || 0}px ${padding.bottom || 0}px ${padding.left || 0}px;text-align:${alignment};">
|
|
249
|
+
${outlookContent}
|
|
250
|
+
<!--[if !mso]><!-->
|
|
251
|
+
${nonMsoContent}
|
|
252
|
+
<!--<![endif]-->
|
|
253
|
+
</td>
|
|
254
|
+
</tr>
|
|
255
255
|
</table>`;
|
|
256
256
|
}
|
|
@@ -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,
|
|
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,52 +79,56 @@ 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
|
})()
|
|
69
103
|
: `<v:fill type="frame" src="${rawBgImageUrl}" color="${fallbackBgColor}" />`;
|
|
70
|
-
const bgPosition = backgroundPosition ?? 'center center';
|
|
71
|
-
const bgSize = backgroundSize ?? 'cover';
|
|
72
|
-
const bgRepeat = backgroundRepeat ?? 'no-repeat';
|
|
73
104
|
const bgCss = isGradient
|
|
74
105
|
? `background:${effectiveGradient};`
|
|
75
|
-
: `background-image:url('${rawBgImageUrl}'); background-position
|
|
76
|
-
const wrappedContent = `
|
|
77
|
-
<table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation"
|
|
78
|
-
style="border-collapse:collapse;width:100%;max-width:${msoWidth}px;" class="${visibilityClass}">
|
|
79
|
-
<tr>
|
|
80
|
-
<td width="100%"
|
|
81
|
-
${!isGradient && rawBgImageUrl ? `background="${rawBgImageUrl}"` :
|
|
82
|
-
style="width:100%;max-width:${msoWidth}px;background-color:${fallbackBgColor};${bgCss}">
|
|
83
|
-
|
|
84
|
-
<!--[if gte mso 9]>
|
|
85
|
-
<v:rect xmlns:v="urn:schemas-microsoft-com:vml"
|
|
86
|
-
fill="true" stroke="false"
|
|
87
|
-
style="width:${msoWidth}px;">
|
|
88
|
-
${vmlFill}
|
|
89
|
-
<v:textbox inset="0,0,0,0">
|
|
90
|
-
<![endif]-->
|
|
91
|
-
|
|
92
|
-
${textContent}
|
|
93
|
-
|
|
94
|
-
<!--[if gte mso 9]>
|
|
95
|
-
</v:textbox>
|
|
96
|
-
</v:rect>
|
|
97
|
-
<![endif]-->
|
|
98
|
-
|
|
99
|
-
</td>
|
|
100
|
-
</tr>
|
|
106
|
+
: `background-image:url('${rawBgImageUrl}'); background-position:center center; background-size:cover; background-repeat:no-repeat;`;
|
|
107
|
+
const wrappedContent = `
|
|
108
|
+
<table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation"
|
|
109
|
+
style="border-collapse:collapse;width:100%;max-width:${msoWidth}px;" class="${visibilityClass}">
|
|
110
|
+
<tr>
|
|
111
|
+
<td width="100%"${(0, common_1.buildOutlookBgAttr)(fallbackBgColor)} valign="top"
|
|
112
|
+
${!isGradient && rawBgImageUrl ? `background="${rawBgImageUrl}"` : ""}
|
|
113
|
+
style="width:100%;max-width:${msoWidth}px;background-color:${fallbackBgColor};${bgCss}">
|
|
114
|
+
|
|
115
|
+
<!--[if gte mso 9]>
|
|
116
|
+
<v:rect xmlns:v="urn:schemas-microsoft-com:vml"
|
|
117
|
+
fill="true" stroke="false"
|
|
118
|
+
style="width:${msoWidth}px;">
|
|
119
|
+
${vmlFill}
|
|
120
|
+
<v:textbox inset="0,0,0,0">
|
|
121
|
+
<![endif]-->
|
|
122
|
+
|
|
123
|
+
${textContent}
|
|
124
|
+
|
|
125
|
+
<!--[if gte mso 9]>
|
|
126
|
+
</v:textbox>
|
|
127
|
+
</v:rect>
|
|
128
|
+
<![endif]-->
|
|
129
|
+
|
|
130
|
+
</td>
|
|
131
|
+
</tr>
|
|
101
132
|
</table>`;
|
|
102
133
|
return navigateToUrl
|
|
103
134
|
? `<a href="${navigateToUrl}" rel="noreferrer noopener" style="${linkColorStyle}text-decoration:none;cursor:pointer;">${wrappedContent}</a>`
|
|
@@ -88,64 +88,64 @@ async function convertVideoBlock(blockData, cellWidthInPx) {
|
|
|
88
88
|
// min-height fallback for clients that ignore the spacer GIF (e.g. some webmail).
|
|
89
89
|
const minHeight = Math.round(thumbnailW * 0.3);
|
|
90
90
|
// VML block — shown only in Outlook (Word engine supports VML, not standard HTML).
|
|
91
|
-
const outlookContent = hideOnDesktop ? '' : `<!--[if vml]>
|
|
92
|
-
<v:group xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word"
|
|
93
|
-
coordsize="${thumbnailW},${thumbnailH}" coordorigin="0,0"
|
|
94
|
-
href="${videoLink}"
|
|
95
|
-
style="width:${thumbnailW}px;height:${thumbnailH}px;">
|
|
96
|
-
<v:rect fill="t" stroked="f" style="position:absolute;width:${thumbnailW};height:${thumbnailH};">
|
|
97
|
-
<v:fill src="${resolvedThumbnail}" type="frame"/>
|
|
98
|
-
</v:rect>
|
|
99
|
-
<v:oval fill="t" strokecolor="#ffffff" strokeweight="3px"
|
|
100
|
-
style="position:absolute;left:${ovalLeft};top:${ovalTop};width:${ovalSize};height:${ovalSize}">
|
|
101
|
-
<v:fill color="#ffffff" opacity="100%" />
|
|
102
|
-
</v:oval>
|
|
103
|
-
<v:shape coordsize="24,32" path="m,l,32,24,16,xe" fillcolor="#000000" stroked="f"
|
|
104
|
-
style="position:absolute;left:${triLeft};top:${triTop};width:${triW};height:${triH};" />
|
|
105
|
-
</v:group>
|
|
91
|
+
const outlookContent = hideOnDesktop ? '' : `<!--[if vml]>
|
|
92
|
+
<v:group xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word"
|
|
93
|
+
coordsize="${thumbnailW},${thumbnailH}" coordorigin="0,0"
|
|
94
|
+
href="${videoLink}"
|
|
95
|
+
style="width:${thumbnailW}px;height:${thumbnailH}px;">
|
|
96
|
+
<v:rect fill="t" stroked="f" style="position:absolute;width:${thumbnailW};height:${thumbnailH};">
|
|
97
|
+
<v:fill src="${resolvedThumbnail}" type="frame"/>
|
|
98
|
+
</v:rect>
|
|
99
|
+
<v:oval fill="t" strokecolor="#ffffff" strokeweight="3px"
|
|
100
|
+
style="position:absolute;left:${ovalLeft};top:${ovalTop};width:${ovalSize};height:${ovalSize}">
|
|
101
|
+
<v:fill color="#ffffff" opacity="100%" />
|
|
102
|
+
</v:oval>
|
|
103
|
+
<v:shape coordsize="24,32" path="m,l,32,24,16,xe" fillcolor="#000000" stroked="f"
|
|
104
|
+
style="position:absolute;left:${triLeft};top:${triTop};width:${triW};height:${triH};" />
|
|
105
|
+
</v:group>
|
|
106
106
|
<![endif]-->`;
|
|
107
107
|
// Non-VML block — shown in all clients except Outlook.
|
|
108
|
-
const nonMsoContent = `<!--[if !vml]><!-->
|
|
109
|
-
<a href="${videoLink}" target="_blank"
|
|
110
|
-
style="display:block; text-decoration:none; background-image:url('${resolvedThumbnail}'); background-size:cover; background-position:center; ${borderCss}${radiusCss}">
|
|
111
|
-
<table cellpadding="0" cellspacing="0" border="0" width="100%"
|
|
112
|
-
background="${resolvedThumbnail}"
|
|
113
|
-
role="presentation"
|
|
114
|
-
style="background-image:url('${resolvedThumbnail}'); background-size:cover; background-position:center center; background-repeat:no-repeat; min-height:${minHeight}px;">
|
|
115
|
-
<tr>
|
|
116
|
-
<td width="25%" style="line-height:0; font-size:0; padding:0;">
|
|
117
|
-
<img src="${SPACER_GIF_URL}" width="100%" border="0" alt=""
|
|
118
|
-
style="display:block; height:auto; opacity:0; visibility:hidden;" />
|
|
119
|
-
</td>
|
|
120
|
-
<td width="50%" align="center" valign="middle"
|
|
121
|
-
style="text-align:center; vertical-align:middle; padding:0;">
|
|
122
|
-
<img src="${PLAY_ICON_URL}" width="${playIconW}" height="${playIconH}" alt="Play"
|
|
123
|
-
style="display:block; width:${playIconW}px; height:${playIconH}px; border:0; margin:0 auto;" />
|
|
124
|
-
</td>
|
|
125
|
-
<td width="25%" style="padding:0;"> </td>
|
|
126
|
-
</tr>
|
|
127
|
-
</table>
|
|
128
|
-
</a>
|
|
108
|
+
const nonMsoContent = `<!--[if !vml]><!-->
|
|
109
|
+
<a href="${videoLink}" target="_blank"
|
|
110
|
+
style="display:block; text-decoration:none; background-image:url('${resolvedThumbnail}'); background-size:cover; background-position:center; ${borderCss}${radiusCss}">
|
|
111
|
+
<table cellpadding="0" cellspacing="0" border="0" width="100%"
|
|
112
|
+
background="${resolvedThumbnail}"
|
|
113
|
+
role="presentation"
|
|
114
|
+
style="background-image:url('${resolvedThumbnail}'); background-size:cover; background-position:center center; background-repeat:no-repeat; min-height:${minHeight}px;">
|
|
115
|
+
<tr>
|
|
116
|
+
<td width="25%" style="line-height:0; font-size:0; padding:0;">
|
|
117
|
+
<img src="${SPACER_GIF_URL}" width="100%" border="0" alt=""
|
|
118
|
+
style="display:block; height:auto; opacity:0; visibility:hidden;" />
|
|
119
|
+
</td>
|
|
120
|
+
<td width="50%" align="center" valign="middle"
|
|
121
|
+
style="text-align:center; vertical-align:middle; padding:0;">
|
|
122
|
+
<img src="${PLAY_ICON_URL}" width="${playIconW}" height="${playIconH}" alt="Play"
|
|
123
|
+
style="display:block; width:${playIconW}px; height:${playIconH}px; border:0; margin:0 auto;" />
|
|
124
|
+
</td>
|
|
125
|
+
<td width="25%" style="padding:0;"> </td>
|
|
126
|
+
</tr>
|
|
127
|
+
</table>
|
|
128
|
+
</a>
|
|
129
129
|
<!--<![endif]-->`;
|
|
130
|
-
return `
|
|
131
|
-
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
|
|
132
|
-
style="border-collapse:collapse; max-width:600px; margin:0; padding:0;"
|
|
133
|
-
class="${visibilityClass}" data-block-type="video"
|
|
134
|
-
data-video-url="${videoUrl || ''}" data-youtube-url="${youtubeVideoUrl || ''}"
|
|
135
|
-
data-thumbnail-url="${thumbnailUrl || ''}" data-alt-text="${altText || ''}"
|
|
136
|
-
data-width="${typeof style?.width === 'number' ? style.width : 100}">
|
|
137
|
-
<tr>
|
|
138
|
-
<td align="${align}" style="${outerContainerStyles}">
|
|
139
|
-
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
|
140
|
-
align="${align}"
|
|
141
|
-
style="border-collapse:collapse; max-width:${thumbnailW}px; width:${percentWidth};">
|
|
142
|
-
<tr>
|
|
143
|
-
<td align="${align}" style="padding:0;">
|
|
144
|
-
${outlookContent}${nonMsoContent}
|
|
145
|
-
</td>
|
|
146
|
-
</tr>
|
|
147
|
-
</table>
|
|
148
|
-
</td>
|
|
149
|
-
</tr>
|
|
130
|
+
return `
|
|
131
|
+
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
|
|
132
|
+
style="border-collapse:collapse; max-width:600px; margin:0; padding:0;"
|
|
133
|
+
class="${visibilityClass}" data-block-type="video"
|
|
134
|
+
data-video-url="${videoUrl || ''}" data-youtube-url="${youtubeVideoUrl || ''}"
|
|
135
|
+
data-thumbnail-url="${thumbnailUrl || ''}" data-alt-text="${altText || ''}"
|
|
136
|
+
data-width="${typeof style?.width === 'number' ? style.width : 100}">
|
|
137
|
+
<tr>
|
|
138
|
+
<td align="${align}" style="${outerContainerStyles}">
|
|
139
|
+
<table border="0" cellpadding="0" cellspacing="0" role="presentation"
|
|
140
|
+
align="${align}"
|
|
141
|
+
style="border-collapse:collapse; max-width:${thumbnailW}px; width:${percentWidth};">
|
|
142
|
+
<tr>
|
|
143
|
+
<td align="${align}" style="padding:0;">
|
|
144
|
+
${outlookContent}${nonMsoContent}
|
|
145
|
+
</td>
|
|
146
|
+
</tr>
|
|
147
|
+
</table>
|
|
148
|
+
</td>
|
|
149
|
+
</tr>
|
|
150
150
|
</table>`;
|
|
151
151
|
}
|
|
@@ -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"}
|