email-builder-utils 1.1.52 → 1.1.54

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.
@@ -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;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>
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;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;" />
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;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>
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":"AASA,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,CAAC,EAAE,MAAM,UAkLtE"}
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,UAoKtE"}
@@ -13,24 +13,22 @@ function convertTextBlock(blockData, cellWidthInPx) {
13
13
  // Detect background image or gradient (may live in backgroundImage or customCss).
14
14
  // Same multi-client approach as Grid block: outer wrapper <td> carries the background
15
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;
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(') ? bgImageStr : gradientInCustomCss;
24
22
  const isGradient = Boolean(effectiveGradient);
25
23
  const parsedGradient = isGradient ? (0, gradientUtils_1.parseGradient)(effectiveGradient) : null;
26
24
  const rawBgImageUrl = !isGradient && bgImageStr
27
- ? bgImageStr.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "")
25
+ ? bgImageStr.replace(/^url\(['"]?/, '').replace(/['"]?\)$/, '')
28
26
  : null;
29
27
  const hasBgImage = Boolean(rawBgImageUrl || isGradient);
30
28
  const fallbackBgColor = textContainerBackgroundColor ||
31
29
  parsedGradient?.fallback ||
32
30
  (0, gradientUtils_1.extractCssFallbackColor)(customCssStr) ||
33
- "#ffffff";
31
+ '#ffffff';
34
32
  // Text box decoration styles (border, background, padding) — no width
35
33
  const textBoxStyle = {
36
34
  backgroundColor,
@@ -46,11 +44,9 @@ function convertTextBlock(blockData, cellWidthInPx) {
46
44
  });
47
45
  // Strip gradient from customCss when it is hoisted to the outer bg wrapper.
48
46
  const innerCustomCss = gradientInCustomCss
49
- ? customCssStr.replace(/background-image\s*:[^;]+;?/gi, "").trim()
47
+ ? customCssStr.replace(/background-image\s*:[^;]+;?/gi, '').trim()
50
48
  : customCssStr;
51
- const restForStyles = gradientInCustomCss
52
- ? { ...rest, customCss: innerCustomCss }
53
- : rest;
49
+ const restForStyles = gradientInCustomCss ? { ...rest, customCss: innerCustomCss } : rest;
54
50
  // Outer td styles: strip container background when a bg-image wrapper is present
55
51
  // so the outer wrapper's background is not double-applied.
56
52
  const styles = (0, buildStyles_1.buildStyles)({
@@ -62,41 +58,37 @@ function convertTextBlock(blockData, cellWidthInPx) {
62
58
  pxChanges: buildStyles_1.allPxAttributes,
63
59
  });
64
60
  const sanitizedText = (props.text ?? "")
65
- .replace(/<p(\s[^>]*)?>/gi, (_, attrs) => `<div${attrs || ""}>`)
61
+ .replace(/<p(\s[^>]*)?>/gi, (_, attrs = "") => `<div${attrs || ""}>`)
66
62
  .replace(/<\/p>/gi, "</div>");
67
63
  const navigateToUrl = props.navigateToUrl || "";
68
64
  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.
65
+ // Email clients apply `a { color: blue; text-decoration: underline }` which
66
+ // overrides inherited styles. Inject block color (if available) and reset
67
+ // text-decoration so inline links match the surrounding text appearance.
71
68
  const blockTextColor = rest.color;
72
- const processedText = blockTextColor
73
- ? sanitizedText.replace(/<a(\s[^>]*)?>/gi, (match, attrs = "") => {
74
- if (/style\s*=\s*["'][^"']*\bcolor\s*:/i.test(attrs))
75
- return match;
76
- if (/\bstyle\s*=/i.test(attrs)) {
77
- return `<a${attrs.replace(/(\bstyle\s*=\s*["'])/, `$1color:${blockTextColor};`)}>`;
78
- }
79
- return `<a${attrs} style="color:${blockTextColor};">`;
80
- })
81
- : sanitizedText;
82
- const colorStyle = blockTextColor ? `color:${blockTextColor};` : "";
69
+ const processedText = sanitizedText.replace(/<a(\s[^>]*)?>/gi, (_match, attrs = '') => {
70
+ const hasExistingColor = /style\s*=\s*["'][^"']*\bcolor\s*:/i.test(attrs);
71
+ const colorPart = (!hasExistingColor && blockTextColor) ? `color:${blockTextColor};` : '';
72
+ const injected = `${colorPart}text-decoration:none;`;
73
+ if (/\bstyle\s*=/i.test(attrs)) {
74
+ return `<a${attrs.replace(/(\bstyle\s*=\s*["'])/, `$1${injected}`)}>`;
75
+ }
76
+ return `<a${attrs} style="${injected}">`;
77
+ });
78
+ const colorStyle = blockTextColor ? `color:${blockTextColor};` : '';
83
79
  // Use display:block + width:100% so text fills the column naturally.
84
80
  // display:inline-block with a pixel width (e.g. 400px) breaks narrow grid cells.
85
81
  const convertedTextBox = `<div style="display:block; width:100%; box-sizing:border-box; ${colorStyle}${fontSizeStyle}${convertedTextStyle}">${processedText.replaceAll(/\n/g, "<br>")}</div>`;
86
- const safeCellWidth = cellWidthInPx
87
- ? Math.min(cellWidthInPx, 600)
88
- : undefined;
82
+ const safeCellWidth = cellWidthInPx ? Math.min(cellWidthInPx, 600) : undefined;
89
83
  // 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;";
84
+ const textContent = (0, outlookSupport_1.appendOutlookSupport)(convertedTextBox, styles, hasBgImage ? '' : visibilityClass, safeCellWidth);
85
+ const linkColorStyle = blockTextColor ? `color:${blockTextColor};` : 'color:inherit;';
94
86
  if (hasBgImage) {
95
87
  const msoWidth = cellWidthInPx ? Math.min(cellWidthInPx, 600) : 600;
96
88
  const vmlFill = isGradient
97
89
  ? (() => {
98
90
  const vmlAngle = (0, gradientUtils_1.cssAngleToVml)(parsedGradient?.angle || 180);
99
- const c1 = parsedGradient?.fallback || "#ffffff";
91
+ const c1 = parsedGradient?.fallback || '#ffffff';
100
92
  const c2 = parsedGradient?.colors[parsedGradient.colors.length - 1] || c1;
101
93
  return `<v:fill type="gradient" color="${c1}" color2="${c2}" angle="${vmlAngle}" />`;
102
94
  })()
@@ -104,31 +96,31 @@ function convertTextBlock(blockData, cellWidthInPx) {
104
96
  const bgCss = isGradient
105
97
  ? `background:${effectiveGradient};`
106
98
  : `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>
99
+ const wrappedContent = `
100
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation"
101
+ style="border-collapse:collapse;width:100%;max-width:${msoWidth}px;" class="${visibilityClass}">
102
+ <tr>
103
+ <td width="100%"${(0, common_1.buildOutlookBgAttr)(fallbackBgColor)} valign="top"
104
+ ${!isGradient && rawBgImageUrl ? `background="${rawBgImageUrl}"` : ''}
105
+ style="width:100%;max-width:${msoWidth}px;background-color:${fallbackBgColor};${bgCss}">
106
+
107
+ <!--[if gte mso 9]>
108
+ <v:rect xmlns:v="urn:schemas-microsoft-com:vml"
109
+ fill="true" stroke="false"
110
+ style="width:${msoWidth}px;">
111
+ ${vmlFill}
112
+ <v:textbox inset="0,0,0,0">
113
+ <![endif]-->
114
+
115
+ ${textContent}
116
+
117
+ <!--[if gte mso 9]>
118
+ </v:textbox>
119
+ </v:rect>
120
+ <![endif]-->
121
+
122
+ </td>
123
+ </tr>
132
124
  </table>`;
133
125
  return navigateToUrl
134
126
  ? `<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;">&nbsp;</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;">&nbsp;</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":"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"}
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;AAcD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGxD"}
@@ -57,8 +57,11 @@ function encodeBlockPropsAttr(props) {
57
57
  function toOutlookBgColor(color) {
58
58
  if (!color || color === 'transparent')
59
59
  return '';
60
- const m = color.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*[\d.]+)?\s*\)/);
60
+ const m = color.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)/);
61
61
  if (m) {
62
+ const alpha = m[4] !== undefined ? parseFloat(m[4]) : 1;
63
+ if (alpha < 1)
64
+ return '';
62
65
  return '#' + [m[1], m[2], m[3]].map(n => parseInt(n).toString(16).padStart(2, '0')).join('');
63
66
  }
64
67
  return color;