@emailens/engine 0.5.2 → 0.7.0
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/README.md +202 -4
- package/dist/{chunk-W4SPWESS.js → chunk-3NDQOTPM.js} +2 -2
- package/dist/{chunk-SZ5O5PDZ.js → chunk-OY3DGZVU.js} +2 -2
- package/dist/{chunk-EBNA3X7P.js → chunk-URZ4XPST.js} +6 -7
- package/dist/chunk-URZ4XPST.js.map +1 -0
- package/dist/{chunk-PFONR3YC.js → chunk-ZQF2XUIJ.js} +1 -8
- package/dist/{chunk-PFONR3YC.js.map → chunk-ZQF2XUIJ.js.map} +1 -1
- package/dist/compile/index.cjs +3 -3
- package/dist/compile/index.cjs.map +1 -1
- package/dist/compile/index.d.cts +2 -2
- package/dist/compile/index.d.ts +2 -2
- package/dist/compile/index.js +7 -7
- package/dist/index.cjs +594 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +147 -4
- package/dist/index.d.ts +147 -4
- package/dist/index.js +591 -61
- package/dist/index.js.map +1 -1
- package/dist/maizzle-Y2CLJ7GB.js +8 -0
- package/dist/mjml-D7IOYXXK.js +8 -0
- package/dist/react-email-4TW6IDAA.js +8 -0
- package/dist/{react-email-BQljgXbo.d.cts → react-email-CYOtHJch.d.cts} +48 -1
- package/dist/{react-email-BQljgXbo.d.ts → react-email-CYOtHJch.d.ts} +48 -1
- package/package.json +1 -1
- package/dist/chunk-EBNA3X7P.js.map +0 -1
- package/dist/maizzle-YDSYDVSM.js +0 -8
- package/dist/mjml-IYGC6AOM.js +0 -8
- package/dist/react-email-ZSQKM36O.js +0 -8
- /package/dist/{chunk-W4SPWESS.js.map → chunk-3NDQOTPM.js.map} +0 -0
- /package/dist/{chunk-SZ5O5PDZ.js.map → chunk-OY3DGZVU.js.map} +0 -0
- /package/dist/{maizzle-YDSYDVSM.js.map → maizzle-Y2CLJ7GB.js.map} +0 -0
- /package/dist/{mjml-IYGC6AOM.js.map → mjml-D7IOYXXK.js.map} +0 -0
- /package/dist/{react-email-ZSQKM36O.js.map → react-email-4TW6IDAA.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
__objRest,
|
|
4
4
|
__spreadProps,
|
|
5
5
|
__spreadValues
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-ZQF2XUIJ.js";
|
|
7
7
|
|
|
8
8
|
// src/clients.ts
|
|
9
9
|
var EMAIL_CLIENTS = [
|
|
@@ -2747,6 +2747,43 @@ var GENERIC_LINK_TEXT = /* @__PURE__ */ new Set([
|
|
|
2747
2747
|
"tap here",
|
|
2748
2748
|
"this"
|
|
2749
2749
|
]);
|
|
2750
|
+
var GMAIL_CLIP_THRESHOLD = 102 * 1024;
|
|
2751
|
+
var GMAIL_CLIP_WARNING_THRESHOLD = 90 * 1024;
|
|
2752
|
+
var CLIENT_DISPLAY_LIMITS = [
|
|
2753
|
+
{ client: "Gmail (Web)", subjectLimit: 70, preheaderLimit: 90 },
|
|
2754
|
+
{ client: "Gmail (Mobile)", subjectLimit: 40, preheaderLimit: 90 },
|
|
2755
|
+
{ client: "Outlook (Web)", subjectLimit: 60, preheaderLimit: 35 },
|
|
2756
|
+
{ client: "Outlook (Desktop)", subjectLimit: 55, preheaderLimit: 35 },
|
|
2757
|
+
{ client: "Apple Mail (macOS)", subjectLimit: 78, preheaderLimit: 140 },
|
|
2758
|
+
{ client: "Apple Mail (iOS)", subjectLimit: 35, preheaderLimit: 90 },
|
|
2759
|
+
{ client: "Yahoo Mail", subjectLimit: 46, preheaderLimit: 100 },
|
|
2760
|
+
{ client: "Samsung Email", subjectLimit: 40, preheaderLimit: 70 }
|
|
2761
|
+
];
|
|
2762
|
+
var TEMPLATE_VARIABLE_PATTERNS = [
|
|
2763
|
+
[/\{\{[\s\S]*?\}\}/g, "Handlebars/Mustache"],
|
|
2764
|
+
// {{var}}
|
|
2765
|
+
[/\$\{[^}]+\}/g, "ES template literal"],
|
|
2766
|
+
// ${var}
|
|
2767
|
+
[/<%=?\s*[^%]+%>/g, "ERB/EJS"],
|
|
2768
|
+
// <% %> / <%= %>
|
|
2769
|
+
[/\*\|[A-Z_][A-Z0-9_]*\|\*/g, "Mailchimp merge tag"],
|
|
2770
|
+
// *|TAG|*
|
|
2771
|
+
[/%%[A-Za-z_][A-Za-z0-9_]*%%/g, "Salesforce AMPscript"],
|
|
2772
|
+
// %%tag%%
|
|
2773
|
+
[/\{[A-Za-z_][A-Za-z0-9_.]{2,}\}/g, "Single-brace merge field"]
|
|
2774
|
+
// {merge_field} (3+ char names)
|
|
2775
|
+
];
|
|
2776
|
+
var EMPTY_SPAM = { score: 100, level: "low", issues: [] };
|
|
2777
|
+
var EMPTY_LINKS = {
|
|
2778
|
+
totalLinks: 0,
|
|
2779
|
+
issues: [],
|
|
2780
|
+
breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
|
|
2781
|
+
};
|
|
2782
|
+
var EMPTY_ACCESSIBILITY = { score: 100, issues: [] };
|
|
2783
|
+
var EMPTY_IMAGES = { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
|
|
2784
|
+
var EMPTY_INBOX_PREVIEW = { subject: null, preheader: null, subjectLength: 0, preheaderLength: 0, truncation: [], issues: [] };
|
|
2785
|
+
var EMPTY_SIZE = { htmlBytes: 0, humanSize: "0 B", clipped: false, issues: [] };
|
|
2786
|
+
var EMPTY_TEMPLATE = { unresolvedCount: 0, issues: [] };
|
|
2750
2787
|
|
|
2751
2788
|
// src/transform.ts
|
|
2752
2789
|
function inlineStyles($) {
|
|
@@ -3255,15 +3292,8 @@ function transformForAllClients(html, framework) {
|
|
|
3255
3292
|
// src/analyze.ts
|
|
3256
3293
|
import * as cheerio2 from "cheerio";
|
|
3257
3294
|
import * as csstree2 from "css-tree";
|
|
3258
|
-
function
|
|
3295
|
+
function analyzeEmailFromDom($, framework) {
|
|
3259
3296
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
3260
|
-
if (!html || !html.trim()) {
|
|
3261
|
-
return [];
|
|
3262
|
-
}
|
|
3263
|
-
if (html.length > MAX_HTML_SIZE) {
|
|
3264
|
-
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
3265
|
-
}
|
|
3266
|
-
const $ = cheerio2.load(html);
|
|
3267
3297
|
const warnings = [];
|
|
3268
3298
|
const seenWarnings = /* @__PURE__ */ new Set();
|
|
3269
3299
|
function addWarning(w) {
|
|
@@ -3508,6 +3538,16 @@ function analyzeEmail(html, framework) {
|
|
|
3508
3538
|
warnings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
3509
3539
|
return warnings;
|
|
3510
3540
|
}
|
|
3541
|
+
function analyzeEmail(html, framework) {
|
|
3542
|
+
if (!html || !html.trim()) {
|
|
3543
|
+
return [];
|
|
3544
|
+
}
|
|
3545
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
3546
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
3547
|
+
}
|
|
3548
|
+
const $ = cheerio2.load(html);
|
|
3549
|
+
return analyzeEmailFromDom($, framework);
|
|
3550
|
+
}
|
|
3511
3551
|
function getFixType(prop) {
|
|
3512
3552
|
return STRUCTURAL_FIX_PROPERTIES.has(prop) ? "structural" : "css";
|
|
3513
3553
|
}
|
|
@@ -4406,7 +4446,9 @@ var WEIGHTS = {
|
|
|
4406
4446
|
"image-only": 20,
|
|
4407
4447
|
"high-image-ratio": 10,
|
|
4408
4448
|
"deceptive-link": 15,
|
|
4409
|
-
"all-caps-subject": 10
|
|
4449
|
+
"all-caps-subject": 10,
|
|
4450
|
+
"missing-physical-address": 8,
|
|
4451
|
+
"missing-one-click-unsubscribe": 5
|
|
4410
4452
|
};
|
|
4411
4453
|
function extractVisibleText($) {
|
|
4412
4454
|
const clone = $.root().clone();
|
|
@@ -4623,6 +4665,47 @@ Href: ${href}`
|
|
|
4623
4665
|
});
|
|
4624
4666
|
return issues;
|
|
4625
4667
|
}
|
|
4668
|
+
var STREET_ADDRESS_PATTERN = /\b\d{1,5}\s+[A-Za-z]+\s+(St(reet)?|Ave(nue)?|Blvd|Boulevard|Dr(ive)?|Rd|Road|Ln|Lane|Way|Ct|Court|Pl(ace)?|Pkwy|Parkway|Cir(cle)?|Terr(ace)?|Loop)\b/i;
|
|
4669
|
+
var PO_BOX_PATTERN = /\bP\.?\s*O\.?\s*Box\s+\d+/i;
|
|
4670
|
+
var ZIP_CODE_PATTERN = /\b\d{5}(-\d{4})?\b/;
|
|
4671
|
+
var ADDRESS_CLASS_PATTERN = /address|mailing|postal|footer-address|physical-address/i;
|
|
4672
|
+
function checkPhysicalAddress($, text, options) {
|
|
4673
|
+
if ((options == null ? void 0 : options.emailType) === "transactional") return null;
|
|
4674
|
+
if (STREET_ADDRESS_PATTERN.test(text) && ZIP_CODE_PATTERN.test(text)) return null;
|
|
4675
|
+
if (PO_BOX_PATTERN.test(text) && ZIP_CODE_PATTERN.test(text)) return null;
|
|
4676
|
+
let foundByClass = false;
|
|
4677
|
+
$("[class]").each((_, el) => {
|
|
4678
|
+
const cls = $(el).attr("class") || "";
|
|
4679
|
+
if (ADDRESS_CLASS_PATTERN.test(cls)) {
|
|
4680
|
+
const elText = $(el).text().trim();
|
|
4681
|
+
if (elText.length > 5) {
|
|
4682
|
+
foundByClass = true;
|
|
4683
|
+
return false;
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4686
|
+
});
|
|
4687
|
+
if (foundByClass) return null;
|
|
4688
|
+
if ($("address").length > 0 && $("address").text().trim().length > 5) return null;
|
|
4689
|
+
return {
|
|
4690
|
+
rule: "missing-physical-address",
|
|
4691
|
+
severity: "warning",
|
|
4692
|
+
message: "No physical mailing address detected \u2014 required by CAN-SPAM for marketing emails.",
|
|
4693
|
+
detail: "Include a street address or P.O. Box in the email footer."
|
|
4694
|
+
};
|
|
4695
|
+
}
|
|
4696
|
+
function checkOneClickUnsubscribe(options) {
|
|
4697
|
+
var _a, _b;
|
|
4698
|
+
if (!((_a = options == null ? void 0 : options.listUnsubscribeHeader) == null ? void 0 : _a.trim())) return null;
|
|
4699
|
+
if (!((_b = options == null ? void 0 : options.listUnsubscribePostHeader) == null ? void 0 : _b.trim())) {
|
|
4700
|
+
return {
|
|
4701
|
+
rule: "missing-one-click-unsubscribe",
|
|
4702
|
+
severity: "warning",
|
|
4703
|
+
message: "List-Unsubscribe header present but missing List-Unsubscribe-Post header (RFC 8058). Gmail and Yahoo require one-click unsubscribe.",
|
|
4704
|
+
detail: 'Add "List-Unsubscribe-Post: List-Unsubscribe=One-Click" header.'
|
|
4705
|
+
};
|
|
4706
|
+
}
|
|
4707
|
+
return null;
|
|
4708
|
+
}
|
|
4626
4709
|
function checkAllCapsTitle($) {
|
|
4627
4710
|
const title = $("title").text().trim();
|
|
4628
4711
|
if (title.length > 5 && title === title.toUpperCase() && /[A-Z]/.test(title)) {
|
|
@@ -4634,14 +4717,7 @@ function checkAllCapsTitle($) {
|
|
|
4634
4717
|
}
|
|
4635
4718
|
return null;
|
|
4636
4719
|
}
|
|
4637
|
-
function
|
|
4638
|
-
if (!html || !html.trim()) {
|
|
4639
|
-
return { score: 100, level: "low", issues: [] };
|
|
4640
|
-
}
|
|
4641
|
-
if (html.length > MAX_HTML_SIZE) {
|
|
4642
|
-
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
4643
|
-
}
|
|
4644
|
-
const $ = cheerio4.load(html);
|
|
4720
|
+
function analyzeSpamFromDom($, options) {
|
|
4645
4721
|
const text = extractVisibleText($);
|
|
4646
4722
|
const issues = [];
|
|
4647
4723
|
const capsIssue = checkCapsRatio(text);
|
|
@@ -4659,6 +4735,10 @@ function analyzeSpam(html, options) {
|
|
|
4659
4735
|
issues.push(...checkDeceptiveLinks($));
|
|
4660
4736
|
const capsTitle = checkAllCapsTitle($);
|
|
4661
4737
|
if (capsTitle) issues.push(capsTitle);
|
|
4738
|
+
const addressIssue = checkPhysicalAddress($, text, options);
|
|
4739
|
+
if (addressIssue) issues.push(addressIssue);
|
|
4740
|
+
const oneClickIssue = checkOneClickUnsubscribe(options);
|
|
4741
|
+
if (oneClickIssue) issues.push(oneClickIssue);
|
|
4662
4742
|
let penalty = 0;
|
|
4663
4743
|
const seenRules = /* @__PURE__ */ new Map();
|
|
4664
4744
|
for (const issue of issues) {
|
|
@@ -4679,6 +4759,16 @@ function analyzeSpam(html, options) {
|
|
|
4679
4759
|
const level = score >= 70 ? "low" : score >= 40 ? "medium" : "high";
|
|
4680
4760
|
return { score, level, issues };
|
|
4681
4761
|
}
|
|
4762
|
+
function analyzeSpam(html, options) {
|
|
4763
|
+
if (!html || !html.trim()) {
|
|
4764
|
+
return { score: 100, level: "low", issues: [] };
|
|
4765
|
+
}
|
|
4766
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
4767
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
4768
|
+
}
|
|
4769
|
+
const $ = cheerio4.load(html);
|
|
4770
|
+
return analyzeSpamFromDom($, options);
|
|
4771
|
+
}
|
|
4682
4772
|
|
|
4683
4773
|
// src/link-validator.ts
|
|
4684
4774
|
import * as cheerio5 from "cheerio";
|
|
@@ -4698,18 +4788,7 @@ function isPlaceholderHref(href) {
|
|
|
4698
4788
|
const h = href.trim().toLowerCase();
|
|
4699
4789
|
return h === "#" || h === "" || h === "javascript:void(0)" || h === "javascript:;";
|
|
4700
4790
|
}
|
|
4701
|
-
function
|
|
4702
|
-
if (!html || !html.trim()) {
|
|
4703
|
-
return {
|
|
4704
|
-
totalLinks: 0,
|
|
4705
|
-
issues: [],
|
|
4706
|
-
breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
|
|
4707
|
-
};
|
|
4708
|
-
}
|
|
4709
|
-
if (html.length > MAX_HTML_SIZE) {
|
|
4710
|
-
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
4711
|
-
}
|
|
4712
|
-
const $ = cheerio5.load(html);
|
|
4791
|
+
function validateLinksFromDom($) {
|
|
4713
4792
|
const issues = [];
|
|
4714
4793
|
const breakdown = { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 };
|
|
4715
4794
|
const links = $("a");
|
|
@@ -4848,6 +4927,23 @@ function validateLinks(html) {
|
|
|
4848
4927
|
});
|
|
4849
4928
|
}
|
|
4850
4929
|
});
|
|
4930
|
+
links.each((_, el) => {
|
|
4931
|
+
const href = $(el).attr("href") || "";
|
|
4932
|
+
const trimmed = href.trim();
|
|
4933
|
+
if (trimmed.startsWith("#") && trimmed.length > 1) {
|
|
4934
|
+
const targetId = trimmed.slice(1);
|
|
4935
|
+
const target = $(`[id="${targetId}"]`);
|
|
4936
|
+
if (target.length === 0) {
|
|
4937
|
+
issues.push({
|
|
4938
|
+
severity: "error",
|
|
4939
|
+
rule: "broken-anchor",
|
|
4940
|
+
message: `Anchor link "${trimmed}" points to an element that does not exist`,
|
|
4941
|
+
href: trimmed,
|
|
4942
|
+
text: $(el).text().trim().slice(0, 80) || "(no text)"
|
|
4943
|
+
});
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
});
|
|
4851
4947
|
for (const [href, count] of hrefCounts) {
|
|
4852
4948
|
if (count > 5) {
|
|
4853
4949
|
issues.push({
|
|
@@ -4860,6 +4956,20 @@ function validateLinks(html) {
|
|
|
4860
4956
|
}
|
|
4861
4957
|
return { totalLinks, issues, breakdown };
|
|
4862
4958
|
}
|
|
4959
|
+
function validateLinks(html) {
|
|
4960
|
+
if (!html || !html.trim()) {
|
|
4961
|
+
return {
|
|
4962
|
+
totalLinks: 0,
|
|
4963
|
+
issues: [],
|
|
4964
|
+
breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
|
|
4965
|
+
};
|
|
4966
|
+
}
|
|
4967
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
4968
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
4969
|
+
}
|
|
4970
|
+
const $ = cheerio5.load(html);
|
|
4971
|
+
return validateLinksFromDom($);
|
|
4972
|
+
}
|
|
4863
4973
|
|
|
4864
4974
|
// src/accessibility-checker.ts
|
|
4865
4975
|
import * as cheerio6 from "cheerio";
|
|
@@ -5087,6 +5197,21 @@ function checkTextSizeAndContrast($) {
|
|
|
5087
5197
|
}
|
|
5088
5198
|
return issues;
|
|
5089
5199
|
}
|
|
5200
|
+
function checkCharsetDeclaration($) {
|
|
5201
|
+
const metaCharset = $("meta[charset]");
|
|
5202
|
+
if (metaCharset.length > 0) return null;
|
|
5203
|
+
const httpEquiv = $('meta[http-equiv="Content-Type"]');
|
|
5204
|
+
if (httpEquiv.length > 0) {
|
|
5205
|
+
const content = httpEquiv.attr("content") || "";
|
|
5206
|
+
if (/charset\s*=/i.test(content)) return null;
|
|
5207
|
+
}
|
|
5208
|
+
return {
|
|
5209
|
+
severity: "warning",
|
|
5210
|
+
rule: "missing-charset",
|
|
5211
|
+
message: "Missing charset declaration",
|
|
5212
|
+
details: 'Add <meta charset="utf-8"> in <head> to prevent encoding issues across email clients.'
|
|
5213
|
+
};
|
|
5214
|
+
}
|
|
5090
5215
|
function checkSemanticStructure($) {
|
|
5091
5216
|
const issues = [];
|
|
5092
5217
|
const headings = [];
|
|
@@ -5108,14 +5233,7 @@ function checkSemanticStructure($) {
|
|
|
5108
5233
|
}
|
|
5109
5234
|
return issues;
|
|
5110
5235
|
}
|
|
5111
|
-
function
|
|
5112
|
-
if (!html || !html.trim()) {
|
|
5113
|
-
return { score: 100, issues: [] };
|
|
5114
|
-
}
|
|
5115
|
-
if (html.length > MAX_HTML_SIZE) {
|
|
5116
|
-
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5117
|
-
}
|
|
5118
|
-
const $ = cheerio6.load(html);
|
|
5236
|
+
function checkAccessibilityFromDom($) {
|
|
5119
5237
|
const issues = [];
|
|
5120
5238
|
const langIssue = checkLangAttribute($);
|
|
5121
5239
|
if (langIssue) issues.push(langIssue);
|
|
@@ -5126,6 +5244,8 @@ function checkAccessibility(html) {
|
|
|
5126
5244
|
issues.push(...checkTableAccessibility($));
|
|
5127
5245
|
issues.push(...checkTextSizeAndContrast($));
|
|
5128
5246
|
issues.push(...checkSemanticStructure($));
|
|
5247
|
+
const charsetIssue = checkCharsetDeclaration($);
|
|
5248
|
+
if (charsetIssue) issues.push(charsetIssue);
|
|
5129
5249
|
let penalty = 0;
|
|
5130
5250
|
const seenRules = /* @__PURE__ */ new Map();
|
|
5131
5251
|
for (const issue of issues) {
|
|
@@ -5148,6 +5268,16 @@ function checkAccessibility(html) {
|
|
|
5148
5268
|
const score = Math.max(0, 100 - penalty);
|
|
5149
5269
|
return { score, issues };
|
|
5150
5270
|
}
|
|
5271
|
+
function checkAccessibility(html) {
|
|
5272
|
+
if (!html || !html.trim()) {
|
|
5273
|
+
return { score: 100, issues: [] };
|
|
5274
|
+
}
|
|
5275
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
5276
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5277
|
+
}
|
|
5278
|
+
const $ = cheerio6.load(html);
|
|
5279
|
+
return checkAccessibilityFromDom($);
|
|
5280
|
+
}
|
|
5151
5281
|
|
|
5152
5282
|
// src/image-analyzer.ts
|
|
5153
5283
|
import * as cheerio7 from "cheerio";
|
|
@@ -5181,14 +5311,7 @@ function truncateSrc(src, max = 60) {
|
|
|
5181
5311
|
}
|
|
5182
5312
|
return src.length > max ? src.slice(0, max - 3) + "..." : src;
|
|
5183
5313
|
}
|
|
5184
|
-
function
|
|
5185
|
-
if (!html || !html.trim()) {
|
|
5186
|
-
return { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
|
|
5187
|
-
}
|
|
5188
|
-
if (html.length > MAX_HTML_SIZE) {
|
|
5189
|
-
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5190
|
-
}
|
|
5191
|
-
const $ = cheerio7.load(html);
|
|
5314
|
+
function analyzeImagesFromDom($) {
|
|
5192
5315
|
const issues = [];
|
|
5193
5316
|
const images = [];
|
|
5194
5317
|
let totalDataUriBytes = 0;
|
|
@@ -5314,16 +5437,318 @@ function analyzeImages(html) {
|
|
|
5314
5437
|
}
|
|
5315
5438
|
return { total: images.length, totalDataUriBytes, issues, images };
|
|
5316
5439
|
}
|
|
5440
|
+
function analyzeImages(html) {
|
|
5441
|
+
if (!html || !html.trim()) {
|
|
5442
|
+
return { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
|
|
5443
|
+
}
|
|
5444
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
5445
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5446
|
+
}
|
|
5447
|
+
const $ = cheerio7.load(html);
|
|
5448
|
+
return analyzeImagesFromDom($);
|
|
5449
|
+
}
|
|
5450
|
+
|
|
5451
|
+
// src/inbox-preview.ts
|
|
5452
|
+
import * as cheerio8 from "cheerio";
|
|
5453
|
+
var MAX_SUBJECT_LENGTH = 60;
|
|
5454
|
+
var MAX_PREHEADER_LENGTH = 100;
|
|
5455
|
+
var MIN_PREHEADER_LENGTH = 30;
|
|
5456
|
+
var EMOJI_PATTERN = /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{2B50}-\u{2B55}\u{FE00}-\u{FE0F}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{200D}\u{20E3}\u{E0020}-\u{E007F}]/u;
|
|
5457
|
+
var ZWNJ_PADDING_PATTERN = /(\u200C\s*( |\u00A0)\s*){2,}|(‌\s* \s*){2,}/;
|
|
5458
|
+
function extractInboxPreview(html) {
|
|
5459
|
+
if (!html || !html.trim()) {
|
|
5460
|
+
return {
|
|
5461
|
+
subject: null,
|
|
5462
|
+
preheader: null,
|
|
5463
|
+
subjectLength: 0,
|
|
5464
|
+
preheaderLength: 0,
|
|
5465
|
+
truncation: [],
|
|
5466
|
+
issues: [{ rule: "missing-subject", severity: "warning", message: "No <title> tag found. Most email clients use this as the subject line." }]
|
|
5467
|
+
};
|
|
5468
|
+
}
|
|
5469
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
5470
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5471
|
+
}
|
|
5472
|
+
const $ = cheerio8.load(html);
|
|
5473
|
+
return extractInboxPreviewFromDom($);
|
|
5474
|
+
}
|
|
5475
|
+
function extractInboxPreviewFromDom($) {
|
|
5476
|
+
var _a, _b;
|
|
5477
|
+
const issues = [];
|
|
5478
|
+
const titleEl = $("title");
|
|
5479
|
+
const subject = titleEl.length > 0 ? titleEl.first().text().trim() || null : null;
|
|
5480
|
+
const subjectLength = (_a = subject == null ? void 0 : subject.length) != null ? _a : 0;
|
|
5481
|
+
if (!subject) {
|
|
5482
|
+
issues.push({
|
|
5483
|
+
rule: "missing-subject",
|
|
5484
|
+
severity: "warning",
|
|
5485
|
+
message: "No <title> tag found. Most email clients use this as the subject line."
|
|
5486
|
+
});
|
|
5487
|
+
} else if (subjectLength > MAX_SUBJECT_LENGTH) {
|
|
5488
|
+
issues.push({
|
|
5489
|
+
rule: "subject-too-long",
|
|
5490
|
+
severity: "warning",
|
|
5491
|
+
message: `Subject is ${subjectLength} characters \u2014 may be truncated in inboxes (recommended: ${MAX_SUBJECT_LENGTH} or fewer).`
|
|
5492
|
+
});
|
|
5493
|
+
}
|
|
5494
|
+
const preheader = extractPreheaderText($);
|
|
5495
|
+
const preheaderLength = (_b = preheader == null ? void 0 : preheader.length) != null ? _b : 0;
|
|
5496
|
+
if (!preheader) {
|
|
5497
|
+
issues.push({
|
|
5498
|
+
rule: "missing-preheader",
|
|
5499
|
+
severity: "info",
|
|
5500
|
+
message: "No preheader text detected. Adding preview text improves open rates in crowded inboxes."
|
|
5501
|
+
});
|
|
5502
|
+
} else if (preheaderLength < MIN_PREHEADER_LENGTH) {
|
|
5503
|
+
issues.push({
|
|
5504
|
+
rule: "preheader-too-short",
|
|
5505
|
+
severity: "warning",
|
|
5506
|
+
message: `Preheader is only ${preheaderLength} characters \u2014 email clients may backfill with body text (recommended: ${MIN_PREHEADER_LENGTH}+).`
|
|
5507
|
+
});
|
|
5508
|
+
} else if (preheaderLength > MAX_PREHEADER_LENGTH) {
|
|
5509
|
+
issues.push({
|
|
5510
|
+
rule: "preheader-too-long",
|
|
5511
|
+
severity: "info",
|
|
5512
|
+
message: `Preheader is ${preheaderLength} characters \u2014 most clients show 40\u2013100 characters. Text beyond that is hidden.`
|
|
5513
|
+
});
|
|
5514
|
+
}
|
|
5515
|
+
if (hasZwnjPadding($)) {
|
|
5516
|
+
issues.push({
|
|
5517
|
+
rule: "zwnj-padding",
|
|
5518
|
+
severity: "info",
|
|
5519
|
+
message: "Preheader uses ‌ padding hack \u2014 works in most clients but may show garbled text in some."
|
|
5520
|
+
});
|
|
5521
|
+
}
|
|
5522
|
+
if (subject && EMOJI_PATTERN.test(subject)) {
|
|
5523
|
+
issues.push({
|
|
5524
|
+
rule: "emoji-in-subject",
|
|
5525
|
+
severity: "info",
|
|
5526
|
+
message: "Subject line contains emoji \u2014 renders inconsistently across email clients and may trigger spam filters."
|
|
5527
|
+
});
|
|
5528
|
+
}
|
|
5529
|
+
const truncation = computeTruncation(subject, preheader);
|
|
5530
|
+
return {
|
|
5531
|
+
subject,
|
|
5532
|
+
preheader,
|
|
5533
|
+
subjectLength,
|
|
5534
|
+
preheaderLength,
|
|
5535
|
+
truncation,
|
|
5536
|
+
issues
|
|
5537
|
+
};
|
|
5538
|
+
}
|
|
5539
|
+
function computeTruncation(subject, preheader) {
|
|
5540
|
+
return CLIENT_DISPLAY_LIMITS.map((limit) => {
|
|
5541
|
+
const truncatedSubject = subject && subject.length > limit.subjectLimit ? subject.slice(0, limit.subjectLimit - 1) + "\u2026" : subject;
|
|
5542
|
+
const truncatedPreheader = preheader && preheader.length > limit.preheaderLimit ? preheader.slice(0, limit.preheaderLimit - 1) + "\u2026" : preheader;
|
|
5543
|
+
return {
|
|
5544
|
+
client: limit.client,
|
|
5545
|
+
subjectLimit: limit.subjectLimit,
|
|
5546
|
+
preheaderLimit: limit.preheaderLimit,
|
|
5547
|
+
truncatedSubject: truncatedSubject != null ? truncatedSubject : null,
|
|
5548
|
+
truncatedPreheader: truncatedPreheader != null ? truncatedPreheader : null,
|
|
5549
|
+
subjectTruncated: !!subject && subject.length > limit.subjectLimit,
|
|
5550
|
+
preheaderTruncated: !!preheader && preheader.length > limit.preheaderLimit
|
|
5551
|
+
};
|
|
5552
|
+
});
|
|
5553
|
+
}
|
|
5554
|
+
function hasZwnjPadding($) {
|
|
5555
|
+
const body = $("body");
|
|
5556
|
+
if (!body.length) return false;
|
|
5557
|
+
const hiddenSelectors = [
|
|
5558
|
+
'div[style*="display:none"]',
|
|
5559
|
+
'div[style*="display: none"]',
|
|
5560
|
+
'span[style*="display:none"]',
|
|
5561
|
+
'span[style*="display: none"]',
|
|
5562
|
+
'div[style*="max-height:0"]',
|
|
5563
|
+
'div[style*="max-height: 0"]',
|
|
5564
|
+
'span[style*="max-height:0"]',
|
|
5565
|
+
'span[style*="max-height: 0"]',
|
|
5566
|
+
'[class*="preheader"]',
|
|
5567
|
+
'[class*="preview-text"]',
|
|
5568
|
+
'[class*="previewText"]'
|
|
5569
|
+
];
|
|
5570
|
+
for (const sel of hiddenSelectors) {
|
|
5571
|
+
const el = body.find(sel).first();
|
|
5572
|
+
if (el.length) {
|
|
5573
|
+
const rawHtml = el.html() || "";
|
|
5574
|
+
if (ZWNJ_PADDING_PATTERN.test(rawHtml)) return true;
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
return false;
|
|
5578
|
+
}
|
|
5579
|
+
function extractPreheaderText($) {
|
|
5580
|
+
const body = $("body");
|
|
5581
|
+
if (!body.length) return null;
|
|
5582
|
+
const hiddenSelectors = [
|
|
5583
|
+
'div[style*="display:none"]',
|
|
5584
|
+
'div[style*="display: none"]',
|
|
5585
|
+
'span[style*="display:none"]',
|
|
5586
|
+
'span[style*="display: none"]',
|
|
5587
|
+
'div[style*="visibility:hidden"]',
|
|
5588
|
+
'div[style*="visibility: hidden"]',
|
|
5589
|
+
'span[style*="visibility:hidden"]',
|
|
5590
|
+
'span[style*="visibility: hidden"]',
|
|
5591
|
+
'div[style*="max-height:0"]',
|
|
5592
|
+
'div[style*="max-height: 0"]',
|
|
5593
|
+
'span[style*="max-height:0"]',
|
|
5594
|
+
'span[style*="max-height: 0"]',
|
|
5595
|
+
'div[style*="mso-hide:all"]',
|
|
5596
|
+
'div[style*="mso-hide: all"]'
|
|
5597
|
+
];
|
|
5598
|
+
for (const sel of hiddenSelectors) {
|
|
5599
|
+
const el = body.find(sel).first();
|
|
5600
|
+
if (el.length) {
|
|
5601
|
+
const text = el.text().trim();
|
|
5602
|
+
if (text) return text;
|
|
5603
|
+
}
|
|
5604
|
+
}
|
|
5605
|
+
const preheaderClasses = body.find(
|
|
5606
|
+
'[class*="preheader"], [class*="preview-text"], [class*="previewText"]'
|
|
5607
|
+
).first();
|
|
5608
|
+
if (preheaderClasses.length) {
|
|
5609
|
+
const text = preheaderClasses.text().trim();
|
|
5610
|
+
if (text) return text;
|
|
5611
|
+
}
|
|
5612
|
+
const firstText = getFirstVisibleText($, body);
|
|
5613
|
+
return firstText || null;
|
|
5614
|
+
}
|
|
5615
|
+
function getFirstVisibleText($, body) {
|
|
5616
|
+
const skipTags = /* @__PURE__ */ new Set(["style", "script", "head", "title"]);
|
|
5617
|
+
let result = null;
|
|
5618
|
+
body.find("*").each((_, el) => {
|
|
5619
|
+
var _a;
|
|
5620
|
+
if (result) return false;
|
|
5621
|
+
const tagName = (_a = el.tagName) == null ? void 0 : _a.toLowerCase();
|
|
5622
|
+
if (skipTags.has(tagName)) return;
|
|
5623
|
+
const style = $(el).attr("style") || "";
|
|
5624
|
+
if (style.includes("display:none") || style.includes("display: none") || style.includes("visibility:hidden") || style.includes("visibility: hidden")) {
|
|
5625
|
+
return;
|
|
5626
|
+
}
|
|
5627
|
+
const directText = $(el).contents().filter((_2, node) => node.type === "text").text().trim();
|
|
5628
|
+
if (directText && directText.length > 1) {
|
|
5629
|
+
const cleaned = directText.replace(/[\u200B\u00A0\s]+/g, " ").trim();
|
|
5630
|
+
if (cleaned.length > 1) {
|
|
5631
|
+
result = cleaned;
|
|
5632
|
+
return false;
|
|
5633
|
+
}
|
|
5634
|
+
}
|
|
5635
|
+
});
|
|
5636
|
+
return result;
|
|
5637
|
+
}
|
|
5638
|
+
|
|
5639
|
+
// src/size-checker.ts
|
|
5640
|
+
import * as cheerio9 from "cheerio";
|
|
5641
|
+
function humanizeBytes(bytes) {
|
|
5642
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
5643
|
+
const kb = bytes / 1024;
|
|
5644
|
+
if (kb < 1024) return `${kb.toFixed(1)} KB`;
|
|
5645
|
+
const mb = kb / 1024;
|
|
5646
|
+
return `${mb.toFixed(2)} MB`;
|
|
5647
|
+
}
|
|
5648
|
+
function checkSizeFromDom(_$, html) {
|
|
5649
|
+
const htmlBytes = new TextEncoder().encode(html).length;
|
|
5650
|
+
const humanSize = humanizeBytes(htmlBytes);
|
|
5651
|
+
const issues = [];
|
|
5652
|
+
let clipped = false;
|
|
5653
|
+
if (htmlBytes > GMAIL_CLIP_THRESHOLD) {
|
|
5654
|
+
clipped = true;
|
|
5655
|
+
issues.push({
|
|
5656
|
+
rule: "gmail-clipped",
|
|
5657
|
+
severity: "error",
|
|
5658
|
+
message: `Email is ${humanSize} \u2014 Gmail will clip it at ~102 KB. Recipients see a "View entire message" link instead of your content.`,
|
|
5659
|
+
detail: `${htmlBytes} bytes exceeds the ${GMAIL_CLIP_THRESHOLD} byte threshold.`
|
|
5660
|
+
});
|
|
5661
|
+
} else if (htmlBytes > GMAIL_CLIP_WARNING_THRESHOLD) {
|
|
5662
|
+
issues.push({
|
|
5663
|
+
rule: "gmail-clip-warning",
|
|
5664
|
+
severity: "warning",
|
|
5665
|
+
message: `Email is ${humanSize} \u2014 approaching Gmail's ~102 KB clip threshold. Consider trimming.`,
|
|
5666
|
+
detail: `${htmlBytes} bytes is within ${GMAIL_CLIP_THRESHOLD - htmlBytes} bytes of the clip threshold.`
|
|
5667
|
+
});
|
|
5668
|
+
}
|
|
5669
|
+
return { htmlBytes, humanSize, clipped, issues };
|
|
5670
|
+
}
|
|
5671
|
+
function checkSize(html) {
|
|
5672
|
+
if (!html || !html.trim()) {
|
|
5673
|
+
return { htmlBytes: 0, humanSize: "0 B", clipped: false, issues: [] };
|
|
5674
|
+
}
|
|
5675
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
5676
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5677
|
+
}
|
|
5678
|
+
const $ = cheerio9.load(html);
|
|
5679
|
+
return checkSizeFromDom($, html);
|
|
5680
|
+
}
|
|
5681
|
+
|
|
5682
|
+
// src/template-checker.ts
|
|
5683
|
+
import * as cheerio10 from "cheerio";
|
|
5684
|
+
function checkTemplateVariablesFromDom($) {
|
|
5685
|
+
const issues = [];
|
|
5686
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5687
|
+
const textContent = extractTextContent($);
|
|
5688
|
+
for (const [pattern, label] of TEMPLATE_VARIABLE_PATTERNS) {
|
|
5689
|
+
pattern.lastIndex = 0;
|
|
5690
|
+
let match;
|
|
5691
|
+
while ((match = pattern.exec(textContent)) !== null) {
|
|
5692
|
+
const variable = match[0];
|
|
5693
|
+
const key = `text:${variable}`;
|
|
5694
|
+
if (seen.has(key)) continue;
|
|
5695
|
+
seen.add(key);
|
|
5696
|
+
issues.push({
|
|
5697
|
+
rule: "unresolved-variable",
|
|
5698
|
+
severity: "error",
|
|
5699
|
+
message: `Unresolved ${label} variable "${variable}" found in text content.`,
|
|
5700
|
+
variable,
|
|
5701
|
+
location: "text"
|
|
5702
|
+
});
|
|
5703
|
+
}
|
|
5704
|
+
}
|
|
5705
|
+
const attrSelectors = ["[href]", "[src]", "[alt]"];
|
|
5706
|
+
for (const sel of attrSelectors) {
|
|
5707
|
+
$(sel).each((_, el) => {
|
|
5708
|
+
const attrs = ["href", "src", "alt"];
|
|
5709
|
+
for (const attr of attrs) {
|
|
5710
|
+
const value = $(el).attr(attr);
|
|
5711
|
+
if (!value) continue;
|
|
5712
|
+
for (const [pattern, label] of TEMPLATE_VARIABLE_PATTERNS) {
|
|
5713
|
+
pattern.lastIndex = 0;
|
|
5714
|
+
let match;
|
|
5715
|
+
while ((match = pattern.exec(value)) !== null) {
|
|
5716
|
+
const variable = match[0];
|
|
5717
|
+
const key = `attr:${attr}:${variable}`;
|
|
5718
|
+
if (seen.has(key)) continue;
|
|
5719
|
+
seen.add(key);
|
|
5720
|
+
issues.push({
|
|
5721
|
+
rule: "unresolved-variable",
|
|
5722
|
+
severity: "error",
|
|
5723
|
+
message: `Unresolved ${label} variable "${variable}" found in ${attr} attribute.`,
|
|
5724
|
+
variable,
|
|
5725
|
+
location: "attribute"
|
|
5726
|
+
});
|
|
5727
|
+
}
|
|
5728
|
+
}
|
|
5729
|
+
}
|
|
5730
|
+
});
|
|
5731
|
+
}
|
|
5732
|
+
return { unresolvedCount: issues.length, issues };
|
|
5733
|
+
}
|
|
5734
|
+
function extractTextContent($) {
|
|
5735
|
+
const clone = $.root().clone();
|
|
5736
|
+
clone.find("style, script, head").remove();
|
|
5737
|
+
return clone.text();
|
|
5738
|
+
}
|
|
5739
|
+
function checkTemplateVariables(html) {
|
|
5740
|
+
if (!html || !html.trim()) {
|
|
5741
|
+
return { unresolvedCount: 0, issues: [] };
|
|
5742
|
+
}
|
|
5743
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
5744
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5745
|
+
}
|
|
5746
|
+
const $ = cheerio10.load(html);
|
|
5747
|
+
return checkTemplateVariablesFromDom($);
|
|
5748
|
+
}
|
|
5317
5749
|
|
|
5318
5750
|
// src/audit.ts
|
|
5319
|
-
|
|
5320
|
-
var EMPTY_LINKS = {
|
|
5321
|
-
totalLinks: 0,
|
|
5322
|
-
issues: [],
|
|
5323
|
-
breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
|
|
5324
|
-
};
|
|
5325
|
-
var EMPTY_ACCESSIBILITY = { score: 100, issues: [] };
|
|
5326
|
-
var EMPTY_IMAGES = { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
|
|
5751
|
+
import * as cheerio11 from "cheerio";
|
|
5327
5752
|
function auditEmail(html, options) {
|
|
5328
5753
|
var _a;
|
|
5329
5754
|
if (!html || !html.trim()) {
|
|
@@ -5332,7 +5757,10 @@ function auditEmail(html, options) {
|
|
|
5332
5757
|
spam: EMPTY_SPAM,
|
|
5333
5758
|
links: EMPTY_LINKS,
|
|
5334
5759
|
accessibility: EMPTY_ACCESSIBILITY,
|
|
5335
|
-
images: EMPTY_IMAGES
|
|
5760
|
+
images: EMPTY_IMAGES,
|
|
5761
|
+
inboxPreview: EMPTY_INBOX_PREVIEW,
|
|
5762
|
+
size: EMPTY_SIZE,
|
|
5763
|
+
templateVariables: EMPTY_TEMPLATE
|
|
5336
5764
|
};
|
|
5337
5765
|
}
|
|
5338
5766
|
if (html.length > MAX_HTML_SIZE) {
|
|
@@ -5340,13 +5768,111 @@ function auditEmail(html, options) {
|
|
|
5340
5768
|
}
|
|
5341
5769
|
const framework = options == null ? void 0 : options.framework;
|
|
5342
5770
|
const skip = new Set((_a = options == null ? void 0 : options.skip) != null ? _a : []);
|
|
5343
|
-
const
|
|
5771
|
+
const $ = cheerio11.load(html);
|
|
5772
|
+
const warnings = skip.has("compatibility") ? [] : analyzeEmailFromDom($, framework);
|
|
5344
5773
|
const scores = skip.has("compatibility") ? {} : generateCompatibilityScore(warnings);
|
|
5345
|
-
const spam = skip.has("spam") ? EMPTY_SPAM :
|
|
5346
|
-
const links = skip.has("links") ? EMPTY_LINKS :
|
|
5347
|
-
const accessibility = skip.has("accessibility") ? EMPTY_ACCESSIBILITY :
|
|
5348
|
-
const images = skip.has("images") ? EMPTY_IMAGES :
|
|
5349
|
-
|
|
5774
|
+
const spam = skip.has("spam") ? EMPTY_SPAM : analyzeSpamFromDom($, options == null ? void 0 : options.spam);
|
|
5775
|
+
const links = skip.has("links") ? EMPTY_LINKS : validateLinksFromDom($);
|
|
5776
|
+
const accessibility = skip.has("accessibility") ? EMPTY_ACCESSIBILITY : checkAccessibilityFromDom($);
|
|
5777
|
+
const images = skip.has("images") ? EMPTY_IMAGES : analyzeImagesFromDom($);
|
|
5778
|
+
const inboxPreview = skip.has("inboxPreview") ? EMPTY_INBOX_PREVIEW : extractInboxPreviewFromDom($);
|
|
5779
|
+
const size = skip.has("size") ? EMPTY_SIZE : checkSizeFromDom($, html);
|
|
5780
|
+
const templateVariables = skip.has("templateVariables") ? EMPTY_TEMPLATE : checkTemplateVariablesFromDom($);
|
|
5781
|
+
return { compatibility: { warnings, scores }, spam, links, accessibility, images, inboxPreview, size, templateVariables };
|
|
5782
|
+
}
|
|
5783
|
+
|
|
5784
|
+
// src/session.ts
|
|
5785
|
+
import * as cheerio12 from "cheerio";
|
|
5786
|
+
function createSession(html, options) {
|
|
5787
|
+
if (!html || !html.trim()) {
|
|
5788
|
+
const fw = options == null ? void 0 : options.framework;
|
|
5789
|
+
return {
|
|
5790
|
+
html: html || "",
|
|
5791
|
+
framework: fw,
|
|
5792
|
+
audit: () => ({
|
|
5793
|
+
compatibility: { warnings: [], scores: {} },
|
|
5794
|
+
spam: EMPTY_SPAM,
|
|
5795
|
+
links: EMPTY_LINKS,
|
|
5796
|
+
accessibility: EMPTY_ACCESSIBILITY,
|
|
5797
|
+
images: EMPTY_IMAGES,
|
|
5798
|
+
inboxPreview: EMPTY_INBOX_PREVIEW,
|
|
5799
|
+
size: EMPTY_SIZE,
|
|
5800
|
+
templateVariables: EMPTY_TEMPLATE
|
|
5801
|
+
}),
|
|
5802
|
+
analyze: () => [],
|
|
5803
|
+
score: () => ({}),
|
|
5804
|
+
analyzeSpam: () => EMPTY_SPAM,
|
|
5805
|
+
validateLinks: () => EMPTY_LINKS,
|
|
5806
|
+
checkAccessibility: () => EMPTY_ACCESSIBILITY,
|
|
5807
|
+
analyzeImages: () => EMPTY_IMAGES,
|
|
5808
|
+
extractInboxPreview: () => EMPTY_INBOX_PREVIEW,
|
|
5809
|
+
checkSize: () => EMPTY_SIZE,
|
|
5810
|
+
checkTemplateVariables: () => EMPTY_TEMPLATE,
|
|
5811
|
+
transformForClient: (clientId) => ({ clientId, html: html || "", warnings: [] }),
|
|
5812
|
+
transformForAllClients: () => [],
|
|
5813
|
+
simulateDarkMode: (clientId) => ({ html: html || "", warnings: [] })
|
|
5814
|
+
};
|
|
5815
|
+
}
|
|
5816
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
5817
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
5818
|
+
}
|
|
5819
|
+
const $ = cheerio12.load(html);
|
|
5820
|
+
const framework = options == null ? void 0 : options.framework;
|
|
5821
|
+
return {
|
|
5822
|
+
html,
|
|
5823
|
+
framework,
|
|
5824
|
+
audit(opts) {
|
|
5825
|
+
var _a;
|
|
5826
|
+
const skip = new Set((_a = opts == null ? void 0 : opts.skip) != null ? _a : []);
|
|
5827
|
+
const warnings = skip.has("compatibility") ? [] : analyzeEmailFromDom($, framework);
|
|
5828
|
+
const scores = skip.has("compatibility") ? {} : generateCompatibilityScore(warnings);
|
|
5829
|
+
const spam = skip.has("spam") ? EMPTY_SPAM : analyzeSpamFromDom($, opts == null ? void 0 : opts.spam);
|
|
5830
|
+
const links = skip.has("links") ? EMPTY_LINKS : validateLinksFromDom($);
|
|
5831
|
+
const accessibility = skip.has("accessibility") ? EMPTY_ACCESSIBILITY : checkAccessibilityFromDom($);
|
|
5832
|
+
const images = skip.has("images") ? EMPTY_IMAGES : analyzeImagesFromDom($);
|
|
5833
|
+
const inboxPreview = skip.has("inboxPreview") ? EMPTY_INBOX_PREVIEW : extractInboxPreviewFromDom($);
|
|
5834
|
+
const size = skip.has("size") ? EMPTY_SIZE : checkSizeFromDom($, html);
|
|
5835
|
+
const templateVariables = skip.has("templateVariables") ? EMPTY_TEMPLATE : checkTemplateVariablesFromDom($);
|
|
5836
|
+
return { compatibility: { warnings, scores }, spam, links, accessibility, images, inboxPreview, size, templateVariables };
|
|
5837
|
+
},
|
|
5838
|
+
analyze() {
|
|
5839
|
+
return analyzeEmailFromDom($, framework);
|
|
5840
|
+
},
|
|
5841
|
+
score(warnings) {
|
|
5842
|
+
return generateCompatibilityScore(warnings);
|
|
5843
|
+
},
|
|
5844
|
+
analyzeSpam(opts) {
|
|
5845
|
+
return analyzeSpamFromDom($, opts);
|
|
5846
|
+
},
|
|
5847
|
+
validateLinks() {
|
|
5848
|
+
return validateLinksFromDom($);
|
|
5849
|
+
},
|
|
5850
|
+
checkAccessibility() {
|
|
5851
|
+
return checkAccessibilityFromDom($);
|
|
5852
|
+
},
|
|
5853
|
+
analyzeImages() {
|
|
5854
|
+
return analyzeImagesFromDom($);
|
|
5855
|
+
},
|
|
5856
|
+
extractInboxPreview() {
|
|
5857
|
+
return extractInboxPreviewFromDom($);
|
|
5858
|
+
},
|
|
5859
|
+
checkSize() {
|
|
5860
|
+
return checkSizeFromDom($, html);
|
|
5861
|
+
},
|
|
5862
|
+
checkTemplateVariables() {
|
|
5863
|
+
return checkTemplateVariablesFromDom($);
|
|
5864
|
+
},
|
|
5865
|
+
// Transforms create isolated copies since they mutate the DOM
|
|
5866
|
+
transformForClient(clientId) {
|
|
5867
|
+
return transformForClient(html, clientId, framework);
|
|
5868
|
+
},
|
|
5869
|
+
transformForAllClients() {
|
|
5870
|
+
return transformForAllClients(html, framework);
|
|
5871
|
+
},
|
|
5872
|
+
simulateDarkMode(clientId) {
|
|
5873
|
+
return simulateDarkMode(html, clientId);
|
|
5874
|
+
}
|
|
5875
|
+
};
|
|
5350
5876
|
}
|
|
5351
5877
|
export {
|
|
5352
5878
|
AI_FIX_SYSTEM_PROMPT,
|
|
@@ -5361,10 +5887,14 @@ export {
|
|
|
5361
5887
|
analyzeSpam,
|
|
5362
5888
|
auditEmail,
|
|
5363
5889
|
checkAccessibility,
|
|
5890
|
+
checkSize,
|
|
5891
|
+
checkTemplateVariables,
|
|
5364
5892
|
contrastRatio,
|
|
5893
|
+
createSession,
|
|
5365
5894
|
diffResults,
|
|
5366
5895
|
errorWarnings,
|
|
5367
5896
|
estimateAiFixTokens,
|
|
5897
|
+
extractInboxPreview,
|
|
5368
5898
|
generateAiFix,
|
|
5369
5899
|
generateCompatibilityScore,
|
|
5370
5900
|
generateFixPrompt,
|