@emailens/engine 0.11.7 → 0.11.9

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.
@@ -1,5 +1,5 @@
1
- import { x as InputFormat } from '../types-bvGDId6f.cjs';
2
- export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy, c as compileReactEmail } from '../react-email-DuZAgJtw.cjs';
1
+ import { x as InputFormat } from '../types-DiseE-UH.cjs';
2
+ export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy, c as compileReactEmail } from '../react-email-DRkmpgX-.cjs';
3
3
 
4
4
  /**
5
5
  * Compile an MJML source string into an HTML email string.
@@ -1,5 +1,5 @@
1
- import { x as InputFormat } from '../types-bvGDId6f.js';
2
- export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy, c as compileReactEmail } from '../react-email-BROBcC8u.js';
1
+ import { x as InputFormat } from '../types-DiseE-UH.js';
2
+ export { C as CompileError, a as CompileReactEmailOptions, S as SandboxStrategy, c as compileReactEmail } from '../react-email-B82A1R_K.js';
3
3
 
4
4
  /**
5
5
  * Compile an MJML source string into an HTML email string.
package/dist/index.cjs CHANGED
@@ -9068,10 +9068,6 @@ function splitTopLevel(value) {
9068
9068
  return parts;
9069
9069
  }
9070
9070
 
9071
- // src/downlevel.ts
9072
- var cheerio = __toESM(require("cheerio"), 1);
9073
- var csstree = __toESM(require("css-tree"), 1);
9074
-
9075
9071
  // src/color-utils.ts
9076
9072
  var NAMED_COLORS = {
9077
9073
  aliceblue: [240, 248, 255],
@@ -9509,6 +9505,8 @@ function colorDistance(a, b) {
9509
9505
  }
9510
9506
 
9511
9507
  // src/downlevel.ts
9508
+ var cheerio = __toESM(require("cheerio"), 1);
9509
+ var csstree = __toESM(require("css-tree"), 1);
9512
9510
  var LOGICAL_PROPERTY_MAP = {
9513
9511
  "padding-inline": ["padding-left", "padding-right"],
9514
9512
  "padding-block": ["padding-top", "padding-bottom"],
@@ -9959,10 +9957,23 @@ function vmlToCss(html) {
9959
9957
  solid ? `background-color:${solid}` : "",
9960
9958
  `padding:${padding}`
9961
9959
  ].filter(Boolean).join(";");
9960
+ if (/data-vml="/.test(content)) {
9961
+ return `<div data-vml="rect" data-vml-outlook="container-dropped">${content}</div>`;
9962
+ }
9962
9963
  return `<div data-vml="rect" style="${background}${box}">${content}</div>`;
9963
9964
  }
9964
9965
  );
9965
- return out.replace(/<\/?v:(?:fill|textbox|stroke|shadow|imagedata|path|formulas|handles)[^>]*?\/?>/gi, "");
9966
+ out = out.replace(/<\/?v:(?:fill|textbox|stroke|shadow|imagedata|path|formulas|handles)[^>]*?\/?>/gi, "");
9967
+ return blankLabelsAfterNestedShape(out);
9968
+ }
9969
+ function blankLabelsAfterNestedShape(html) {
9970
+ const at = html.indexOf('data-vml-outlook="container-dropped"');
9971
+ if (at === -1) return html;
9972
+ const tail = html.slice(at).replace(
9973
+ /(<div data-vml="roundrect")([^>]*>)([\s\S]*?)(<\/div>)/gi,
9974
+ (whole, open, rest, label, close) => label.trim() ? `${open}${rest.slice(0, -1)} data-vml-outlook="label-dropped">${close}` : whole
9975
+ );
9976
+ return html.slice(0, at) + tail;
9966
9977
  }
9967
9978
  function renderOutlookBranch(html) {
9968
9979
  if (!/<!--\[if/i.test(html)) return html;
@@ -10014,6 +10025,7 @@ var EMPTY_SPAM = { score: 100, level: "low", issues: [] };
10014
10025
  var EMPTY_LINKS = {
10015
10026
  totalLinks: 0,
10016
10027
  issues: [],
10028
+ links: [],
10017
10029
  breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
10018
10030
  };
10019
10031
  var EMPTY_ACCESSIBILITY = { score: 100, issues: [] };
@@ -10393,13 +10405,17 @@ var CLIENT_CONFIGS = {
10393
10405
  strippedProperties: OUTLOOK_WORD_UNSUPPORTED,
10394
10406
  stripMode: "strip",
10395
10407
  valueStrips: [
10396
- { prop: "background", pattern: /linear-gradient|radial-gradient/ },
10397
- { prop: "background-image", pattern: /linear-gradient|radial-gradient/ }
10408
+ // Gradients and image URLs alike: the Word engine paints neither. Only
10409
+ // VML does, and vmlToCss runs after this pass, so a hero built the
10410
+ // bulletproof way keeps the background this strip would otherwise take.
10411
+ { prop: "background", pattern: /linear-gradient|radial-gradient|url\(/ },
10412
+ { prop: "background-image", pattern: /linear-gradient|radial-gradient|url\(/ }
10398
10413
  ],
10399
10414
  inlineAndStripStyles: false,
10400
10415
  stripExternalStylesheets: false,
10401
10416
  stripForms: false,
10402
10417
  stripSvg: false,
10418
+ stripBackgroundAttribute: true,
10403
10419
  additionalChecks: outlookWindowsAdditionalChecks
10404
10420
  },
10405
10421
  "outlook-web": {
@@ -10620,6 +10636,11 @@ function applyTransform(html, config, framework) {
10620
10636
  $(el).replaceWith('<img alt="[SVG not supported]" />');
10621
10637
  });
10622
10638
  }
10639
+ if (config.stripBackgroundAttribute) {
10640
+ $("[background]").each((_, el) => {
10641
+ $(el).removeAttr("background");
10642
+ });
10643
+ }
10623
10644
  if (config.strippedProperties.size > 0 || config.valueStrips && config.valueStrips.length > 0) {
10624
10645
  $("[style]").each((_, el) => {
10625
10646
  const style = $(el).attr("style") || "";
@@ -10644,7 +10665,9 @@ function applyTransform(html, config, framework) {
10644
10665
  for (const vs of (_a = config.valueStrips) != null ? _a : []) {
10645
10666
  if (prop === vs.prop && vs.pattern.test(value)) {
10646
10667
  removed.push(prop);
10647
- props.delete(prop);
10668
+ const fallback = prop === "background" ? backgroundShorthandColor(value) : null;
10669
+ if (fallback) props.set(prop, fallback);
10670
+ else props.delete(prop);
10648
10671
  return;
10649
10672
  }
10650
10673
  }
@@ -13114,6 +13137,7 @@ function isPlaceholderHref(href) {
13114
13137
  }
13115
13138
  function validateLinksFromDom($) {
13116
13139
  const issues = [];
13140
+ const inventory = [];
13117
13141
  const breakdown = { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 };
13118
13142
  const links = $("a");
13119
13143
  const totalLinks = links.length;
@@ -13123,13 +13147,22 @@ function validateLinksFromDom($) {
13123
13147
  rule: "no-links",
13124
13148
  message: "Email contains no links"
13125
13149
  });
13126
- return { totalLinks: 0, issues, breakdown };
13150
+ return { totalLinks: 0, issues, breakdown, links: [] };
13127
13151
  }
13128
13152
  const hrefCounts = /* @__PURE__ */ new Map();
13129
13153
  links.each((_, el) => {
13130
13154
  const href = $(el).attr("href") || "";
13131
13155
  const text = $(el).text().trim();
13132
13156
  const category = classifyHref(href);
13157
+ const entry = {
13158
+ href,
13159
+ text: text.slice(0, 80),
13160
+ scheme: category,
13161
+ isPlaceholder: isPlaceholderHref(href),
13162
+ issues: []
13163
+ };
13164
+ inventory.push(entry);
13165
+ const flag = (rule) => entry.issues.push(rule);
13133
13166
  const elLoc = locOfElement(el);
13134
13167
  const hrefLoc = href ? locOfAttr(el, "href") : elLoc;
13135
13168
  switch (category) {
@@ -13162,6 +13195,7 @@ function validateLinksFromDom($) {
13162
13195
  hrefCounts.set(href, (hrefCounts.get(href) || 0) + 1);
13163
13196
  }
13164
13197
  if (!href || !href.trim()) {
13198
+ flag("empty-href");
13165
13199
  issues.push(__spreadValues({
13166
13200
  severity: "error",
13167
13201
  rule: "empty-href",
@@ -13171,6 +13205,7 @@ function validateLinksFromDom($) {
13171
13205
  return;
13172
13206
  }
13173
13207
  if (category === "javascript" && !isPlaceholderHref(href)) {
13208
+ flag("javascript-href");
13174
13209
  issues.push(__spreadValues({
13175
13210
  severity: "error",
13176
13211
  rule: "javascript-href",
@@ -13181,6 +13216,7 @@ function validateLinksFromDom($) {
13181
13216
  return;
13182
13217
  }
13183
13218
  if (isPlaceholderHref(href)) {
13219
+ flag("placeholder-href");
13184
13220
  issues.push(__spreadValues({
13185
13221
  severity: "warning",
13186
13222
  rule: "placeholder-href",
@@ -13191,6 +13227,7 @@ function validateLinksFromDom($) {
13191
13227
  return;
13192
13228
  }
13193
13229
  if (category === "http") {
13230
+ flag("insecure-link");
13194
13231
  issues.push(__spreadValues({
13195
13232
  severity: "warning",
13196
13233
  rule: "insecure-link",
@@ -13200,6 +13237,7 @@ function validateLinksFromDom($) {
13200
13237
  }, hrefLoc ? { loc: hrefLoc } : {}));
13201
13238
  }
13202
13239
  if (category === "protocol-relative") {
13240
+ flag("protocol-relative");
13203
13241
  issues.push(__spreadValues({
13204
13242
  severity: "warning",
13205
13243
  rule: "protocol-relative",
@@ -13209,6 +13247,7 @@ function validateLinksFromDom($) {
13209
13247
  }, hrefLoc ? { loc: hrefLoc } : {}));
13210
13248
  }
13211
13249
  if (text && GENERIC_LINK_TEXT.has(text.toLowerCase())) {
13250
+ flag("generic-link-text");
13212
13251
  issues.push(__spreadValues({
13213
13252
  severity: "warning",
13214
13253
  rule: "generic-link-text",
@@ -13218,6 +13257,7 @@ function validateLinksFromDom($) {
13218
13257
  }, elLoc ? { loc: elLoc } : {}));
13219
13258
  }
13220
13259
  if (!text && !$(el).attr("aria-label") && !$(el).find("img[alt]").length) {
13260
+ flag("empty-link-text");
13221
13261
  issues.push(__spreadValues({
13222
13262
  severity: "error",
13223
13263
  rule: "empty-link-text",
@@ -13226,6 +13266,7 @@ function validateLinksFromDom($) {
13226
13266
  }, elLoc ? { loc: elLoc } : {}));
13227
13267
  }
13228
13268
  if (category === "mailto" && href.trim().toLowerCase() === "mailto:") {
13269
+ flag("empty-mailto");
13229
13270
  issues.push(__spreadValues({
13230
13271
  severity: "error",
13231
13272
  rule: "empty-mailto",
@@ -13235,6 +13276,7 @@ function validateLinksFromDom($) {
13235
13276
  }, hrefLoc ? { loc: hrefLoc } : {}));
13236
13277
  }
13237
13278
  if (category === "tel" && href.trim().toLowerCase() === "tel:") {
13279
+ flag("empty-tel");
13238
13280
  issues.push(__spreadValues({
13239
13281
  severity: "error",
13240
13282
  rule: "empty-tel",
@@ -13244,6 +13286,7 @@ function validateLinksFromDom($) {
13244
13286
  }, hrefLoc ? { loc: hrefLoc } : {}));
13245
13287
  }
13246
13288
  if (href.length > 2e3) {
13289
+ flag("long-url");
13247
13290
  issues.push(__spreadValues({
13248
13291
  severity: "info",
13249
13292
  rule: "long-url",
@@ -13253,7 +13296,8 @@ function validateLinksFromDom($) {
13253
13296
  }, hrefLoc ? { loc: hrefLoc } : {}));
13254
13297
  }
13255
13298
  });
13256
- links.each((_, el) => {
13299
+ links.each((i, el) => {
13300
+ var _a;
13257
13301
  const href = $(el).attr("href") || "";
13258
13302
  const trimmed = href.trim();
13259
13303
  if (trimmed.startsWith("#") && trimmed.length > 1) {
@@ -13261,6 +13305,7 @@ function validateLinksFromDom($) {
13261
13305
  const target = $(`[id="${targetId}"]`);
13262
13306
  const anchorLoc = locOfAttr(el, "href");
13263
13307
  if (target.length === 0) {
13308
+ (_a = inventory[i]) == null ? void 0 : _a.issues.push("broken-anchor");
13264
13309
  issues.push(__spreadValues({
13265
13310
  severity: "error",
13266
13311
  rule: "broken-anchor",
@@ -13281,7 +13326,7 @@ function validateLinksFromDom($) {
13281
13326
  });
13282
13327
  }
13283
13328
  }
13284
- return { totalLinks, issues, breakdown };
13329
+ return { totalLinks, issues, breakdown, links: inventory };
13285
13330
  }
13286
13331
  function validateLinks(html, options) {
13287
13332
  return fromHtml(html, EMPTY_LINKS, validateLinksFromDom, options);
@@ -14820,7 +14865,20 @@ function checkVml(html, options) {
14820
14865
  rule: "vml-nested-shape",
14821
14866
  severity: "error",
14822
14867
  message: `<v:${name}> is nested inside <v:${parent.name}>. Outlook does not support nesting one VML shape inside another.`,
14823
- detail: `Verified in Outlook Classic, and the damage is not local. Three things happen: the containing <v:${parent.name}> does not render at all (its fill and everything inside it disappear, leaving the inner shape stranded); the table structure around it terminates early, so content *after* the shape falls out of the layout and renders outside the email frame; and every VML shape further down the email stops drawing its text, so buttons and headings after this point ship as blank coloured blocks. Confirmed with byte-identical probes either side of one nested shape: the one before it renders its labels, the one after it does not. Every other client renders the HTML fallback correctly, which is why none of this is visible outside the Word engine. Fix: lift the inner shape out of <v:${parent.name}>, or drop the container shape and keep the inner one (a framed background can degrade to a solid fill colour on the <td> instead).`
14868
+ // Two effects, not the three this used to claim. The withdrawn one
14869
+ // said the table structure terminates early so content after the
14870
+ // shape falls outside the email frame. It had no methodology behind
14871
+ // it, and `T1c-blast-radius.html`, the fixture built to measure
14872
+ // exactly this, contradicts it: the block after the nested shape
14873
+ // rendered in place inside the same table with its background and
14874
+ // borders intact, and only the VML labels were missing. The likely
14875
+ // origin is over-reading "layout breaks after it" from a run where a
14876
+ // 600px hero band vanished and everything below shifted up.
14877
+ //
14878
+ // Kept as a warning to whoever edits this next: the probe sentence
14879
+ // below covers the downstream text suppression only. Do not let it
14880
+ // lend its authority to a neighbouring clause again.
14881
+ detail: `Verified in Outlook Classic, and the damage is not local. Two things happen: the containing <v:${parent.name}> does not render at all (its fill and everything inside it disappear, leaving the inner shape stranded); and every VML shape further down the email stops drawing its text, so buttons and headings after this point ship as blank coloured blocks. That second effect was confirmed with byte-identical probes either side of one nested shape: the one before it renders its labels, the one after it does not. Every other client renders the HTML fallback correctly, which is why none of this is visible outside the Word engine. Fix: lift the inner shape out of <v:${parent.name}>, or drop the container shape and keep the inner one (a framed background can degrade to a solid fill colour on the <td> instead).`
14824
14882
  }, loc));
14825
14883
  }
14826
14884
  if (!tag.selfClosing) openShapes.push(tag);