@emailens/engine 0.8.5 → 0.8.6
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/index.cjs +1721 -1051
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -3
- package/dist/index.d.ts +41 -3
- package/dist/index.js +1718 -1051
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -82,9 +82,11 @@ __export(index_exports, {
|
|
|
82
82
|
contrastRatio: () => contrastRatio,
|
|
83
83
|
createSession: () => createSession,
|
|
84
84
|
diffResults: () => diffResults,
|
|
85
|
+
downlevelCSS: () => downlevelCSS,
|
|
85
86
|
errorWarnings: () => errorWarnings,
|
|
86
87
|
estimateAiFixTokens: () => estimateAiFixTokens,
|
|
87
88
|
extractInboxPreview: () => extractInboxPreview,
|
|
89
|
+
formatRgb: () => formatRgb,
|
|
88
90
|
generateAiFix: () => generateAiFix,
|
|
89
91
|
generateCompatibilityScore: () => generateCompatibilityScore,
|
|
90
92
|
generateFixPrompt: () => generateFixPrompt,
|
|
@@ -96,6 +98,7 @@ __export(index_exports, {
|
|
|
96
98
|
relativeLuminance: () => relativeLuminance,
|
|
97
99
|
simulateDarkMode: () => simulateDarkMode,
|
|
98
100
|
structuralWarnings: () => structuralWarnings,
|
|
101
|
+
toPlainText: () => toPlainText,
|
|
99
102
|
transformForAllClients: () => transformForAllClients,
|
|
100
103
|
transformForClient: () => transformForClient,
|
|
101
104
|
validateLinks: () => validateLinks,
|
|
@@ -217,8 +220,8 @@ function getClient(id) {
|
|
|
217
220
|
}
|
|
218
221
|
|
|
219
222
|
// src/transform.ts
|
|
220
|
-
var
|
|
221
|
-
var
|
|
223
|
+
var cheerio2 = __toESM(require("cheerio"), 1);
|
|
224
|
+
var csstree2 = __toESM(require("css-tree"), 1);
|
|
222
225
|
|
|
223
226
|
// src/rules/css-support.ts
|
|
224
227
|
var CSS_SUPPORT = {
|
|
@@ -4743,6 +4746,48 @@ h1 {
|
|
|
4743
4746
|
Shortened content...
|
|
4744
4747
|
<a href="https://example.com/full">Read more</a>
|
|
4745
4748
|
</div>`
|
|
4749
|
+
},
|
|
4750
|
+
// ── padding (Outlook MSO font-width trick) ─────────────────────────────
|
|
4751
|
+
"padding::outlook": {
|
|
4752
|
+
language: "html",
|
|
4753
|
+
description: "Use MSO font-width trick for reliable button padding in Outlook",
|
|
4754
|
+
before: `<a href="https://example.com"
|
|
4755
|
+
style="background-color: #6d28d9; color: #fff;
|
|
4756
|
+
padding: 12px 32px; display: inline-block;
|
|
4757
|
+
text-decoration: none;">
|
|
4758
|
+
Click Here
|
|
4759
|
+
</a>`,
|
|
4760
|
+
after: `<!--[if mso]>
|
|
4761
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0"
|
|
4762
|
+
style="border-collapse: collapse;">
|
|
4763
|
+
<tr>
|
|
4764
|
+
<td style="background-color: #6d28d9; padding: 0;
|
|
4765
|
+
mso-padding-alt: 12px 32px;">
|
|
4766
|
+
<!--[if mso]>
|
|
4767
|
+
<i style="letter-spacing: 32px; mso-font-width: -100%;
|
|
4768
|
+
mso-text-raise: 18pt;" hidden> </i>
|
|
4769
|
+
<![endif]-->
|
|
4770
|
+
<a href="https://example.com"
|
|
4771
|
+
style="background-color: #6d28d9; color: #fff;
|
|
4772
|
+
mso-text-raise: 9pt; text-decoration: none;
|
|
4773
|
+
font-family: Arial, sans-serif; font-size: 14px;
|
|
4774
|
+
font-weight: bold;">Click Here</a>
|
|
4775
|
+
<!--[if mso]>
|
|
4776
|
+
<i style="letter-spacing: 32px; mso-font-width: -100%;"
|
|
4777
|
+
hidden> </i>
|
|
4778
|
+
<![endif]-->
|
|
4779
|
+
</td>
|
|
4780
|
+
</tr>
|
|
4781
|
+
</table>
|
|
4782
|
+
<![endif]-->
|
|
4783
|
+
<!--[if !mso]><!-->
|
|
4784
|
+
<a href="https://example.com"
|
|
4785
|
+
style="background-color: #6d28d9; color: #fff;
|
|
4786
|
+
padding: 12px 32px; display: inline-block;
|
|
4787
|
+
text-decoration: none;">
|
|
4788
|
+
Click Here
|
|
4789
|
+
</a>
|
|
4790
|
+
<!--<![endif]-->`
|
|
4746
4791
|
}
|
|
4747
4792
|
};
|
|
4748
4793
|
|
|
@@ -5285,6 +5330,35 @@ var JSX_FIX_DATABASE = {
|
|
|
5285
5330
|
Content \u2014 padding on inner element
|
|
5286
5331
|
</div>
|
|
5287
5332
|
</div>`
|
|
5333
|
+
},
|
|
5334
|
+
// ── padding (Outlook MSO font-width trick, JSX) ───────────────────────
|
|
5335
|
+
"padding::outlook::jsx": {
|
|
5336
|
+
language: "jsx",
|
|
5337
|
+
description: "Use React Email Button component which handles MSO padding automatically",
|
|
5338
|
+
before: `<a
|
|
5339
|
+
href="https://example.com"
|
|
5340
|
+
style={{ backgroundColor: "#6d28d9", color: "#fff",
|
|
5341
|
+
padding: "12px 32px", display: "inline-block",
|
|
5342
|
+
textDecoration: "none" }}>
|
|
5343
|
+
Click Here
|
|
5344
|
+
</a>`,
|
|
5345
|
+
after: `import { Button } from "@react-email/components";
|
|
5346
|
+
|
|
5347
|
+
{/* The Button component automatically handles MSO padding
|
|
5348
|
+
via the mso-font-width +   technique */}
|
|
5349
|
+
<Button
|
|
5350
|
+
href="https://example.com"
|
|
5351
|
+
style={{
|
|
5352
|
+
backgroundColor: "#6d28d9",
|
|
5353
|
+
color: "#fff",
|
|
5354
|
+
padding: "12px 32px",
|
|
5355
|
+
borderRadius: "6px",
|
|
5356
|
+
textDecoration: "none",
|
|
5357
|
+
fontWeight: "bold",
|
|
5358
|
+
}}
|
|
5359
|
+
>
|
|
5360
|
+
Click Here
|
|
5361
|
+
</Button>`
|
|
5288
5362
|
}
|
|
5289
5363
|
};
|
|
5290
5364
|
|
|
@@ -5582,7 +5656,9 @@ var HTML_SUGGESTION_DATABASE = {
|
|
|
5582
5656
|
"transition": "CSS transitions are not supported in email.",
|
|
5583
5657
|
"box-sizing": "Account for padding in your width calculations (use padding on a nested element).",
|
|
5584
5658
|
"object-fit": "Use width/height attributes on <img> directly.",
|
|
5585
|
-
"display": "Use tables for layout in email clients."
|
|
5659
|
+
"display": "Use tables for layout in email clients.",
|
|
5660
|
+
// ── padding ─────────────────────────────────────────────────────────────
|
|
5661
|
+
"padding::outlook": "Outlook ignores padding on <a> tags. Use the MSO font-width trick with   hair spaces, or use VML. React Email's <Button> handles this automatically."
|
|
5586
5662
|
};
|
|
5587
5663
|
|
|
5588
5664
|
// src/fix-snippets/jsx-suggestions.ts
|
|
@@ -5627,7 +5703,9 @@ var JSX_SUGGESTION_DATABASE = {
|
|
|
5627
5703
|
// ── word-break ────────────────────────────────────────────────────────
|
|
5628
5704
|
"word-break::jsx": "Wrap long text in a <table><tr><td> element. Outlook ignores wordBreak but respects table cell widths.",
|
|
5629
5705
|
// ── overflow-wrap ─────────────────────────────────────────────────────
|
|
5630
|
-
"overflow-wrap::jsx": "Wrap text in a <table><tr><td> element. Outlook ignores overflowWrap but respects table cell widths."
|
|
5706
|
+
"overflow-wrap::jsx": "Wrap text in a <table><tr><td> element. Outlook ignores overflowWrap but respects table cell widths.",
|
|
5707
|
+
// ── padding ─────────────────────────────────────────────────────────────
|
|
5708
|
+
"padding::outlook::jsx": "Use React Email's <Button> component which automatically generates MSO-compatible padding using the mso-font-width trick."
|
|
5631
5709
|
};
|
|
5632
5710
|
|
|
5633
5711
|
// src/fix-snippets/mjml-suggestions.ts
|
|
@@ -5849,1103 +5927,1647 @@ function serializeStyle(map) {
|
|
|
5849
5927
|
return parts.join("; ");
|
|
5850
5928
|
}
|
|
5851
5929
|
|
|
5852
|
-
// src/
|
|
5853
|
-
var
|
|
5854
|
-
var
|
|
5855
|
-
"click here",
|
|
5856
|
-
"here",
|
|
5857
|
-
"read more",
|
|
5858
|
-
"learn more",
|
|
5859
|
-
"more",
|
|
5860
|
-
"link",
|
|
5861
|
-
"this link",
|
|
5862
|
-
"click",
|
|
5863
|
-
"tap here",
|
|
5864
|
-
"this"
|
|
5865
|
-
]);
|
|
5866
|
-
var GMAIL_CLIP_THRESHOLD = 102 * 1024;
|
|
5867
|
-
var GMAIL_CLIP_WARNING_THRESHOLD = 90 * 1024;
|
|
5868
|
-
var CLIENT_DISPLAY_LIMITS = [
|
|
5869
|
-
{ client: "Gmail (Web)", subjectLimit: 70, preheaderLimit: 90 },
|
|
5870
|
-
{ client: "Gmail (Mobile)", subjectLimit: 40, preheaderLimit: 45 },
|
|
5871
|
-
{ client: "Outlook (Web)", subjectLimit: 60, preheaderLimit: 90 },
|
|
5872
|
-
{ client: "Outlook (Desktop)", subjectLimit: 55, preheaderLimit: 35 },
|
|
5873
|
-
{ client: "Apple Mail (macOS)", subjectLimit: 78, preheaderLimit: 140 },
|
|
5874
|
-
{ client: "Apple Mail (iOS)", subjectLimit: 41, preheaderLimit: 90 },
|
|
5875
|
-
{ client: "Yahoo Mail", subjectLimit: 46, preheaderLimit: 100 },
|
|
5876
|
-
{ client: "Samsung Email", subjectLimit: 40, preheaderLimit: 70 }
|
|
5877
|
-
];
|
|
5878
|
-
var TEMPLATE_VARIABLE_PATTERNS = [
|
|
5879
|
-
[/\{\{[\s\S]*?\}\}/g, "Handlebars/Mustache"],
|
|
5880
|
-
// {{var}}
|
|
5881
|
-
[/\$\{[^}]+\}/g, "ES template literal"],
|
|
5882
|
-
// ${var}
|
|
5883
|
-
[/<%=?\s*[^%]+%>/g, "ERB/EJS"],
|
|
5884
|
-
// <% %> / <%= %>
|
|
5885
|
-
[/\*\|[A-Z_][A-Z0-9_]*\|\*/g, "Mailchimp merge tag"],
|
|
5886
|
-
// *|TAG|*
|
|
5887
|
-
[/%%[A-Za-z_][A-Za-z0-9_]*%%/g, "Salesforce AMPscript"],
|
|
5888
|
-
// %%tag%%
|
|
5889
|
-
[/\{[A-Za-z_][A-Za-z0-9_.]{2,}\}/g, "Single-brace merge field"]
|
|
5890
|
-
// {merge_field} (3+ char names)
|
|
5891
|
-
];
|
|
5892
|
-
var EMPTY_SPAM = { score: 100, level: "low", issues: [] };
|
|
5893
|
-
var EMPTY_LINKS = {
|
|
5894
|
-
totalLinks: 0,
|
|
5895
|
-
issues: [],
|
|
5896
|
-
breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
|
|
5897
|
-
};
|
|
5898
|
-
var EMPTY_ACCESSIBILITY = { score: 100, issues: [] };
|
|
5899
|
-
var EMPTY_IMAGES = { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
|
|
5900
|
-
var EMPTY_INBOX_PREVIEW = { subject: null, preheader: null, subjectLength: 0, preheaderLength: 0, truncation: [], issues: [] };
|
|
5901
|
-
var EMPTY_SIZE = { htmlBytes: 0, humanSize: "0 B", clipped: false, issues: [] };
|
|
5902
|
-
var EMPTY_TEMPLATE = { unresolvedCount: 0, issues: [] };
|
|
5903
|
-
var EMPTY_DELIVERABILITY = { domain: "", checks: [], score: 0, issues: [] };
|
|
5930
|
+
// src/downlevel.ts
|
|
5931
|
+
var cheerio = __toESM(require("cheerio"), 1);
|
|
5932
|
+
var csstree = __toESM(require("css-tree"), 1);
|
|
5904
5933
|
|
|
5905
|
-
// src/
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
}, "background-image", clientId, framework));
|
|
6062
|
-
}
|
|
6063
|
-
return warnings;
|
|
6064
|
-
}
|
|
6065
|
-
function appleMailAdditionalChecks($, clientId) {
|
|
6066
|
-
const warnings = [];
|
|
6067
|
-
const imgsWithTransparentBg = $("img").filter((_, el) => {
|
|
6068
|
-
const src = $(el).attr("src") || "";
|
|
6069
|
-
return src.endsWith(".png") || src.endsWith(".svg");
|
|
6070
|
-
});
|
|
6071
|
-
if (imgsWithTransparentBg.length > 0) {
|
|
6072
|
-
warnings.push({
|
|
6073
|
-
severity: "info",
|
|
6074
|
-
client: clientId,
|
|
6075
|
-
property: "dark-mode",
|
|
6076
|
-
message: "PNG/SVG images with transparent backgrounds may become invisible in Apple Mail dark mode.",
|
|
6077
|
-
suggestion: "Add a white background or padding around images, or use dark-mode-friendly image variants."
|
|
6078
|
-
});
|
|
5934
|
+
// src/color-utils.ts
|
|
5935
|
+
var NAMED_COLORS = {
|
|
5936
|
+
aliceblue: [240, 248, 255],
|
|
5937
|
+
antiquewhite: [250, 235, 215],
|
|
5938
|
+
aqua: [0, 255, 255],
|
|
5939
|
+
aquamarine: [127, 255, 212],
|
|
5940
|
+
azure: [240, 255, 255],
|
|
5941
|
+
beige: [245, 245, 220],
|
|
5942
|
+
bisque: [255, 228, 196],
|
|
5943
|
+
black: [0, 0, 0],
|
|
5944
|
+
blanchedalmond: [255, 235, 205],
|
|
5945
|
+
blue: [0, 0, 255],
|
|
5946
|
+
blueviolet: [138, 43, 226],
|
|
5947
|
+
brown: [165, 42, 42],
|
|
5948
|
+
burlywood: [222, 184, 135],
|
|
5949
|
+
cadetblue: [95, 158, 160],
|
|
5950
|
+
chartreuse: [127, 255, 0],
|
|
5951
|
+
chocolate: [210, 105, 30],
|
|
5952
|
+
coral: [255, 127, 80],
|
|
5953
|
+
cornflowerblue: [100, 149, 237],
|
|
5954
|
+
cornsilk: [255, 248, 220],
|
|
5955
|
+
crimson: [220, 20, 60],
|
|
5956
|
+
cyan: [0, 255, 255],
|
|
5957
|
+
darkblue: [0, 0, 139],
|
|
5958
|
+
darkcyan: [0, 139, 139],
|
|
5959
|
+
darkgoldenrod: [184, 134, 11],
|
|
5960
|
+
darkgray: [169, 169, 169],
|
|
5961
|
+
darkgreen: [0, 100, 0],
|
|
5962
|
+
darkgrey: [169, 169, 169],
|
|
5963
|
+
darkkhaki: [189, 183, 107],
|
|
5964
|
+
darkmagenta: [139, 0, 139],
|
|
5965
|
+
darkolivegreen: [85, 107, 47],
|
|
5966
|
+
darkorange: [255, 140, 0],
|
|
5967
|
+
darkorchid: [153, 50, 204],
|
|
5968
|
+
darkred: [139, 0, 0],
|
|
5969
|
+
darksalmon: [233, 150, 122],
|
|
5970
|
+
darkseagreen: [143, 188, 143],
|
|
5971
|
+
darkslateblue: [72, 61, 139],
|
|
5972
|
+
darkslategray: [47, 79, 79],
|
|
5973
|
+
darkslategrey: [47, 79, 79],
|
|
5974
|
+
darkturquoise: [0, 206, 209],
|
|
5975
|
+
darkviolet: [148, 0, 211],
|
|
5976
|
+
deeppink: [255, 20, 147],
|
|
5977
|
+
deepskyblue: [0, 191, 255],
|
|
5978
|
+
dimgray: [105, 105, 105],
|
|
5979
|
+
dimgrey: [105, 105, 105],
|
|
5980
|
+
dodgerblue: [30, 144, 255],
|
|
5981
|
+
firebrick: [178, 34, 34],
|
|
5982
|
+
floralwhite: [255, 250, 240],
|
|
5983
|
+
forestgreen: [34, 139, 34],
|
|
5984
|
+
fuchsia: [255, 0, 255],
|
|
5985
|
+
gainsboro: [220, 220, 220],
|
|
5986
|
+
ghostwhite: [248, 248, 255],
|
|
5987
|
+
gold: [255, 215, 0],
|
|
5988
|
+
goldenrod: [218, 165, 32],
|
|
5989
|
+
gray: [128, 128, 128],
|
|
5990
|
+
green: [0, 128, 0],
|
|
5991
|
+
greenyellow: [173, 255, 47],
|
|
5992
|
+
grey: [128, 128, 128],
|
|
5993
|
+
honeydew: [240, 255, 240],
|
|
5994
|
+
hotpink: [255, 105, 180],
|
|
5995
|
+
indianred: [205, 92, 92],
|
|
5996
|
+
indigo: [75, 0, 130],
|
|
5997
|
+
ivory: [255, 255, 240],
|
|
5998
|
+
khaki: [240, 230, 140],
|
|
5999
|
+
lavender: [230, 230, 250],
|
|
6000
|
+
lavenderblush: [255, 240, 245],
|
|
6001
|
+
lawngreen: [124, 252, 0],
|
|
6002
|
+
lemonchiffon: [255, 250, 205],
|
|
6003
|
+
lightblue: [173, 216, 230],
|
|
6004
|
+
lightcoral: [240, 128, 128],
|
|
6005
|
+
lightcyan: [224, 255, 255],
|
|
6006
|
+
lightgoldenrodyellow: [250, 250, 210],
|
|
6007
|
+
lightgray: [211, 211, 211],
|
|
6008
|
+
lightgreen: [144, 238, 144],
|
|
6009
|
+
lightgrey: [211, 211, 211],
|
|
6010
|
+
lightpink: [255, 182, 193],
|
|
6011
|
+
lightsalmon: [255, 160, 122],
|
|
6012
|
+
lightseagreen: [32, 178, 170],
|
|
6013
|
+
lightskyblue: [135, 206, 250],
|
|
6014
|
+
lightslategray: [119, 136, 153],
|
|
6015
|
+
lightslategrey: [119, 136, 153],
|
|
6016
|
+
lightsteelblue: [176, 196, 222],
|
|
6017
|
+
lightyellow: [255, 255, 224],
|
|
6018
|
+
lime: [0, 255, 0],
|
|
6019
|
+
limegreen: [50, 205, 50],
|
|
6020
|
+
linen: [250, 240, 230],
|
|
6021
|
+
magenta: [255, 0, 255],
|
|
6022
|
+
maroon: [128, 0, 0],
|
|
6023
|
+
mediumaquamarine: [102, 205, 170],
|
|
6024
|
+
mediumblue: [0, 0, 205],
|
|
6025
|
+
mediumorchid: [186, 85, 211],
|
|
6026
|
+
mediumpurple: [147, 111, 219],
|
|
6027
|
+
mediumseagreen: [60, 179, 113],
|
|
6028
|
+
mediumslateblue: [123, 104, 238],
|
|
6029
|
+
mediumspringgreen: [0, 250, 154],
|
|
6030
|
+
mediumturquoise: [72, 209, 204],
|
|
6031
|
+
mediumvioletred: [199, 21, 133],
|
|
6032
|
+
midnightblue: [25, 25, 112],
|
|
6033
|
+
mintcream: [245, 255, 250],
|
|
6034
|
+
mistyrose: [255, 228, 225],
|
|
6035
|
+
moccasin: [255, 228, 181],
|
|
6036
|
+
navajowhite: [255, 222, 173],
|
|
6037
|
+
navy: [0, 0, 128],
|
|
6038
|
+
oldlace: [253, 245, 230],
|
|
6039
|
+
olive: [128, 128, 0],
|
|
6040
|
+
olivedrab: [107, 142, 35],
|
|
6041
|
+
orange: [255, 165, 0],
|
|
6042
|
+
orangered: [255, 69, 0],
|
|
6043
|
+
orchid: [218, 112, 214],
|
|
6044
|
+
palegoldenrod: [238, 232, 170],
|
|
6045
|
+
palegreen: [152, 251, 152],
|
|
6046
|
+
paleturquoise: [175, 238, 238],
|
|
6047
|
+
palevioletred: [219, 112, 147],
|
|
6048
|
+
papayawhip: [255, 239, 213],
|
|
6049
|
+
peachpuff: [255, 218, 185],
|
|
6050
|
+
peru: [205, 133, 63],
|
|
6051
|
+
pink: [255, 192, 203],
|
|
6052
|
+
plum: [221, 160, 221],
|
|
6053
|
+
powderblue: [176, 224, 230],
|
|
6054
|
+
purple: [128, 0, 128],
|
|
6055
|
+
rebeccapurple: [102, 51, 153],
|
|
6056
|
+
red: [255, 0, 0],
|
|
6057
|
+
rosybrown: [188, 143, 143],
|
|
6058
|
+
royalblue: [65, 105, 225],
|
|
6059
|
+
saddlebrown: [139, 69, 19],
|
|
6060
|
+
salmon: [250, 128, 114],
|
|
6061
|
+
sandybrown: [244, 164, 96],
|
|
6062
|
+
seagreen: [46, 139, 87],
|
|
6063
|
+
seashell: [255, 245, 238],
|
|
6064
|
+
sienna: [160, 82, 45],
|
|
6065
|
+
silver: [192, 192, 192],
|
|
6066
|
+
skyblue: [135, 206, 235],
|
|
6067
|
+
slateblue: [106, 90, 205],
|
|
6068
|
+
slategray: [112, 128, 144],
|
|
6069
|
+
slategrey: [112, 128, 144],
|
|
6070
|
+
snow: [255, 250, 250],
|
|
6071
|
+
springgreen: [0, 255, 127],
|
|
6072
|
+
steelblue: [70, 130, 180],
|
|
6073
|
+
tan: [210, 180, 140],
|
|
6074
|
+
teal: [0, 128, 128],
|
|
6075
|
+
thistle: [216, 191, 216],
|
|
6076
|
+
tomato: [255, 99, 71],
|
|
6077
|
+
turquoise: [64, 224, 208],
|
|
6078
|
+
violet: [238, 130, 238],
|
|
6079
|
+
wheat: [245, 222, 179],
|
|
6080
|
+
white: [255, 255, 255],
|
|
6081
|
+
whitesmoke: [245, 245, 245],
|
|
6082
|
+
yellow: [255, 255, 0],
|
|
6083
|
+
yellowgreen: [154, 205, 50]
|
|
6084
|
+
};
|
|
6085
|
+
function parseAlpha(raw) {
|
|
6086
|
+
if (raw === void 0) return 1;
|
|
6087
|
+
const s = raw.trim();
|
|
6088
|
+
if (s.endsWith("%")) {
|
|
6089
|
+
return Math.min(1, Math.max(0, parseFloat(s) / 100));
|
|
6079
6090
|
}
|
|
6080
|
-
return
|
|
6091
|
+
return Math.min(1, Math.max(0, parseFloat(s)));
|
|
6081
6092
|
}
|
|
6082
|
-
function
|
|
6083
|
-
|
|
6084
|
-
warnings.push({
|
|
6085
|
-
severity: "info",
|
|
6086
|
-
client: clientId,
|
|
6087
|
-
property: "class",
|
|
6088
|
-
message: "Yahoo Mail rewrites CSS class names with a prefix. Class-based selectors in <style> blocks will still work but the names change."
|
|
6089
|
-
});
|
|
6090
|
-
if ($("[style*='background']").filter(
|
|
6091
|
-
(_, el) => ($(el).attr("style") || "").includes("url(")
|
|
6092
|
-
).length > 0) {
|
|
6093
|
-
warnings.push(makeWarning({
|
|
6094
|
-
severity: "warning",
|
|
6095
|
-
client: clientId,
|
|
6096
|
-
property: "background-image",
|
|
6097
|
-
message: "Yahoo Mail has inconsistent support for CSS background images."
|
|
6098
|
-
}, "background-image", clientId, framework));
|
|
6099
|
-
}
|
|
6100
|
-
return warnings;
|
|
6093
|
+
function clamp255(n) {
|
|
6094
|
+
return Math.round(Math.min(255, Math.max(0, n)));
|
|
6101
6095
|
}
|
|
6102
|
-
function
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6096
|
+
function hslToRgb(h, s, l) {
|
|
6097
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
6098
|
+
const hp = h / 60;
|
|
6099
|
+
const x = c * (1 - Math.abs(hp % 2 - 1));
|
|
6100
|
+
const m = l - c / 2;
|
|
6101
|
+
let r1 = 0, g1 = 0, b1 = 0;
|
|
6102
|
+
if (hp >= 0 && hp < 1) {
|
|
6103
|
+
r1 = c;
|
|
6104
|
+
g1 = x;
|
|
6105
|
+
b1 = 0;
|
|
6106
|
+
} else if (hp >= 1 && hp < 2) {
|
|
6107
|
+
r1 = x;
|
|
6108
|
+
g1 = c;
|
|
6109
|
+
b1 = 0;
|
|
6110
|
+
} else if (hp >= 2 && hp < 3) {
|
|
6111
|
+
r1 = 0;
|
|
6112
|
+
g1 = c;
|
|
6113
|
+
b1 = x;
|
|
6114
|
+
} else if (hp >= 3 && hp < 4) {
|
|
6115
|
+
r1 = 0;
|
|
6116
|
+
g1 = x;
|
|
6117
|
+
b1 = c;
|
|
6118
|
+
} else if (hp >= 4 && hp < 5) {
|
|
6119
|
+
r1 = x;
|
|
6120
|
+
g1 = 0;
|
|
6121
|
+
b1 = c;
|
|
6122
|
+
} else {
|
|
6123
|
+
r1 = c;
|
|
6124
|
+
g1 = 0;
|
|
6125
|
+
b1 = x;
|
|
6110
6126
|
}
|
|
6111
|
-
return [
|
|
6127
|
+
return [
|
|
6128
|
+
clamp255((r1 + m) * 255),
|
|
6129
|
+
clamp255((g1 + m) * 255),
|
|
6130
|
+
clamp255((b1 + m) * 255)
|
|
6131
|
+
];
|
|
6112
6132
|
}
|
|
6113
|
-
function
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
severity: "info",
|
|
6118
|
-
client: clientId,
|
|
6119
|
-
property: "dark-mode",
|
|
6120
|
-
message: "HEY Mail supports @media (prefers-color-scheme: dark). Consider adding dark mode styles.",
|
|
6121
|
-
suggestion: "Add a @media (prefers-color-scheme: dark) block to optimize for HEY's audience."
|
|
6122
|
-
});
|
|
6133
|
+
function hwbToRgb(h, w, b) {
|
|
6134
|
+
if (w + b >= 1) {
|
|
6135
|
+
const gray = clamp255(w / (w + b) * 255);
|
|
6136
|
+
return [gray, gray, gray];
|
|
6123
6137
|
}
|
|
6124
|
-
|
|
6138
|
+
const [r0, g0, b0] = hslToRgb(h, 1, 0.5);
|
|
6139
|
+
return [
|
|
6140
|
+
clamp255(r0 / 255 * (1 - w - b) * 255 + w * 255),
|
|
6141
|
+
clamp255(g0 / 255 * (1 - w - b) * 255 + w * 255),
|
|
6142
|
+
clamp255(b0 / 255 * (1 - w - b) * 255 + w * 255)
|
|
6143
|
+
];
|
|
6125
6144
|
}
|
|
6126
|
-
function
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
warnings.push({
|
|
6130
|
-
severity: "info",
|
|
6131
|
-
client: clientId,
|
|
6132
|
-
property: "animation",
|
|
6133
|
-
message: "Superhuman may honor OS-level 'reduce motion' preferences, disabling animations.",
|
|
6134
|
-
suggestion: "Use @media (prefers-reduced-motion: reduce) to provide static fallbacks."
|
|
6135
|
-
});
|
|
6136
|
-
}
|
|
6137
|
-
warnings.push({
|
|
6138
|
-
severity: "info",
|
|
6139
|
-
client: clientId,
|
|
6140
|
-
property: "<style>",
|
|
6141
|
-
message: "Superhuman uses Chromium rendering with excellent CSS support. Flexbox, Grid, CSS variables, and modern properties all work."
|
|
6142
|
-
});
|
|
6143
|
-
return warnings;
|
|
6145
|
+
function linearToGamma(c) {
|
|
6146
|
+
if (c <= 31308e-7) return 12.92 * c;
|
|
6147
|
+
return 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
6144
6148
|
}
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
inlineAndStripStyles: true,
|
|
6170
|
-
stripExternalStylesheets: true,
|
|
6171
|
-
stripForms: true,
|
|
6172
|
-
stripSvg: true,
|
|
6173
|
-
additionalChecks: gmailAdditionalChecks
|
|
6174
|
-
},
|
|
6175
|
-
"gmail-ios": {
|
|
6176
|
-
id: "gmail-ios",
|
|
6177
|
-
strippedProperties: GMAIL_STRIPPED_PROPERTIES,
|
|
6178
|
-
stripMode: "strip",
|
|
6179
|
-
valueStrips: [
|
|
6180
|
-
{ prop: "display", pattern: /grid/ },
|
|
6181
|
-
{ prop: "background", pattern: /linear-gradient|radial-gradient/ }
|
|
6182
|
-
],
|
|
6183
|
-
inlineAndStripStyles: true,
|
|
6184
|
-
stripExternalStylesheets: true,
|
|
6185
|
-
stripForms: true,
|
|
6186
|
-
stripSvg: true,
|
|
6187
|
-
additionalChecks: gmailAdditionalChecks
|
|
6188
|
-
},
|
|
6189
|
-
"outlook-windows": {
|
|
6190
|
-
id: "outlook-windows",
|
|
6191
|
-
strippedProperties: /* @__PURE__ */ new Set(["position", "transform", "animation", "transition"]),
|
|
6192
|
-
stripMode: "strip",
|
|
6193
|
-
inlineAndStripStyles: false,
|
|
6194
|
-
stripExternalStylesheets: false,
|
|
6195
|
-
stripForms: false,
|
|
6196
|
-
stripSvg: false
|
|
6197
|
-
},
|
|
6198
|
-
"outlook-windows-legacy": {
|
|
6199
|
-
id: "outlook-windows-legacy",
|
|
6200
|
-
strippedProperties: OUTLOOK_WORD_UNSUPPORTED,
|
|
6201
|
-
stripMode: "strip",
|
|
6202
|
-
valueStrips: [
|
|
6203
|
-
{ prop: "background", pattern: /linear-gradient|radial-gradient/ },
|
|
6204
|
-
{ prop: "background-image", pattern: /linear-gradient|radial-gradient/ }
|
|
6205
|
-
],
|
|
6206
|
-
inlineAndStripStyles: false,
|
|
6207
|
-
stripExternalStylesheets: false,
|
|
6208
|
-
stripForms: false,
|
|
6209
|
-
stripSvg: false,
|
|
6210
|
-
additionalChecks: outlookWindowsAdditionalChecks
|
|
6211
|
-
},
|
|
6212
|
-
"outlook-web": {
|
|
6213
|
-
id: "outlook-web",
|
|
6214
|
-
strippedProperties: /* @__PURE__ */ new Set(["position", "transform", "animation", "transition"]),
|
|
6215
|
-
stripMode: "strip",
|
|
6216
|
-
inlineAndStripStyles: false,
|
|
6217
|
-
stripExternalStylesheets: false,
|
|
6218
|
-
stripForms: false,
|
|
6219
|
-
stripSvg: false
|
|
6220
|
-
},
|
|
6221
|
-
"apple-mail-macos": {
|
|
6222
|
-
id: "apple-mail-macos",
|
|
6223
|
-
strippedProperties: EMPTY_SET,
|
|
6224
|
-
stripMode: "strip",
|
|
6225
|
-
inlineAndStripStyles: false,
|
|
6226
|
-
stripExternalStylesheets: false,
|
|
6227
|
-
stripForms: false,
|
|
6228
|
-
stripSvg: false,
|
|
6229
|
-
additionalChecks: appleMailAdditionalChecks
|
|
6230
|
-
},
|
|
6231
|
-
"apple-mail-ios": {
|
|
6232
|
-
id: "apple-mail-ios",
|
|
6233
|
-
strippedProperties: EMPTY_SET,
|
|
6234
|
-
stripMode: "strip",
|
|
6235
|
-
inlineAndStripStyles: false,
|
|
6236
|
-
stripExternalStylesheets: false,
|
|
6237
|
-
stripForms: false,
|
|
6238
|
-
stripSvg: false,
|
|
6239
|
-
additionalChecks: appleMailAdditionalChecks
|
|
6240
|
-
},
|
|
6241
|
-
"yahoo-mail": {
|
|
6242
|
-
id: "yahoo-mail",
|
|
6243
|
-
strippedProperties: /* @__PURE__ */ new Set(["position", "box-shadow", "transform", "animation", "transition", "opacity"]),
|
|
6244
|
-
stripMode: "strip",
|
|
6245
|
-
inlineAndStripStyles: false,
|
|
6246
|
-
stripExternalStylesheets: false,
|
|
6247
|
-
stripForms: false,
|
|
6248
|
-
stripSvg: false,
|
|
6249
|
-
additionalChecks: yahooAdditionalChecks
|
|
6250
|
-
},
|
|
6251
|
-
"samsung-mail": {
|
|
6252
|
-
id: "samsung-mail",
|
|
6253
|
-
strippedProperties: /* @__PURE__ */ new Set(["box-shadow", "transform", "animation", "transition", "opacity"]),
|
|
6254
|
-
stripMode: "info",
|
|
6255
|
-
inlineAndStripStyles: false,
|
|
6256
|
-
stripExternalStylesheets: false,
|
|
6257
|
-
stripForms: false,
|
|
6258
|
-
stripSvg: false
|
|
6259
|
-
},
|
|
6260
|
-
"thunderbird": {
|
|
6261
|
-
id: "thunderbird",
|
|
6262
|
-
strippedProperties: EMPTY_SET,
|
|
6263
|
-
stripMode: "strip",
|
|
6264
|
-
inlineAndStripStyles: false,
|
|
6265
|
-
stripExternalStylesheets: false,
|
|
6266
|
-
stripForms: false,
|
|
6267
|
-
stripSvg: false,
|
|
6268
|
-
additionalChecks: thunderbirdAdditionalChecks
|
|
6269
|
-
},
|
|
6270
|
-
"hey-mail": {
|
|
6271
|
-
id: "hey-mail",
|
|
6272
|
-
strippedProperties: /* @__PURE__ */ new Set(["transform", "animation", "transition"]),
|
|
6273
|
-
stripMode: "strip",
|
|
6274
|
-
valueStrips: [
|
|
6275
|
-
{ prop: "position", pattern: /fixed|sticky/ }
|
|
6276
|
-
],
|
|
6277
|
-
inlineAndStripStyles: false,
|
|
6278
|
-
stripExternalStylesheets: true,
|
|
6279
|
-
stripForms: true,
|
|
6280
|
-
stripSvg: false,
|
|
6281
|
-
additionalChecks: heyAdditionalChecks
|
|
6282
|
-
},
|
|
6283
|
-
"superhuman": {
|
|
6284
|
-
id: "superhuman",
|
|
6285
|
-
strippedProperties: EMPTY_SET,
|
|
6286
|
-
stripMode: "strip",
|
|
6287
|
-
inlineAndStripStyles: false,
|
|
6288
|
-
stripExternalStylesheets: true,
|
|
6289
|
-
stripForms: true,
|
|
6290
|
-
stripSvg: false,
|
|
6291
|
-
additionalChecks: superhumanAdditionalChecks
|
|
6149
|
+
function oklchToRgb(L, C, H) {
|
|
6150
|
+
const hRad = H * Math.PI / 180;
|
|
6151
|
+
const a = C * Math.cos(hRad);
|
|
6152
|
+
const b = C * Math.sin(hRad);
|
|
6153
|
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
6154
|
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
6155
|
+
const s_ = L - 0.0894841775 * a - 1.291485548 * b;
|
|
6156
|
+
const l = l_ * l_ * l_;
|
|
6157
|
+
const m = m_ * m_ * m_;
|
|
6158
|
+
const s = s_ * s_ * s_;
|
|
6159
|
+
const rLin = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
|
6160
|
+
const gLin = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
|
6161
|
+
const bLin = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;
|
|
6162
|
+
return [
|
|
6163
|
+
clamp255(linearToGamma(rLin) * 255),
|
|
6164
|
+
clamp255(linearToGamma(gLin) * 255),
|
|
6165
|
+
clamp255(linearToGamma(bLin) * 255)
|
|
6166
|
+
];
|
|
6167
|
+
}
|
|
6168
|
+
function parseColor(value) {
|
|
6169
|
+
if (!value) return null;
|
|
6170
|
+
const v = value.trim().toLowerCase();
|
|
6171
|
+
if (v === "inherit" || v === "currentcolor" || v === "initial" || v === "unset" || v.startsWith("var(")) {
|
|
6172
|
+
return null;
|
|
6292
6173
|
}
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
const $ = cheerio.load(html);
|
|
6296
|
-
const warnings = [];
|
|
6297
|
-
const clientId = config.id;
|
|
6298
|
-
if (config.additionalChecks && config.inlineAndStripStyles) {
|
|
6299
|
-
warnings.push(...config.additionalChecks($, clientId, html, framework));
|
|
6174
|
+
if (v === "transparent") {
|
|
6175
|
+
return { r: 0, g: 0, b: 0, a: 0 };
|
|
6300
6176
|
}
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6177
|
+
const named = NAMED_COLORS[v];
|
|
6178
|
+
if (named) {
|
|
6179
|
+
return { r: named[0], g: named[1], b: named[2], a: 1 };
|
|
6304
6180
|
}
|
|
6305
|
-
if (
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6181
|
+
if (v.startsWith("#")) {
|
|
6182
|
+
const hex = v.slice(1);
|
|
6183
|
+
if (hex.length === 3) {
|
|
6184
|
+
return {
|
|
6185
|
+
r: parseInt(hex[0] + hex[0], 16),
|
|
6186
|
+
g: parseInt(hex[1] + hex[1], 16),
|
|
6187
|
+
b: parseInt(hex[2] + hex[2], 16),
|
|
6188
|
+
a: 1
|
|
6189
|
+
};
|
|
6314
6190
|
}
|
|
6191
|
+
if (hex.length === 4) {
|
|
6192
|
+
return {
|
|
6193
|
+
r: parseInt(hex[0] + hex[0], 16),
|
|
6194
|
+
g: parseInt(hex[1] + hex[1], 16),
|
|
6195
|
+
b: parseInt(hex[2] + hex[2], 16),
|
|
6196
|
+
a: parseInt(hex[3] + hex[3], 16) / 255
|
|
6197
|
+
};
|
|
6198
|
+
}
|
|
6199
|
+
if (hex.length === 6) {
|
|
6200
|
+
return {
|
|
6201
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
6202
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
6203
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
6204
|
+
a: 1
|
|
6205
|
+
};
|
|
6206
|
+
}
|
|
6207
|
+
if (hex.length === 8) {
|
|
6208
|
+
return {
|
|
6209
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
6210
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
6211
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
6212
|
+
a: parseInt(hex.slice(6, 8), 16) / 255
|
|
6213
|
+
};
|
|
6214
|
+
}
|
|
6215
|
+
return null;
|
|
6315
6216
|
}
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
$(el).replaceWith($(el).html() || "");
|
|
6325
|
-
});
|
|
6326
|
-
}
|
|
6327
|
-
if (config.stripSvg && $("svg").length > 0) {
|
|
6328
|
-
warnings.push(makeWarning({
|
|
6329
|
-
severity: "error",
|
|
6330
|
-
client: clientId,
|
|
6331
|
-
property: "<svg>",
|
|
6332
|
-
message: `${clientId} does not support inline SVG elements.`
|
|
6333
|
-
}, "<svg>", clientId, framework));
|
|
6334
|
-
$("svg").each((_, el) => {
|
|
6335
|
-
$(el).replaceWith('<img alt="[SVG not supported]" />');
|
|
6336
|
-
});
|
|
6337
|
-
}
|
|
6338
|
-
if (config.strippedProperties.size > 0 || config.valueStrips && config.valueStrips.length > 0) {
|
|
6339
|
-
$("[style]").each((_, el) => {
|
|
6340
|
-
const style = $(el).attr("style") || "";
|
|
6341
|
-
const props = parseInlineStyle(style);
|
|
6342
|
-
const removed = [];
|
|
6343
|
-
props.forEach((value, prop) => {
|
|
6344
|
-
var _a;
|
|
6345
|
-
if (config.strippedProperties.has(prop)) {
|
|
6346
|
-
if (config.stripMode === "strip") {
|
|
6347
|
-
removed.push(prop);
|
|
6348
|
-
props.delete(prop);
|
|
6349
|
-
} else {
|
|
6350
|
-
warnings.push(makeWarning({
|
|
6351
|
-
severity: "info",
|
|
6352
|
-
client: clientId,
|
|
6353
|
-
property: prop,
|
|
6354
|
-
message: `${clientId} has limited support for "${prop}".`
|
|
6355
|
-
}, prop, clientId, framework));
|
|
6356
|
-
}
|
|
6357
|
-
return;
|
|
6358
|
-
}
|
|
6359
|
-
for (const vs of (_a = config.valueStrips) != null ? _a : []) {
|
|
6360
|
-
if (prop === vs.prop && vs.pattern.test(value)) {
|
|
6361
|
-
removed.push(prop);
|
|
6362
|
-
props.delete(prop);
|
|
6363
|
-
return;
|
|
6364
|
-
}
|
|
6365
|
-
}
|
|
6366
|
-
});
|
|
6367
|
-
if (removed.length > 0) {
|
|
6368
|
-
$(el).attr("style", serializeStyle(props));
|
|
6369
|
-
for (const prop of removed) {
|
|
6370
|
-
warnings.push(makeWarning({
|
|
6371
|
-
severity: "warning",
|
|
6372
|
-
client: clientId,
|
|
6373
|
-
property: prop,
|
|
6374
|
-
message: `${clientId} strips "${prop}" from styles.`
|
|
6375
|
-
}, prop, clientId, framework));
|
|
6376
|
-
}
|
|
6377
|
-
}
|
|
6378
|
-
});
|
|
6217
|
+
const rgbComma = v.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+%?))?\s*\)$/);
|
|
6218
|
+
if (rgbComma) {
|
|
6219
|
+
return {
|
|
6220
|
+
r: Math.min(255, parseInt(rgbComma[1], 10)),
|
|
6221
|
+
g: Math.min(255, parseInt(rgbComma[2], 10)),
|
|
6222
|
+
b: Math.min(255, parseInt(rgbComma[3], 10)),
|
|
6223
|
+
a: rgbComma[4] !== void 0 ? parseAlpha(rgbComma[4]) : 1
|
|
6224
|
+
};
|
|
6379
6225
|
}
|
|
6380
|
-
|
|
6381
|
-
|
|
6226
|
+
const rgbSpace = v.match(/^rgba?\(\s*(\d+)\s+(\d+)\s+(\d+)(?:\s*\/\s*([\d.]+%?))?\s*\)$/);
|
|
6227
|
+
if (rgbSpace) {
|
|
6228
|
+
return {
|
|
6229
|
+
r: Math.min(255, parseInt(rgbSpace[1], 10)),
|
|
6230
|
+
g: Math.min(255, parseInt(rgbSpace[2], 10)),
|
|
6231
|
+
b: Math.min(255, parseInt(rgbSpace[3], 10)),
|
|
6232
|
+
a: rgbSpace[4] !== void 0 ? parseAlpha(rgbSpace[4]) : 1
|
|
6233
|
+
};
|
|
6382
6234
|
}
|
|
6383
|
-
|
|
6384
|
-
|
|
6235
|
+
const hslComma = v.match(/^hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%(?:\s*,\s*([\d.]+%?))?\s*\)$/);
|
|
6236
|
+
if (hslComma) {
|
|
6237
|
+
const h = (parseFloat(hslComma[1]) % 360 + 360) % 360;
|
|
6238
|
+
const s = Math.min(100, parseFloat(hslComma[2])) / 100;
|
|
6239
|
+
const l = Math.min(100, parseFloat(hslComma[3])) / 100;
|
|
6240
|
+
const [r, g, b] = hslToRgb(h, s, l);
|
|
6241
|
+
return { r, g, b, a: hslComma[4] !== void 0 ? parseAlpha(hslComma[4]) : 1 };
|
|
6242
|
+
}
|
|
6243
|
+
const hslSpace = v.match(/^hsla?\(\s*([\d.]+)\s+([\d.]+)%\s+([\d.]+)%(?:\s*\/\s*([\d.]+%?))?\s*\)$/);
|
|
6244
|
+
if (hslSpace) {
|
|
6245
|
+
const h = (parseFloat(hslSpace[1]) % 360 + 360) % 360;
|
|
6246
|
+
const s = Math.min(100, parseFloat(hslSpace[2])) / 100;
|
|
6247
|
+
const l = Math.min(100, parseFloat(hslSpace[3])) / 100;
|
|
6248
|
+
const [r, g, b] = hslToRgb(h, s, l);
|
|
6249
|
+
return { r, g, b, a: hslSpace[4] !== void 0 ? parseAlpha(hslSpace[4]) : 1 };
|
|
6250
|
+
}
|
|
6251
|
+
const hwbMatch = v.match(/^hwb\(\s*([\d.]+)\s+([\d.]+)%\s+([\d.]+)%(?:\s*\/\s*([\d.]+%?))?\s*\)$/);
|
|
6252
|
+
if (hwbMatch) {
|
|
6253
|
+
const h = (parseFloat(hwbMatch[1]) % 360 + 360) % 360;
|
|
6254
|
+
const w = Math.min(100, parseFloat(hwbMatch[2])) / 100;
|
|
6255
|
+
const bk = Math.min(100, parseFloat(hwbMatch[3])) / 100;
|
|
6256
|
+
const [r, g, b] = hwbToRgb(h, w, bk);
|
|
6257
|
+
return { r, g, b, a: hwbMatch[4] !== void 0 ? parseAlpha(hwbMatch[4]) : 1 };
|
|
6258
|
+
}
|
|
6259
|
+
const oklchMatch = v.match(/^oklch\(\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*([\d.]+%?))?\s*\)$/);
|
|
6260
|
+
if (oklchMatch) {
|
|
6261
|
+
const L = Math.min(1, Math.max(0, parseFloat(oklchMatch[1])));
|
|
6262
|
+
const C = Math.max(0, parseFloat(oklchMatch[2]));
|
|
6263
|
+
const H = (parseFloat(oklchMatch[3]) % 360 + 360) % 360;
|
|
6264
|
+
const [r, g, b] = oklchToRgb(L, C, H);
|
|
6265
|
+
return { r, g, b, a: oklchMatch[4] !== void 0 ? parseAlpha(oklchMatch[4]) : 1 };
|
|
6385
6266
|
}
|
|
6386
|
-
return
|
|
6267
|
+
return null;
|
|
6387
6268
|
}
|
|
6388
|
-
function
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
if (
|
|
6393
|
-
|
|
6394
|
-
}
|
|
6395
|
-
|
|
6396
|
-
if (!config) {
|
|
6397
|
-
return {
|
|
6398
|
-
clientId,
|
|
6399
|
-
html,
|
|
6400
|
-
warnings: [
|
|
6401
|
-
{
|
|
6402
|
-
severity: "info",
|
|
6403
|
-
client: clientId,
|
|
6404
|
-
property: "unknown",
|
|
6405
|
-
message: `No transformation rules available for client "${clientId}".`
|
|
6406
|
-
}
|
|
6407
|
-
]
|
|
6408
|
-
};
|
|
6409
|
-
}
|
|
6410
|
-
return applyTransform(html, config, framework);
|
|
6269
|
+
function formatRgb(rgba) {
|
|
6270
|
+
const r = clamp255(rgba.r);
|
|
6271
|
+
const g = clamp255(rgba.g);
|
|
6272
|
+
const b = clamp255(rgba.b);
|
|
6273
|
+
if (rgba.a < 1) {
|
|
6274
|
+
return `rgba(${r}, ${g}, ${b}, ${rgba.a})`;
|
|
6275
|
+
}
|
|
6276
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
6411
6277
|
}
|
|
6412
|
-
function
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6278
|
+
function relativeLuminance(r, g, b) {
|
|
6279
|
+
const [rs, gs, bs] = [r, g, b].map((c) => {
|
|
6280
|
+
const s = c / 255;
|
|
6281
|
+
return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
6282
|
+
});
|
|
6283
|
+
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
6284
|
+
}
|
|
6285
|
+
function contrastRatio(l1, l2) {
|
|
6286
|
+
const lighter = Math.max(l1, l2);
|
|
6287
|
+
const darker = Math.min(l1, l2);
|
|
6288
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
6289
|
+
}
|
|
6290
|
+
function wcagGrade(ratio) {
|
|
6291
|
+
if (ratio >= 7) return "AAA";
|
|
6292
|
+
if (ratio >= 4.5) return "AA";
|
|
6293
|
+
if (ratio >= 3) return "AA Large";
|
|
6294
|
+
return "Fail";
|
|
6295
|
+
}
|
|
6296
|
+
function alphaBlend(fg, bgR, bgG, bgB) {
|
|
6297
|
+
const a = fg.a;
|
|
6298
|
+
return [
|
|
6299
|
+
Math.round(fg.r * a + bgR * (1 - a)),
|
|
6300
|
+
Math.round(fg.g * a + bgG * (1 - a)),
|
|
6301
|
+
Math.round(fg.b * a + bgB * (1 - a))
|
|
6302
|
+
];
|
|
6416
6303
|
}
|
|
6417
6304
|
|
|
6418
|
-
// src/
|
|
6419
|
-
var
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
"
|
|
6423
|
-
"
|
|
6424
|
-
"
|
|
6425
|
-
"
|
|
6426
|
-
"
|
|
6427
|
-
"
|
|
6428
|
-
"<picture>": "picture",
|
|
6429
|
-
"<dialog>": "dialog",
|
|
6430
|
-
"<meter>": "meter",
|
|
6431
|
-
"<progress>": "progress",
|
|
6432
|
-
"<select>": "select",
|
|
6433
|
-
"<textarea>": "textarea",
|
|
6434
|
-
"<marquee>": "marquee",
|
|
6435
|
-
"<object>": "object",
|
|
6436
|
-
"<base>": "base"
|
|
6437
|
-
};
|
|
6438
|
-
var HTML_ELEMENT_SEVERITY = {
|
|
6439
|
-
"<style>": "error",
|
|
6440
|
-
"<link>": "error",
|
|
6441
|
-
"<svg>": "error",
|
|
6442
|
-
"<form>": "error",
|
|
6443
|
-
"<video>": "warning",
|
|
6444
|
-
"<audio>": "warning",
|
|
6445
|
-
"<picture>": "warning",
|
|
6446
|
-
"<dialog>": "warning",
|
|
6447
|
-
"<marquee>": "warning",
|
|
6448
|
-
"<meter>": "warning",
|
|
6449
|
-
"<progress>": "warning",
|
|
6450
|
-
"<select>": "warning",
|
|
6451
|
-
"<textarea>": "warning",
|
|
6452
|
-
"<object>": "warning",
|
|
6453
|
-
"<base>": "warning"
|
|
6454
|
-
};
|
|
6455
|
-
var HTML_ELEMENT_MESSAGES = {
|
|
6456
|
-
"<style>": (n) => `${n} strips <style> blocks. Styles must be inlined.`,
|
|
6457
|
-
"<link>": (n) => `${n} does not support external stylesheets.`,
|
|
6458
|
-
"<svg>": (n) => `${n} does not support inline SVG.`,
|
|
6459
|
-
"<video>": (n) => `${n} does not support <video> elements.`,
|
|
6460
|
-
"<form>": (n) => `${n} strips form elements.`
|
|
6305
|
+
// src/downlevel.ts
|
|
6306
|
+
var LOGICAL_PROPERTY_MAP = {
|
|
6307
|
+
"padding-inline": ["padding-left", "padding-right"],
|
|
6308
|
+
"padding-block": ["padding-top", "padding-bottom"],
|
|
6309
|
+
"margin-inline": ["margin-left", "margin-right"],
|
|
6310
|
+
"margin-block": ["margin-top", "margin-bottom"],
|
|
6311
|
+
"inset-inline": ["left", "right"],
|
|
6312
|
+
"inset-block": ["top", "bottom"],
|
|
6313
|
+
"border-inline": ["border-left", "border-right"],
|
|
6314
|
+
"border-block": ["border-top", "border-bottom"]
|
|
6461
6315
|
};
|
|
6462
|
-
var
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
]
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
}
|
|
6472
|
-
function analyzeEmailFromDom($, framework) {
|
|
6473
|
-
const warnings = [];
|
|
6474
|
-
const seenWarnings = /* @__PURE__ */ new Set();
|
|
6475
|
-
function addWarning(w) {
|
|
6476
|
-
const key = `${w.client}:${w.property}:${w.severity}:${w.selector || ""}`;
|
|
6477
|
-
if (!seenWarnings.has(key)) {
|
|
6478
|
-
seenWarnings.add(key);
|
|
6479
|
-
warnings.push(w);
|
|
6316
|
+
var MODERN_COLOR_RE = /\b(oklch|hsl|hsla|hwb|rgba?)\s*\(/g;
|
|
6317
|
+
function extractFunctionCall(str, fnStart, openParenIndex) {
|
|
6318
|
+
let depth = 0;
|
|
6319
|
+
for (let i = openParenIndex; i < str.length; i++) {
|
|
6320
|
+
if (str[i] === "(") depth++;
|
|
6321
|
+
else if (str[i] === ")") {
|
|
6322
|
+
depth--;
|
|
6323
|
+
if (depth === 0) {
|
|
6324
|
+
return str.slice(fnStart, i + 1);
|
|
6325
|
+
}
|
|
6480
6326
|
}
|
|
6481
6327
|
}
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6328
|
+
return null;
|
|
6329
|
+
}
|
|
6330
|
+
function isSpaceSyntaxRgb(args) {
|
|
6331
|
+
const inner = args.trim();
|
|
6332
|
+
return !inner.includes(",") && /^\d/.test(inner);
|
|
6333
|
+
}
|
|
6334
|
+
function convertModernColors(value) {
|
|
6335
|
+
let result = value;
|
|
6336
|
+
let match;
|
|
6337
|
+
MODERN_COLOR_RE.lastIndex = 0;
|
|
6338
|
+
const replacements = [];
|
|
6339
|
+
while ((match = MODERN_COLOR_RE.exec(result)) !== null) {
|
|
6340
|
+
const fnName = match[1].toLowerCase();
|
|
6341
|
+
const fnStart = match.index;
|
|
6342
|
+
const openParen = fnStart + match[0].length - 1;
|
|
6343
|
+
const fullCall = extractFunctionCall(result, fnStart, openParen);
|
|
6344
|
+
if (!fullCall) continue;
|
|
6345
|
+
if (fnName === "rgb" || fnName === "rgba") {
|
|
6346
|
+
const argsStr = fullCall.slice(fullCall.indexOf("(") + 1, -1);
|
|
6347
|
+
if (!isSpaceSyntaxRgb(argsStr)) continue;
|
|
6348
|
+
}
|
|
6349
|
+
const parsed = parseColor(fullCall);
|
|
6350
|
+
if (parsed) {
|
|
6351
|
+
replacements.push({
|
|
6352
|
+
start: fnStart,
|
|
6353
|
+
end: fnStart + fullCall.length,
|
|
6354
|
+
replacement: formatRgb(parsed)
|
|
6355
|
+
});
|
|
6356
|
+
}
|
|
6493
6357
|
}
|
|
6494
|
-
for (
|
|
6495
|
-
const
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6358
|
+
for (let i = replacements.length - 1; i >= 0; i--) {
|
|
6359
|
+
const r = replacements[i];
|
|
6360
|
+
result = result.slice(0, r.start) + r.replacement + result.slice(r.end);
|
|
6361
|
+
}
|
|
6362
|
+
return result;
|
|
6363
|
+
}
|
|
6364
|
+
function convertLogicalProperty(prop, value) {
|
|
6365
|
+
const mapping = LOGICAL_PROPERTY_MAP[prop];
|
|
6366
|
+
if (!mapping) return null;
|
|
6367
|
+
const parts = value.trim().split(/\s+/);
|
|
6368
|
+
const [first, second] = mapping;
|
|
6369
|
+
if (parts.length >= 2) {
|
|
6370
|
+
return [
|
|
6371
|
+
[first, parts[0]],
|
|
6372
|
+
[second, parts[1]]
|
|
6373
|
+
];
|
|
6374
|
+
}
|
|
6375
|
+
return [
|
|
6376
|
+
[first, parts[0]],
|
|
6377
|
+
[second, parts[0]]
|
|
6378
|
+
];
|
|
6379
|
+
}
|
|
6380
|
+
var CALC_INFINITY_RE = /calc\(\s*infinity\s*\*\s*1(px|em|rem|%|vh|vw)\s*\)/gi;
|
|
6381
|
+
var CALC_INFINITY_SIMPLE_RE = /calc\(\s*infinity\s*\)/gi;
|
|
6382
|
+
function resolveCalcInfinity(value) {
|
|
6383
|
+
let result = value.replace(CALC_INFINITY_RE, (_match, unit) => {
|
|
6384
|
+
return `9999${unit}`;
|
|
6385
|
+
});
|
|
6386
|
+
result = result.replace(CALC_INFINITY_SIMPLE_RE, "9999px");
|
|
6387
|
+
return result;
|
|
6388
|
+
}
|
|
6389
|
+
function unnestCSSRules(css) {
|
|
6390
|
+
let result = "";
|
|
6391
|
+
let i = 0;
|
|
6392
|
+
while (i < css.length) {
|
|
6393
|
+
const atMediaInside = findNestedAtRule(css, i);
|
|
6394
|
+
if (atMediaInside) {
|
|
6395
|
+
result += css.slice(i, atMediaInside.outerStart);
|
|
6396
|
+
if (atMediaInside.preamble) {
|
|
6397
|
+
result += `${atMediaInside.selector}{${atMediaInside.preamble}}`;
|
|
6529
6398
|
}
|
|
6399
|
+
result += `${atMediaInside.atRule}{${atMediaInside.selector}{${atMediaInside.body}}}`;
|
|
6400
|
+
i = atMediaInside.outerEnd;
|
|
6401
|
+
} else {
|
|
6402
|
+
result += css.slice(i);
|
|
6403
|
+
break;
|
|
6530
6404
|
}
|
|
6531
6405
|
}
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6406
|
+
return result;
|
|
6407
|
+
}
|
|
6408
|
+
function findNestedAtRule(css, startPos) {
|
|
6409
|
+
for (let i = startPos; i < css.length; i++) {
|
|
6410
|
+
if (css[i] === "{") {
|
|
6411
|
+
const selector = css.slice(startPos, i).trim();
|
|
6412
|
+
if (!selector || selector.startsWith("@")) {
|
|
6413
|
+
const closeIdx2 = findMatchingBrace(css, i);
|
|
6414
|
+
if (closeIdx2 === -1) return null;
|
|
6415
|
+
const rest = findNestedAtRule(css, closeIdx2 + 1);
|
|
6416
|
+
if (rest) return rest;
|
|
6417
|
+
return null;
|
|
6418
|
+
}
|
|
6419
|
+
const innerStart = i + 1;
|
|
6420
|
+
const closeIdx = findMatchingBrace(css, i);
|
|
6421
|
+
if (closeIdx === -1) return null;
|
|
6422
|
+
const innerContent = css.slice(innerStart, closeIdx).trim();
|
|
6423
|
+
const atMatch = innerContent.match(/@(media|supports)\s/);
|
|
6424
|
+
if (atMatch && atMatch.index !== void 0) {
|
|
6425
|
+
const atPos = atMatch.index;
|
|
6426
|
+
const atBraceIdx = innerContent.indexOf("{", atPos);
|
|
6427
|
+
if (atBraceIdx === -1) continue;
|
|
6428
|
+
const atRule = innerContent.slice(atPos, atBraceIdx).trim();
|
|
6429
|
+
const atCloseIdx = findMatchingBrace(innerContent, atBraceIdx);
|
|
6430
|
+
if (atCloseIdx === -1) continue;
|
|
6431
|
+
const body = innerContent.slice(atBraceIdx + 1, atCloseIdx).trim();
|
|
6432
|
+
const beforeAt = innerContent.slice(0, atPos).trim();
|
|
6433
|
+
return __spreadValues({
|
|
6434
|
+
outerStart: findSelectorStart(css, i, startPos),
|
|
6435
|
+
outerEnd: closeIdx + 1,
|
|
6436
|
+
selector,
|
|
6437
|
+
atRule,
|
|
6438
|
+
body
|
|
6439
|
+
}, beforeAt ? { preamble: beforeAt } : {});
|
|
6440
|
+
}
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6443
|
+
return null;
|
|
6444
|
+
}
|
|
6445
|
+
function findSelectorStart(css, braceIdx, minPos) {
|
|
6446
|
+
let i = braceIdx - 1;
|
|
6447
|
+
while (i >= minPos && css[i] !== "}" && css[i] !== ";") {
|
|
6448
|
+
i--;
|
|
6449
|
+
}
|
|
6450
|
+
return i + 1;
|
|
6451
|
+
}
|
|
6452
|
+
function findMatchingBrace(css, openIdx) {
|
|
6453
|
+
let depth = 0;
|
|
6454
|
+
for (let i = openIdx; i < css.length; i++) {
|
|
6455
|
+
if (css[i] === "{") depth++;
|
|
6456
|
+
else if (css[i] === "}") {
|
|
6457
|
+
depth--;
|
|
6458
|
+
if (depth === 0) return i;
|
|
6459
|
+
}
|
|
6460
|
+
}
|
|
6461
|
+
return -1;
|
|
6462
|
+
}
|
|
6463
|
+
function convertRangeMediaQueries(css) {
|
|
6464
|
+
return css.replace(
|
|
6465
|
+
/\(\s*width\s*(>=?|<=?)\s*([\d.]+\w*)\s*\)/g,
|
|
6466
|
+
(_match, op, value) => {
|
|
6467
|
+
if (op === ">=" || op === ">") {
|
|
6468
|
+
return `(min-width: ${value})`;
|
|
6469
|
+
}
|
|
6470
|
+
return `(max-width: ${value})`;
|
|
6471
|
+
}
|
|
6472
|
+
);
|
|
6473
|
+
}
|
|
6474
|
+
var UNIVERSAL_SELECTORS = /* @__PURE__ */ new Set([":root", "*", "html"]);
|
|
6475
|
+
function selectorsCouldMatch(defSelector, useSelector) {
|
|
6476
|
+
if (defSelector === useSelector) return true;
|
|
6477
|
+
if (UNIVERSAL_SELECTORS.has(defSelector)) return true;
|
|
6478
|
+
return false;
|
|
6479
|
+
}
|
|
6480
|
+
function resolveCSSVariables(ast) {
|
|
6481
|
+
const varDefs = [];
|
|
6482
|
+
csstree.walk(ast, {
|
|
6483
|
+
visit: "Rule",
|
|
6484
|
+
enter(rule) {
|
|
6485
|
+
if (rule.type !== "Rule" || !rule.prelude || !rule.block) return;
|
|
6486
|
+
const selector = csstree.generate(rule.prelude);
|
|
6487
|
+
rule.block.children.forEach((node) => {
|
|
6488
|
+
if (node.type === "Declaration" && node.property.startsWith("--")) {
|
|
6489
|
+
varDefs.push({
|
|
6490
|
+
selector,
|
|
6491
|
+
varName: node.property,
|
|
6492
|
+
value: csstree.generate(node.value)
|
|
6493
|
+
});
|
|
6494
|
+
}
|
|
6495
|
+
});
|
|
6496
|
+
}
|
|
6497
|
+
});
|
|
6498
|
+
if (varDefs.length === 0) return;
|
|
6499
|
+
csstree.walk(ast, {
|
|
6500
|
+
visit: "Rule",
|
|
6501
|
+
enter(rule) {
|
|
6502
|
+
if (rule.type !== "Rule" || !rule.prelude || !rule.block) return;
|
|
6503
|
+
const useSelector = csstree.generate(rule.prelude);
|
|
6504
|
+
csstree.walk(rule.block, {
|
|
6505
|
+
visit: "Function",
|
|
6506
|
+
enter(node, item, list) {
|
|
6507
|
+
if (node.name !== "var" || !list) return;
|
|
6508
|
+
const args = [];
|
|
6509
|
+
if (node.children) {
|
|
6510
|
+
node.children.forEach((child) => {
|
|
6511
|
+
args.push(csstree.generate(child));
|
|
6512
|
+
});
|
|
6552
6513
|
}
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6514
|
+
const fullArgs = args.join("");
|
|
6515
|
+
const commaIdx = fullArgs.indexOf(",");
|
|
6516
|
+
const varName = commaIdx !== -1 ? fullArgs.slice(0, commaIdx).trim() : fullArgs.trim();
|
|
6517
|
+
const fallback = commaIdx !== -1 ? fullArgs.slice(commaIdx + 1).trim() : null;
|
|
6518
|
+
let resolved = null;
|
|
6519
|
+
for (const def of varDefs) {
|
|
6520
|
+
if (def.varName === varName && def.selector === useSelector) {
|
|
6521
|
+
resolved = def.value;
|
|
6522
|
+
break;
|
|
6558
6523
|
}
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
}
|
|
6524
|
+
}
|
|
6525
|
+
if (!resolved) {
|
|
6526
|
+
for (const def of varDefs) {
|
|
6527
|
+
if (def.varName === varName && selectorsCouldMatch(def.selector, useSelector)) {
|
|
6528
|
+
resolved = def.value;
|
|
6529
|
+
break;
|
|
6566
6530
|
}
|
|
6567
6531
|
}
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6532
|
+
}
|
|
6533
|
+
if (!resolved && fallback) {
|
|
6534
|
+
resolved = fallback;
|
|
6535
|
+
}
|
|
6536
|
+
if (resolved && item) {
|
|
6537
|
+
const replacement = csstree.parse(resolved, {
|
|
6538
|
+
context: "value"
|
|
6539
|
+
});
|
|
6540
|
+
if (replacement.children) {
|
|
6541
|
+
replacement.children.forEach((child) => {
|
|
6542
|
+
list.insertData(child, item);
|
|
6543
|
+
});
|
|
6544
|
+
list.remove(item);
|
|
6575
6545
|
}
|
|
6576
6546
|
}
|
|
6577
6547
|
}
|
|
6578
6548
|
});
|
|
6579
|
-
} catch (e) {
|
|
6580
6549
|
}
|
|
6581
6550
|
});
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6551
|
+
}
|
|
6552
|
+
function sanitizeDeclarations(ast) {
|
|
6553
|
+
csstree.walk(ast, {
|
|
6554
|
+
visit: "Declaration",
|
|
6555
|
+
enter(decl) {
|
|
6556
|
+
if (decl.property.startsWith("--")) return;
|
|
6557
|
+
if (decl.value.type === "Value") {
|
|
6558
|
+
const raw = csstree.generate(decl.value);
|
|
6559
|
+
let converted = convertModernColors(raw);
|
|
6560
|
+
converted = resolveCalcInfinity(converted);
|
|
6561
|
+
if (converted !== raw) {
|
|
6562
|
+
try {
|
|
6563
|
+
const newValue = csstree.parse(converted, {
|
|
6564
|
+
context: "value"
|
|
6565
|
+
});
|
|
6566
|
+
decl.value = newValue;
|
|
6567
|
+
} catch (e) {
|
|
6599
6568
|
}
|
|
6600
6569
|
}
|
|
6601
6570
|
}
|
|
6602
|
-
|
|
6603
|
-
|
|
6571
|
+
}
|
|
6572
|
+
});
|
|
6573
|
+
csstree.walk(ast, {
|
|
6574
|
+
visit: "Rule",
|
|
6575
|
+
enter(rule) {
|
|
6576
|
+
if (rule.type !== "Rule" || !rule.block) return;
|
|
6577
|
+
const toInsert = [];
|
|
6578
|
+
rule.block.children.forEach(function(node, item) {
|
|
6579
|
+
if (node.type !== "Declaration") return;
|
|
6580
|
+
const mapping = LOGICAL_PROPERTY_MAP[node.property];
|
|
6581
|
+
if (!mapping) return;
|
|
6582
|
+
const valueStr = csstree.generate(node.value);
|
|
6583
|
+
const parts = valueStr.trim().split(/\s+/);
|
|
6584
|
+
const [first, second] = mapping;
|
|
6585
|
+
const val1 = parts[0];
|
|
6586
|
+
const val2 = parts.length >= 2 ? parts[1] : parts[0];
|
|
6587
|
+
toInsert.push(
|
|
6588
|
+
{ property: first, value: val1, important: !!node.important, item },
|
|
6589
|
+
{ property: second, value: val2, important: !!node.important, item }
|
|
6590
|
+
);
|
|
6591
|
+
});
|
|
6592
|
+
for (const { property, value, important, item } of toInsert) {
|
|
6593
|
+
try {
|
|
6594
|
+
const newDecl = {
|
|
6595
|
+
type: "Declaration",
|
|
6596
|
+
important,
|
|
6597
|
+
property,
|
|
6598
|
+
value: csstree.parse(value, { context: "value" })
|
|
6599
|
+
};
|
|
6600
|
+
rule.block.children.insertData(newDecl, item);
|
|
6601
|
+
} catch (e) {
|
|
6602
|
+
}
|
|
6604
6603
|
}
|
|
6605
|
-
const
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
}
|
|
6604
|
+
const removed = /* @__PURE__ */ new Set();
|
|
6605
|
+
for (const { item } of toInsert) {
|
|
6606
|
+
if (!removed.has(item)) {
|
|
6607
|
+
removed.add(item);
|
|
6608
|
+
rule.block.children.remove(item);
|
|
6611
6609
|
}
|
|
6612
6610
|
}
|
|
6613
6611
|
}
|
|
6614
6612
|
});
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6613
|
+
}
|
|
6614
|
+
function downlevelCSS(html) {
|
|
6615
|
+
const $ = cheerio.load(html);
|
|
6616
|
+
$("style").each((_, el) => {
|
|
6617
|
+
let css = $(el).text();
|
|
6618
|
+
css = unnestCSSRules(css);
|
|
6619
|
+
css = convertRangeMediaQueries(css);
|
|
6620
|
+
try {
|
|
6621
|
+
const ast = csstree.parse(css, { parseCustomProperty: true });
|
|
6622
|
+
resolveCSSVariables(ast);
|
|
6623
|
+
sanitizeDeclarations(ast);
|
|
6624
|
+
$(el).text(csstree.generate(ast));
|
|
6625
|
+
} catch (e) {
|
|
6626
|
+
$(el).text(css);
|
|
6624
6627
|
}
|
|
6625
|
-
}
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6628
|
+
});
|
|
6629
|
+
$("[style]").each((_, el) => {
|
|
6630
|
+
const style = $(el).attr("style") || "";
|
|
6631
|
+
const props = parseInlineStyle(style);
|
|
6632
|
+
let changed = false;
|
|
6633
|
+
const newProps = /* @__PURE__ */ new Map();
|
|
6634
|
+
props.forEach((value, prop) => {
|
|
6635
|
+
const converted = convertModernColors(value);
|
|
6636
|
+
if (converted !== value) changed = true;
|
|
6637
|
+
const physical = convertLogicalProperty(prop, converted);
|
|
6638
|
+
if (physical) {
|
|
6639
|
+
changed = true;
|
|
6640
|
+
for (const [p, v] of physical) {
|
|
6641
|
+
newProps.set(p, v);
|
|
6642
|
+
}
|
|
6643
|
+
} else {
|
|
6644
|
+
const calcFixed = resolveCalcInfinity(converted);
|
|
6645
|
+
if (calcFixed !== converted) changed = true;
|
|
6646
|
+
newProps.set(prop, calcFixed);
|
|
6647
|
+
}
|
|
6648
|
+
});
|
|
6649
|
+
if (changed) {
|
|
6650
|
+
$(el).attr("style", serializeStyle(newProps));
|
|
6629
6651
|
}
|
|
6630
|
-
}
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6652
|
+
});
|
|
6653
|
+
return $.html();
|
|
6654
|
+
}
|
|
6655
|
+
|
|
6656
|
+
// src/constants.ts
|
|
6657
|
+
var MAX_HTML_SIZE = 2 * 1024 * 1024;
|
|
6658
|
+
var GENERIC_LINK_TEXT = /* @__PURE__ */ new Set([
|
|
6659
|
+
"click here",
|
|
6660
|
+
"here",
|
|
6661
|
+
"read more",
|
|
6662
|
+
"learn more",
|
|
6663
|
+
"more",
|
|
6664
|
+
"link",
|
|
6665
|
+
"this link",
|
|
6666
|
+
"click",
|
|
6667
|
+
"tap here",
|
|
6668
|
+
"this"
|
|
6669
|
+
]);
|
|
6670
|
+
var GMAIL_CLIP_THRESHOLD = 102 * 1024;
|
|
6671
|
+
var GMAIL_CLIP_WARNING_THRESHOLD = 90 * 1024;
|
|
6672
|
+
var CLIENT_DISPLAY_LIMITS = [
|
|
6673
|
+
{ client: "Gmail (Web)", subjectLimit: 70, preheaderLimit: 90 },
|
|
6674
|
+
{ client: "Gmail (Mobile)", subjectLimit: 40, preheaderLimit: 45 },
|
|
6675
|
+
{ client: "Outlook (Web)", subjectLimit: 60, preheaderLimit: 90 },
|
|
6676
|
+
{ client: "Outlook (Desktop)", subjectLimit: 55, preheaderLimit: 35 },
|
|
6677
|
+
{ client: "Apple Mail (macOS)", subjectLimit: 78, preheaderLimit: 140 },
|
|
6678
|
+
{ client: "Apple Mail (iOS)", subjectLimit: 41, preheaderLimit: 90 },
|
|
6679
|
+
{ client: "Yahoo Mail", subjectLimit: 46, preheaderLimit: 100 },
|
|
6680
|
+
{ client: "Samsung Email", subjectLimit: 40, preheaderLimit: 70 }
|
|
6681
|
+
];
|
|
6682
|
+
var TEMPLATE_VARIABLE_PATTERNS = [
|
|
6683
|
+
[/\{\{[\s\S]*?\}\}/g, "Handlebars/Mustache"],
|
|
6684
|
+
// {{var}}
|
|
6685
|
+
[/\$\{[^}]+\}/g, "ES template literal"],
|
|
6686
|
+
// ${var}
|
|
6687
|
+
[/<%=?\s*[^%]+%>/g, "ERB/EJS"],
|
|
6688
|
+
// <% %> / <%= %>
|
|
6689
|
+
[/\*\|[A-Z_][A-Z0-9_]*\|\*/g, "Mailchimp merge tag"],
|
|
6690
|
+
// *|TAG|*
|
|
6691
|
+
[/%%[A-Za-z_][A-Za-z0-9_]*%%/g, "Salesforce AMPscript"],
|
|
6692
|
+
// %%tag%%
|
|
6693
|
+
[/\{[A-Za-z_][A-Za-z0-9_.]{2,}\}/g, "Single-brace merge field"]
|
|
6694
|
+
// {merge_field} (3+ char names)
|
|
6695
|
+
];
|
|
6696
|
+
var EMPTY_SPAM = { score: 100, level: "low", issues: [] };
|
|
6697
|
+
var EMPTY_LINKS = {
|
|
6698
|
+
totalLinks: 0,
|
|
6699
|
+
issues: [],
|
|
6700
|
+
breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
|
|
6701
|
+
};
|
|
6702
|
+
var EMPTY_ACCESSIBILITY = { score: 100, issues: [] };
|
|
6703
|
+
var EMPTY_IMAGES = { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
|
|
6704
|
+
var EMPTY_INBOX_PREVIEW = { subject: null, preheader: null, subjectLength: 0, preheaderLength: 0, truncation: [], issues: [] };
|
|
6705
|
+
var EMPTY_SIZE = { htmlBytes: 0, humanSize: "0 B", clipped: false, issues: [] };
|
|
6706
|
+
var EMPTY_TEMPLATE = { unresolvedCount: 0, issues: [] };
|
|
6707
|
+
var EMPTY_DELIVERABILITY = { domain: "", checks: [], score: 0, issues: [] };
|
|
6708
|
+
|
|
6709
|
+
// src/transform.ts
|
|
6710
|
+
function hasPseudoSelector(selector) {
|
|
6711
|
+
return new RegExp("(?<![[\\w])::?[a-z][\\w-]*(?:\\(|(?=[^(]))", "i").test(selector);
|
|
6712
|
+
}
|
|
6713
|
+
function addImportantToDeclarations(node) {
|
|
6714
|
+
csstree2.walk(node, {
|
|
6715
|
+
visit: "Declaration",
|
|
6716
|
+
enter(decl) {
|
|
6717
|
+
if (decl.type !== "Declaration") return;
|
|
6718
|
+
if (decl.important) return;
|
|
6719
|
+
decl.important = true;
|
|
6634
6720
|
}
|
|
6635
|
-
}
|
|
6636
|
-
for (const fn of detectedCssFunctions) {
|
|
6637
|
-
checkPropertySupport(fn, addWarning, framework, void 0, propertyLines.get(fn));
|
|
6638
|
-
}
|
|
6639
|
-
const severityOrder = { error: 0, warning: 1, info: 2 };
|
|
6640
|
-
warnings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
6641
|
-
return warnings;
|
|
6721
|
+
});
|
|
6642
6722
|
}
|
|
6643
|
-
function
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6723
|
+
function inlineStyles($) {
|
|
6724
|
+
const styleBlocks = [];
|
|
6725
|
+
$("style").each((_, el) => {
|
|
6726
|
+
styleBlocks.push($(el).text());
|
|
6727
|
+
});
|
|
6728
|
+
if (styleBlocks.length === 0) return "";
|
|
6729
|
+
const preserved = [];
|
|
6730
|
+
for (const block of styleBlocks) {
|
|
6731
|
+
let ast;
|
|
6732
|
+
try {
|
|
6733
|
+
ast = csstree2.parse(block, { parseCustomProperty: true });
|
|
6734
|
+
} catch (e) {
|
|
6735
|
+
continue;
|
|
6736
|
+
}
|
|
6737
|
+
csstree2.walk(ast, {
|
|
6738
|
+
visit: "Atrule",
|
|
6739
|
+
enter(node) {
|
|
6740
|
+
if (node.type !== "Atrule") return;
|
|
6741
|
+
const name = node.name.toLowerCase();
|
|
6742
|
+
if (name === "media" || name === "supports" || name === "keyframes" || name === "-webkit-keyframes" || name === "font-face") {
|
|
6743
|
+
if (name === "media" || name === "supports") {
|
|
6744
|
+
addImportantToDeclarations(node);
|
|
6745
|
+
}
|
|
6746
|
+
preserved.push(csstree2.generate(node));
|
|
6747
|
+
return this.skip;
|
|
6748
|
+
}
|
|
6749
|
+
}
|
|
6750
|
+
});
|
|
6751
|
+
csstree2.walk(ast, {
|
|
6752
|
+
visit: "Rule",
|
|
6753
|
+
enter(node) {
|
|
6754
|
+
if (node.type !== "Rule" || node.prelude.type !== "SelectorList") return;
|
|
6755
|
+
const declarations = csstree2.generate(node.block);
|
|
6756
|
+
const declText = declarations.slice(1, -1).trim();
|
|
6757
|
+
if (!declText) return;
|
|
6758
|
+
const selectorText = csstree2.generate(node.prelude);
|
|
6759
|
+
if (hasPseudoSelector(selectorText)) {
|
|
6760
|
+
preserved.push(csstree2.generate(node));
|
|
6761
|
+
return;
|
|
6762
|
+
}
|
|
6763
|
+
try {
|
|
6764
|
+
$(selectorText).each((_, el) => {
|
|
6765
|
+
const existing = $(el).attr("style") || "";
|
|
6766
|
+
$(el).attr("style", existing ? `${existing}; ${declText}` : declText);
|
|
6767
|
+
});
|
|
6768
|
+
} catch (e) {
|
|
6769
|
+
}
|
|
6770
|
+
}
|
|
6771
|
+
});
|
|
6649
6772
|
}
|
|
6650
|
-
|
|
6651
|
-
return analyzeEmailFromDom($, framework);
|
|
6773
|
+
return preserved.join("\n");
|
|
6652
6774
|
}
|
|
6653
|
-
function
|
|
6654
|
-
|
|
6775
|
+
function makeWarning(base, prop, clientId, framework) {
|
|
6776
|
+
const sug = getSuggestion(prop, clientId, framework);
|
|
6777
|
+
const fix = getCodeFix(prop, clientId, framework);
|
|
6778
|
+
const isFallback = framework && ((sug == null ? void 0 : sug.isGenericFallback) || fix && isCodeFixGenericFallback(prop, clientId, framework));
|
|
6779
|
+
return __spreadProps(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, base), sug ? { suggestion: sug.text } : {}), fix ? { fix } : {}), isFallback ? { fixIsGenericFallback: true } : {}), {
|
|
6780
|
+
fixType: STRUCTURAL_FIX_PROPERTIES.has(prop) ? "structural" : "css"
|
|
6781
|
+
});
|
|
6655
6782
|
}
|
|
6656
|
-
function
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
fixType
|
|
6685
|
-
}, selector ? { selector } : {}), line !== void 0 ? { line } : {}), framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(prop, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
6686
|
-
}
|
|
6783
|
+
function detectAnimations($) {
|
|
6784
|
+
let found = false;
|
|
6785
|
+
$("[style]").each((_, el) => {
|
|
6786
|
+
const style = $(el).attr("style") || "";
|
|
6787
|
+
const props = parseInlineStyle(style);
|
|
6788
|
+
props.forEach((_2, prop) => {
|
|
6789
|
+
if (prop === "animation" || prop === "transition" || prop.startsWith("animation-") || prop.startsWith("transition-")) {
|
|
6790
|
+
found = true;
|
|
6791
|
+
}
|
|
6792
|
+
});
|
|
6793
|
+
});
|
|
6794
|
+
if (!found) {
|
|
6795
|
+
$("style").each((_, el) => {
|
|
6796
|
+
try {
|
|
6797
|
+
const ast = csstree2.parse($(el).text());
|
|
6798
|
+
csstree2.walk(ast, {
|
|
6799
|
+
enter(node) {
|
|
6800
|
+
if (node.type === "Declaration") {
|
|
6801
|
+
const prop = node.property.toLowerCase();
|
|
6802
|
+
if (prop === "animation" || prop === "transition" || prop.startsWith("animation-") || prop.startsWith("transition-")) {
|
|
6803
|
+
found = true;
|
|
6804
|
+
}
|
|
6805
|
+
}
|
|
6806
|
+
}
|
|
6807
|
+
});
|
|
6808
|
+
} catch (e) {
|
|
6809
|
+
}
|
|
6810
|
+
});
|
|
6687
6811
|
}
|
|
6812
|
+
return found;
|
|
6688
6813
|
}
|
|
6689
|
-
function
|
|
6690
|
-
const
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6814
|
+
function gmailAdditionalChecks($, clientId, _html, framework) {
|
|
6815
|
+
const warnings = [];
|
|
6816
|
+
let hasAtFontFace = false;
|
|
6817
|
+
$("style").each((_, el) => {
|
|
6818
|
+
try {
|
|
6819
|
+
const ast = csstree2.parse($(el).text());
|
|
6820
|
+
csstree2.walk(ast, {
|
|
6821
|
+
enter(node) {
|
|
6822
|
+
if (node.type === "Atrule" && node.name === "font-face") {
|
|
6823
|
+
hasAtFontFace = true;
|
|
6824
|
+
}
|
|
6825
|
+
}
|
|
6826
|
+
});
|
|
6827
|
+
} catch (e) {
|
|
6828
|
+
}
|
|
6829
|
+
});
|
|
6830
|
+
if (hasAtFontFace) {
|
|
6831
|
+
warnings.push(makeWarning({
|
|
6832
|
+
severity: "warning",
|
|
6833
|
+
client: clientId,
|
|
6834
|
+
property: "@font-face",
|
|
6835
|
+
message: "Gmail does not support custom web fonts."
|
|
6836
|
+
}, "@font-face", clientId, framework));
|
|
6701
6837
|
}
|
|
6702
|
-
return
|
|
6838
|
+
return warnings;
|
|
6703
6839
|
}
|
|
6704
|
-
function
|
|
6705
|
-
|
|
6840
|
+
function gmailPostChecks($, clientId, _html, framework) {
|
|
6841
|
+
const warnings = [];
|
|
6842
|
+
$("*").contents().filter(function() {
|
|
6843
|
+
return this.type === "comment";
|
|
6844
|
+
}).each(function() {
|
|
6845
|
+
const commentText = this.data || "";
|
|
6846
|
+
if (commentText.includes("<style") || commentText.includes("[if mso]") || commentText.includes("[if gte mso")) {
|
|
6847
|
+
$(this).remove();
|
|
6848
|
+
}
|
|
6849
|
+
});
|
|
6850
|
+
const styleSug = getSuggestion("<style>:partial", clientId, framework);
|
|
6851
|
+
warnings.push({
|
|
6852
|
+
severity: "info",
|
|
6853
|
+
client: clientId,
|
|
6854
|
+
property: "<style>",
|
|
6855
|
+
message: "Gmail partially supports <style> blocks (head only, 16KB limit). Inlining recommended for safety.",
|
|
6856
|
+
suggestion: styleSug.text
|
|
6857
|
+
});
|
|
6858
|
+
return warnings;
|
|
6706
6859
|
}
|
|
6707
|
-
function
|
|
6708
|
-
|
|
6860
|
+
function outlookWindowsAdditionalChecks($, clientId, _html, framework) {
|
|
6861
|
+
const warnings = [];
|
|
6862
|
+
if ($("[style*='border-radius']").length > 0) {
|
|
6863
|
+
warnings.push(makeWarning({
|
|
6864
|
+
severity: "warning",
|
|
6865
|
+
client: clientId,
|
|
6866
|
+
property: "border-radius",
|
|
6867
|
+
message: "Outlook Classic ignores border-radius. Buttons and containers will have sharp corners."
|
|
6868
|
+
}, "border-radius", clientId, framework));
|
|
6869
|
+
}
|
|
6870
|
+
if ($("[style*='max-width']").length > 0) {
|
|
6871
|
+
warnings.push(makeWarning({
|
|
6872
|
+
severity: "warning",
|
|
6873
|
+
client: clientId,
|
|
6874
|
+
property: "max-width",
|
|
6875
|
+
message: "Outlook Classic ignores max-width."
|
|
6876
|
+
}, "max-width", clientId, framework));
|
|
6877
|
+
}
|
|
6878
|
+
const hasDivLayout = $("div[style*='display']").length > 0 || $("div[style*='flex']").length > 0 || $("div[style*='grid']").length > 0;
|
|
6879
|
+
if (hasDivLayout) {
|
|
6880
|
+
warnings.push(makeWarning({
|
|
6881
|
+
severity: "error",
|
|
6882
|
+
client: clientId,
|
|
6883
|
+
property: "display:flex",
|
|
6884
|
+
message: "Outlook Classic uses Microsoft Word for rendering. Flexbox and Grid layouts will break."
|
|
6885
|
+
}, "display:flex", clientId, framework));
|
|
6886
|
+
}
|
|
6887
|
+
if ($("[style*='background-image']").length > 0 || $("[style*='background:']").filter(
|
|
6888
|
+
(_, el) => ($(el).attr("style") || "").includes("url(")
|
|
6889
|
+
).length > 0) {
|
|
6890
|
+
warnings.push(makeWarning({
|
|
6891
|
+
severity: "warning",
|
|
6892
|
+
client: clientId,
|
|
6893
|
+
property: "background-image",
|
|
6894
|
+
message: "Outlook Classic requires VML for background images."
|
|
6895
|
+
}, "background-image", clientId, framework));
|
|
6896
|
+
}
|
|
6897
|
+
return warnings;
|
|
6709
6898
|
}
|
|
6710
|
-
function
|
|
6711
|
-
|
|
6899
|
+
function appleMailAdditionalChecks($, clientId) {
|
|
6900
|
+
const warnings = [];
|
|
6901
|
+
const imgsWithTransparentBg = $("img").filter((_, el) => {
|
|
6902
|
+
const src = $(el).attr("src") || "";
|
|
6903
|
+
return src.endsWith(".png") || src.endsWith(".svg");
|
|
6904
|
+
});
|
|
6905
|
+
if (imgsWithTransparentBg.length > 0) {
|
|
6906
|
+
warnings.push({
|
|
6907
|
+
severity: "info",
|
|
6908
|
+
client: clientId,
|
|
6909
|
+
property: "dark-mode",
|
|
6910
|
+
message: "PNG/SVG images with transparent backgrounds may become invisible in Apple Mail dark mode.",
|
|
6911
|
+
suggestion: "Add a white background or padding around images, or use dark-mode-friendly image variants."
|
|
6912
|
+
});
|
|
6913
|
+
}
|
|
6914
|
+
return warnings;
|
|
6712
6915
|
}
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
blueviolet: [138, 43, 226],
|
|
6731
|
-
brown: [165, 42, 42],
|
|
6732
|
-
burlywood: [222, 184, 135],
|
|
6733
|
-
cadetblue: [95, 158, 160],
|
|
6734
|
-
chartreuse: [127, 255, 0],
|
|
6735
|
-
chocolate: [210, 105, 30],
|
|
6736
|
-
coral: [255, 127, 80],
|
|
6737
|
-
cornflowerblue: [100, 149, 237],
|
|
6738
|
-
cornsilk: [255, 248, 220],
|
|
6739
|
-
crimson: [220, 20, 60],
|
|
6740
|
-
cyan: [0, 255, 255],
|
|
6741
|
-
darkblue: [0, 0, 139],
|
|
6742
|
-
darkcyan: [0, 139, 139],
|
|
6743
|
-
darkgoldenrod: [184, 134, 11],
|
|
6744
|
-
darkgray: [169, 169, 169],
|
|
6745
|
-
darkgreen: [0, 100, 0],
|
|
6746
|
-
darkgrey: [169, 169, 169],
|
|
6747
|
-
darkkhaki: [189, 183, 107],
|
|
6748
|
-
darkmagenta: [139, 0, 139],
|
|
6749
|
-
darkolivegreen: [85, 107, 47],
|
|
6750
|
-
darkorange: [255, 140, 0],
|
|
6751
|
-
darkorchid: [153, 50, 204],
|
|
6752
|
-
darkred: [139, 0, 0],
|
|
6753
|
-
darksalmon: [233, 150, 122],
|
|
6754
|
-
darkseagreen: [143, 188, 143],
|
|
6755
|
-
darkslateblue: [72, 61, 139],
|
|
6756
|
-
darkslategray: [47, 79, 79],
|
|
6757
|
-
darkslategrey: [47, 79, 79],
|
|
6758
|
-
darkturquoise: [0, 206, 209],
|
|
6759
|
-
darkviolet: [148, 0, 211],
|
|
6760
|
-
deeppink: [255, 20, 147],
|
|
6761
|
-
deepskyblue: [0, 191, 255],
|
|
6762
|
-
dimgray: [105, 105, 105],
|
|
6763
|
-
dimgrey: [105, 105, 105],
|
|
6764
|
-
dodgerblue: [30, 144, 255],
|
|
6765
|
-
firebrick: [178, 34, 34],
|
|
6766
|
-
floralwhite: [255, 250, 240],
|
|
6767
|
-
forestgreen: [34, 139, 34],
|
|
6768
|
-
fuchsia: [255, 0, 255],
|
|
6769
|
-
gainsboro: [220, 220, 220],
|
|
6770
|
-
ghostwhite: [248, 248, 255],
|
|
6771
|
-
gold: [255, 215, 0],
|
|
6772
|
-
goldenrod: [218, 165, 32],
|
|
6773
|
-
gray: [128, 128, 128],
|
|
6774
|
-
green: [0, 128, 0],
|
|
6775
|
-
greenyellow: [173, 255, 47],
|
|
6776
|
-
grey: [128, 128, 128],
|
|
6777
|
-
honeydew: [240, 255, 240],
|
|
6778
|
-
hotpink: [255, 105, 180],
|
|
6779
|
-
indianred: [205, 92, 92],
|
|
6780
|
-
indigo: [75, 0, 130],
|
|
6781
|
-
ivory: [255, 255, 240],
|
|
6782
|
-
khaki: [240, 230, 140],
|
|
6783
|
-
lavender: [230, 230, 250],
|
|
6784
|
-
lavenderblush: [255, 240, 245],
|
|
6785
|
-
lawngreen: [124, 252, 0],
|
|
6786
|
-
lemonchiffon: [255, 250, 205],
|
|
6787
|
-
lightblue: [173, 216, 230],
|
|
6788
|
-
lightcoral: [240, 128, 128],
|
|
6789
|
-
lightcyan: [224, 255, 255],
|
|
6790
|
-
lightgoldenrodyellow: [250, 250, 210],
|
|
6791
|
-
lightgray: [211, 211, 211],
|
|
6792
|
-
lightgreen: [144, 238, 144],
|
|
6793
|
-
lightgrey: [211, 211, 211],
|
|
6794
|
-
lightpink: [255, 182, 193],
|
|
6795
|
-
lightsalmon: [255, 160, 122],
|
|
6796
|
-
lightseagreen: [32, 178, 170],
|
|
6797
|
-
lightskyblue: [135, 206, 250],
|
|
6798
|
-
lightslategray: [119, 136, 153],
|
|
6799
|
-
lightslategrey: [119, 136, 153],
|
|
6800
|
-
lightsteelblue: [176, 196, 222],
|
|
6801
|
-
lightyellow: [255, 255, 224],
|
|
6802
|
-
lime: [0, 255, 0],
|
|
6803
|
-
limegreen: [50, 205, 50],
|
|
6804
|
-
linen: [250, 240, 230],
|
|
6805
|
-
magenta: [255, 0, 255],
|
|
6806
|
-
maroon: [128, 0, 0],
|
|
6807
|
-
mediumaquamarine: [102, 205, 170],
|
|
6808
|
-
mediumblue: [0, 0, 205],
|
|
6809
|
-
mediumorchid: [186, 85, 211],
|
|
6810
|
-
mediumpurple: [147, 111, 219],
|
|
6811
|
-
mediumseagreen: [60, 179, 113],
|
|
6812
|
-
mediumslateblue: [123, 104, 238],
|
|
6813
|
-
mediumspringgreen: [0, 250, 154],
|
|
6814
|
-
mediumturquoise: [72, 209, 204],
|
|
6815
|
-
mediumvioletred: [199, 21, 133],
|
|
6816
|
-
midnightblue: [25, 25, 112],
|
|
6817
|
-
mintcream: [245, 255, 250],
|
|
6818
|
-
mistyrose: [255, 228, 225],
|
|
6819
|
-
moccasin: [255, 228, 181],
|
|
6820
|
-
navajowhite: [255, 222, 173],
|
|
6821
|
-
navy: [0, 0, 128],
|
|
6822
|
-
oldlace: [253, 245, 230],
|
|
6823
|
-
olive: [128, 128, 0],
|
|
6824
|
-
olivedrab: [107, 142, 35],
|
|
6825
|
-
orange: [255, 165, 0],
|
|
6826
|
-
orangered: [255, 69, 0],
|
|
6827
|
-
orchid: [218, 112, 214],
|
|
6828
|
-
palegoldenrod: [238, 232, 170],
|
|
6829
|
-
palegreen: [152, 251, 152],
|
|
6830
|
-
paleturquoise: [175, 238, 238],
|
|
6831
|
-
palevioletred: [219, 112, 147],
|
|
6832
|
-
papayawhip: [255, 239, 213],
|
|
6833
|
-
peachpuff: [255, 218, 185],
|
|
6834
|
-
peru: [205, 133, 63],
|
|
6835
|
-
pink: [255, 192, 203],
|
|
6836
|
-
plum: [221, 160, 221],
|
|
6837
|
-
powderblue: [176, 224, 230],
|
|
6838
|
-
purple: [128, 0, 128],
|
|
6839
|
-
rebeccapurple: [102, 51, 153],
|
|
6840
|
-
red: [255, 0, 0],
|
|
6841
|
-
rosybrown: [188, 143, 143],
|
|
6842
|
-
royalblue: [65, 105, 225],
|
|
6843
|
-
saddlebrown: [139, 69, 19],
|
|
6844
|
-
salmon: [250, 128, 114],
|
|
6845
|
-
sandybrown: [244, 164, 96],
|
|
6846
|
-
seagreen: [46, 139, 87],
|
|
6847
|
-
seashell: [255, 245, 238],
|
|
6848
|
-
sienna: [160, 82, 45],
|
|
6849
|
-
silver: [192, 192, 192],
|
|
6850
|
-
skyblue: [135, 206, 235],
|
|
6851
|
-
slateblue: [106, 90, 205],
|
|
6852
|
-
slategray: [112, 128, 144],
|
|
6853
|
-
slategrey: [112, 128, 144],
|
|
6854
|
-
snow: [255, 250, 250],
|
|
6855
|
-
springgreen: [0, 255, 127],
|
|
6856
|
-
steelblue: [70, 130, 180],
|
|
6857
|
-
tan: [210, 180, 140],
|
|
6858
|
-
teal: [0, 128, 128],
|
|
6859
|
-
thistle: [216, 191, 216],
|
|
6860
|
-
tomato: [255, 99, 71],
|
|
6861
|
-
turquoise: [64, 224, 208],
|
|
6862
|
-
violet: [238, 130, 238],
|
|
6863
|
-
wheat: [245, 222, 179],
|
|
6864
|
-
white: [255, 255, 255],
|
|
6865
|
-
whitesmoke: [245, 245, 245],
|
|
6866
|
-
yellow: [255, 255, 0],
|
|
6867
|
-
yellowgreen: [154, 205, 50]
|
|
6868
|
-
};
|
|
6869
|
-
function parseColor(value) {
|
|
6870
|
-
if (!value) return null;
|
|
6871
|
-
const v = value.trim().toLowerCase();
|
|
6872
|
-
if (v === "inherit" || v === "currentcolor" || v === "initial" || v === "unset" || v.startsWith("var(")) {
|
|
6873
|
-
return null;
|
|
6916
|
+
function yahooAdditionalChecks($, clientId, _html, framework) {
|
|
6917
|
+
const warnings = [];
|
|
6918
|
+
warnings.push({
|
|
6919
|
+
severity: "info",
|
|
6920
|
+
client: clientId,
|
|
6921
|
+
property: "class",
|
|
6922
|
+
message: "Yahoo Mail rewrites CSS class names with a prefix. Class-based selectors in <style> blocks will still work but the names change."
|
|
6923
|
+
});
|
|
6924
|
+
if ($("[style*='background']").filter(
|
|
6925
|
+
(_, el) => ($(el).attr("style") || "").includes("url(")
|
|
6926
|
+
).length > 0) {
|
|
6927
|
+
warnings.push(makeWarning({
|
|
6928
|
+
severity: "warning",
|
|
6929
|
+
client: clientId,
|
|
6930
|
+
property: "background-image",
|
|
6931
|
+
message: "Yahoo Mail has inconsistent support for CSS background images."
|
|
6932
|
+
}, "background-image", clientId, framework));
|
|
6874
6933
|
}
|
|
6875
|
-
|
|
6876
|
-
|
|
6934
|
+
return warnings;
|
|
6935
|
+
}
|
|
6936
|
+
function thunderbirdAdditionalChecks($, clientId) {
|
|
6937
|
+
if (detectAnimations($)) {
|
|
6938
|
+
return [{
|
|
6939
|
+
severity: "info",
|
|
6940
|
+
client: clientId,
|
|
6941
|
+
property: "animation",
|
|
6942
|
+
message: "Thunderbird does not support CSS animations or transitions."
|
|
6943
|
+
}];
|
|
6944
|
+
}
|
|
6945
|
+
return [];
|
|
6946
|
+
}
|
|
6947
|
+
function heyAdditionalChecks(_$, clientId, html) {
|
|
6948
|
+
const warnings = [];
|
|
6949
|
+
if (!html.includes("prefers-color-scheme")) {
|
|
6950
|
+
warnings.push({
|
|
6951
|
+
severity: "info",
|
|
6952
|
+
client: clientId,
|
|
6953
|
+
property: "dark-mode",
|
|
6954
|
+
message: "HEY Mail supports @media (prefers-color-scheme: dark). Consider adding dark mode styles.",
|
|
6955
|
+
suggestion: "Add a @media (prefers-color-scheme: dark) block to optimize for HEY's audience."
|
|
6956
|
+
});
|
|
6957
|
+
}
|
|
6958
|
+
return warnings;
|
|
6959
|
+
}
|
|
6960
|
+
function superhumanAdditionalChecks($, clientId, html) {
|
|
6961
|
+
const warnings = [];
|
|
6962
|
+
if (detectAnimations($)) {
|
|
6963
|
+
warnings.push({
|
|
6964
|
+
severity: "info",
|
|
6965
|
+
client: clientId,
|
|
6966
|
+
property: "animation",
|
|
6967
|
+
message: "Superhuman may honor OS-level 'reduce motion' preferences, disabling animations.",
|
|
6968
|
+
suggestion: "Use @media (prefers-reduced-motion: reduce) to provide static fallbacks."
|
|
6969
|
+
});
|
|
6970
|
+
}
|
|
6971
|
+
warnings.push({
|
|
6972
|
+
severity: "info",
|
|
6973
|
+
client: clientId,
|
|
6974
|
+
property: "<style>",
|
|
6975
|
+
message: "Superhuman uses Chromium rendering with excellent CSS support. Flexbox, Grid, CSS variables, and modern properties all work."
|
|
6976
|
+
});
|
|
6977
|
+
return warnings;
|
|
6978
|
+
}
|
|
6979
|
+
var EMPTY_SET = /* @__PURE__ */ new Set();
|
|
6980
|
+
var CLIENT_CONFIGS = {
|
|
6981
|
+
"gmail-web": {
|
|
6982
|
+
id: "gmail-web",
|
|
6983
|
+
strippedProperties: GMAIL_STRIPPED_PROPERTIES,
|
|
6984
|
+
stripMode: "strip",
|
|
6985
|
+
valueStrips: [
|
|
6986
|
+
{ prop: "display", pattern: /grid/ },
|
|
6987
|
+
{ prop: "background", pattern: /linear-gradient|radial-gradient/ }
|
|
6988
|
+
],
|
|
6989
|
+
inlineAndStripStyles: true,
|
|
6990
|
+
stripExternalStylesheets: true,
|
|
6991
|
+
stripForms: true,
|
|
6992
|
+
stripSvg: true,
|
|
6993
|
+
additionalChecks: gmailAdditionalChecks
|
|
6994
|
+
},
|
|
6995
|
+
"gmail-android": {
|
|
6996
|
+
id: "gmail-android",
|
|
6997
|
+
strippedProperties: GMAIL_STRIPPED_PROPERTIES,
|
|
6998
|
+
stripMode: "strip",
|
|
6999
|
+
valueStrips: [
|
|
7000
|
+
{ prop: "display", pattern: /grid/ },
|
|
7001
|
+
{ prop: "background", pattern: /linear-gradient|radial-gradient/ }
|
|
7002
|
+
],
|
|
7003
|
+
inlineAndStripStyles: true,
|
|
7004
|
+
stripExternalStylesheets: true,
|
|
7005
|
+
stripForms: true,
|
|
7006
|
+
stripSvg: true,
|
|
7007
|
+
additionalChecks: gmailAdditionalChecks
|
|
7008
|
+
},
|
|
7009
|
+
"gmail-ios": {
|
|
7010
|
+
id: "gmail-ios",
|
|
7011
|
+
strippedProperties: GMAIL_STRIPPED_PROPERTIES,
|
|
7012
|
+
stripMode: "strip",
|
|
7013
|
+
valueStrips: [
|
|
7014
|
+
{ prop: "display", pattern: /grid/ },
|
|
7015
|
+
{ prop: "background", pattern: /linear-gradient|radial-gradient/ }
|
|
7016
|
+
],
|
|
7017
|
+
inlineAndStripStyles: true,
|
|
7018
|
+
stripExternalStylesheets: true,
|
|
7019
|
+
stripForms: true,
|
|
7020
|
+
stripSvg: true,
|
|
7021
|
+
additionalChecks: gmailAdditionalChecks
|
|
7022
|
+
},
|
|
7023
|
+
"outlook-windows": {
|
|
7024
|
+
id: "outlook-windows",
|
|
7025
|
+
strippedProperties: /* @__PURE__ */ new Set(["position", "transform", "animation", "transition"]),
|
|
7026
|
+
stripMode: "strip",
|
|
7027
|
+
inlineAndStripStyles: false,
|
|
7028
|
+
stripExternalStylesheets: false,
|
|
7029
|
+
stripForms: false,
|
|
7030
|
+
stripSvg: false
|
|
7031
|
+
},
|
|
7032
|
+
"outlook-windows-legacy": {
|
|
7033
|
+
id: "outlook-windows-legacy",
|
|
7034
|
+
strippedProperties: OUTLOOK_WORD_UNSUPPORTED,
|
|
7035
|
+
stripMode: "strip",
|
|
7036
|
+
valueStrips: [
|
|
7037
|
+
{ prop: "background", pattern: /linear-gradient|radial-gradient/ },
|
|
7038
|
+
{ prop: "background-image", pattern: /linear-gradient|radial-gradient/ }
|
|
7039
|
+
],
|
|
7040
|
+
inlineAndStripStyles: false,
|
|
7041
|
+
stripExternalStylesheets: false,
|
|
7042
|
+
stripForms: false,
|
|
7043
|
+
stripSvg: false,
|
|
7044
|
+
additionalChecks: outlookWindowsAdditionalChecks
|
|
7045
|
+
},
|
|
7046
|
+
"outlook-web": {
|
|
7047
|
+
id: "outlook-web",
|
|
7048
|
+
strippedProperties: /* @__PURE__ */ new Set(["position", "transform", "animation", "transition"]),
|
|
7049
|
+
stripMode: "strip",
|
|
7050
|
+
inlineAndStripStyles: false,
|
|
7051
|
+
stripExternalStylesheets: false,
|
|
7052
|
+
stripForms: false,
|
|
7053
|
+
stripSvg: false
|
|
7054
|
+
},
|
|
7055
|
+
"apple-mail-macos": {
|
|
7056
|
+
id: "apple-mail-macos",
|
|
7057
|
+
strippedProperties: EMPTY_SET,
|
|
7058
|
+
stripMode: "strip",
|
|
7059
|
+
inlineAndStripStyles: false,
|
|
7060
|
+
stripExternalStylesheets: false,
|
|
7061
|
+
stripForms: false,
|
|
7062
|
+
stripSvg: false,
|
|
7063
|
+
additionalChecks: appleMailAdditionalChecks
|
|
7064
|
+
},
|
|
7065
|
+
"apple-mail-ios": {
|
|
7066
|
+
id: "apple-mail-ios",
|
|
7067
|
+
strippedProperties: EMPTY_SET,
|
|
7068
|
+
stripMode: "strip",
|
|
7069
|
+
inlineAndStripStyles: false,
|
|
7070
|
+
stripExternalStylesheets: false,
|
|
7071
|
+
stripForms: false,
|
|
7072
|
+
stripSvg: false,
|
|
7073
|
+
additionalChecks: appleMailAdditionalChecks
|
|
7074
|
+
},
|
|
7075
|
+
"yahoo-mail": {
|
|
7076
|
+
id: "yahoo-mail",
|
|
7077
|
+
strippedProperties: /* @__PURE__ */ new Set(["position", "box-shadow", "transform", "animation", "transition", "opacity"]),
|
|
7078
|
+
stripMode: "strip",
|
|
7079
|
+
inlineAndStripStyles: false,
|
|
7080
|
+
stripExternalStylesheets: false,
|
|
7081
|
+
stripForms: false,
|
|
7082
|
+
stripSvg: false,
|
|
7083
|
+
additionalChecks: yahooAdditionalChecks
|
|
7084
|
+
},
|
|
7085
|
+
"samsung-mail": {
|
|
7086
|
+
id: "samsung-mail",
|
|
7087
|
+
strippedProperties: /* @__PURE__ */ new Set(["box-shadow", "transform", "animation", "transition", "opacity"]),
|
|
7088
|
+
stripMode: "info",
|
|
7089
|
+
inlineAndStripStyles: false,
|
|
7090
|
+
stripExternalStylesheets: false,
|
|
7091
|
+
stripForms: false,
|
|
7092
|
+
stripSvg: false
|
|
7093
|
+
},
|
|
7094
|
+
"thunderbird": {
|
|
7095
|
+
id: "thunderbird",
|
|
7096
|
+
strippedProperties: EMPTY_SET,
|
|
7097
|
+
stripMode: "strip",
|
|
7098
|
+
inlineAndStripStyles: false,
|
|
7099
|
+
stripExternalStylesheets: false,
|
|
7100
|
+
stripForms: false,
|
|
7101
|
+
stripSvg: false,
|
|
7102
|
+
additionalChecks: thunderbirdAdditionalChecks
|
|
7103
|
+
},
|
|
7104
|
+
"hey-mail": {
|
|
7105
|
+
id: "hey-mail",
|
|
7106
|
+
strippedProperties: /* @__PURE__ */ new Set(["transform", "animation", "transition"]),
|
|
7107
|
+
stripMode: "strip",
|
|
7108
|
+
valueStrips: [
|
|
7109
|
+
{ prop: "position", pattern: /fixed|sticky/ }
|
|
7110
|
+
],
|
|
7111
|
+
inlineAndStripStyles: false,
|
|
7112
|
+
stripExternalStylesheets: true,
|
|
7113
|
+
stripForms: true,
|
|
7114
|
+
stripSvg: false,
|
|
7115
|
+
additionalChecks: heyAdditionalChecks
|
|
7116
|
+
},
|
|
7117
|
+
"superhuman": {
|
|
7118
|
+
id: "superhuman",
|
|
7119
|
+
strippedProperties: EMPTY_SET,
|
|
7120
|
+
stripMode: "strip",
|
|
7121
|
+
inlineAndStripStyles: false,
|
|
7122
|
+
stripExternalStylesheets: true,
|
|
7123
|
+
stripForms: true,
|
|
7124
|
+
stripSvg: false,
|
|
7125
|
+
additionalChecks: superhumanAdditionalChecks
|
|
7126
|
+
}
|
|
7127
|
+
};
|
|
7128
|
+
function applyTransform(html, config, framework) {
|
|
7129
|
+
const $ = cheerio2.load(html);
|
|
7130
|
+
const warnings = [];
|
|
7131
|
+
const clientId = config.id;
|
|
7132
|
+
if (config.additionalChecks && config.inlineAndStripStyles) {
|
|
7133
|
+
warnings.push(...config.additionalChecks($, clientId, html, framework));
|
|
7134
|
+
}
|
|
7135
|
+
if (config.inlineAndStripStyles) {
|
|
7136
|
+
const preserved = inlineStyles($);
|
|
7137
|
+
$("style").remove();
|
|
7138
|
+
if (preserved.trim()) {
|
|
7139
|
+
const head = $("head");
|
|
7140
|
+
if (head.length > 0) {
|
|
7141
|
+
head.append(`<style>${preserved}</style>`);
|
|
7142
|
+
} else {
|
|
7143
|
+
$("body").prepend(`<style>${preserved}</style>`);
|
|
7144
|
+
}
|
|
7145
|
+
}
|
|
7146
|
+
}
|
|
7147
|
+
if (config.stripExternalStylesheets) {
|
|
7148
|
+
if ($("link[rel='stylesheet']").length > 0) {
|
|
7149
|
+
warnings.push(makeWarning({
|
|
7150
|
+
severity: "error",
|
|
7151
|
+
client: clientId,
|
|
7152
|
+
property: "<link>",
|
|
7153
|
+
message: `${clientId} does not load external stylesheets.`
|
|
7154
|
+
}, "<link>", clientId, framework));
|
|
7155
|
+
$("link[rel='stylesheet']").remove();
|
|
7156
|
+
}
|
|
7157
|
+
}
|
|
7158
|
+
if (config.stripForms && $("form").length > 0) {
|
|
7159
|
+
warnings.push(makeWarning({
|
|
7160
|
+
severity: "error",
|
|
7161
|
+
client: clientId,
|
|
7162
|
+
property: "<form>",
|
|
7163
|
+
message: `${clientId} removes form elements.`
|
|
7164
|
+
}, "<form>", clientId, framework));
|
|
7165
|
+
$("form").each((_, el) => {
|
|
7166
|
+
$(el).replaceWith($(el).html() || "");
|
|
7167
|
+
});
|
|
7168
|
+
}
|
|
7169
|
+
if (config.stripSvg && $("svg").length > 0) {
|
|
7170
|
+
warnings.push(makeWarning({
|
|
7171
|
+
severity: "error",
|
|
7172
|
+
client: clientId,
|
|
7173
|
+
property: "<svg>",
|
|
7174
|
+
message: `${clientId} does not support inline SVG elements.`
|
|
7175
|
+
}, "<svg>", clientId, framework));
|
|
7176
|
+
$("svg").each((_, el) => {
|
|
7177
|
+
$(el).replaceWith('<img alt="[SVG not supported]" />');
|
|
7178
|
+
});
|
|
7179
|
+
}
|
|
7180
|
+
if (config.strippedProperties.size > 0 || config.valueStrips && config.valueStrips.length > 0) {
|
|
7181
|
+
$("[style]").each((_, el) => {
|
|
7182
|
+
const style = $(el).attr("style") || "";
|
|
7183
|
+
const props = parseInlineStyle(style);
|
|
7184
|
+
const removed = [];
|
|
7185
|
+
props.forEach((value, prop) => {
|
|
7186
|
+
var _a;
|
|
7187
|
+
if (config.strippedProperties.has(prop)) {
|
|
7188
|
+
if (config.stripMode === "strip") {
|
|
7189
|
+
removed.push(prop);
|
|
7190
|
+
props.delete(prop);
|
|
7191
|
+
} else {
|
|
7192
|
+
warnings.push(makeWarning({
|
|
7193
|
+
severity: "info",
|
|
7194
|
+
client: clientId,
|
|
7195
|
+
property: prop,
|
|
7196
|
+
message: `${clientId} has limited support for "${prop}".`
|
|
7197
|
+
}, prop, clientId, framework));
|
|
7198
|
+
}
|
|
7199
|
+
return;
|
|
7200
|
+
}
|
|
7201
|
+
for (const vs of (_a = config.valueStrips) != null ? _a : []) {
|
|
7202
|
+
if (prop === vs.prop && vs.pattern.test(value)) {
|
|
7203
|
+
removed.push(prop);
|
|
7204
|
+
props.delete(prop);
|
|
7205
|
+
return;
|
|
7206
|
+
}
|
|
7207
|
+
}
|
|
7208
|
+
});
|
|
7209
|
+
if (removed.length > 0) {
|
|
7210
|
+
$(el).attr("style", serializeStyle(props));
|
|
7211
|
+
for (const prop of removed) {
|
|
7212
|
+
warnings.push(makeWarning({
|
|
7213
|
+
severity: "warning",
|
|
7214
|
+
client: clientId,
|
|
7215
|
+
property: prop,
|
|
7216
|
+
message: `${clientId} strips "${prop}" from styles.`
|
|
7217
|
+
}, prop, clientId, framework));
|
|
7218
|
+
}
|
|
7219
|
+
}
|
|
7220
|
+
});
|
|
7221
|
+
}
|
|
7222
|
+
if (config.inlineAndStripStyles) {
|
|
7223
|
+
warnings.push(...gmailPostChecks($, clientId, html, framework));
|
|
7224
|
+
}
|
|
7225
|
+
if (config.additionalChecks && !config.inlineAndStripStyles) {
|
|
7226
|
+
warnings.push(...config.additionalChecks($, clientId, html, framework));
|
|
7227
|
+
}
|
|
7228
|
+
return { clientId, html: $.html(), warnings };
|
|
7229
|
+
}
|
|
7230
|
+
function transformForClient(html, clientId, framework) {
|
|
7231
|
+
if (!html || !html.trim()) {
|
|
7232
|
+
return { clientId, html: html || "", warnings: [] };
|
|
7233
|
+
}
|
|
7234
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
7235
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
7236
|
+
}
|
|
7237
|
+
const config = CLIENT_CONFIGS[clientId];
|
|
7238
|
+
if (!config) {
|
|
7239
|
+
return {
|
|
7240
|
+
clientId,
|
|
7241
|
+
html,
|
|
7242
|
+
warnings: [
|
|
7243
|
+
{
|
|
7244
|
+
severity: "info",
|
|
7245
|
+
client: clientId,
|
|
7246
|
+
property: "unknown",
|
|
7247
|
+
message: `No transformation rules available for client "${clientId}".`
|
|
7248
|
+
}
|
|
7249
|
+
]
|
|
7250
|
+
};
|
|
7251
|
+
}
|
|
7252
|
+
const downleveled = downlevelCSS(html);
|
|
7253
|
+
return applyTransform(downleveled, config, framework);
|
|
7254
|
+
}
|
|
7255
|
+
function transformForAllClients(html, framework) {
|
|
7256
|
+
if (!html || !html.trim()) {
|
|
7257
|
+
return Object.keys(CLIENT_CONFIGS).map((clientId) => ({
|
|
7258
|
+
clientId,
|
|
7259
|
+
html: html || "",
|
|
7260
|
+
warnings: []
|
|
7261
|
+
}));
|
|
7262
|
+
}
|
|
7263
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
7264
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
7265
|
+
}
|
|
7266
|
+
const downleveled = downlevelCSS(html);
|
|
7267
|
+
return Object.keys(CLIENT_CONFIGS).map(
|
|
7268
|
+
(clientId) => applyTransform(downleveled, CLIENT_CONFIGS[clientId], framework)
|
|
7269
|
+
);
|
|
7270
|
+
}
|
|
7271
|
+
|
|
7272
|
+
// src/analyze.ts
|
|
7273
|
+
var cheerio3 = __toESM(require("cheerio"), 1);
|
|
7274
|
+
var csstree3 = __toESM(require("css-tree"), 1);
|
|
7275
|
+
var HTML_ELEMENT_SELECTORS = {
|
|
7276
|
+
"<style>": "style",
|
|
7277
|
+
"<link>": "link[rel='stylesheet']",
|
|
7278
|
+
"<svg>": "svg",
|
|
7279
|
+
"<video>": "video",
|
|
7280
|
+
"<form>": "form, input, button[type='submit']",
|
|
7281
|
+
"<audio>": "audio",
|
|
7282
|
+
"<picture>": "picture",
|
|
7283
|
+
"<dialog>": "dialog",
|
|
7284
|
+
"<meter>": "meter",
|
|
7285
|
+
"<progress>": "progress",
|
|
7286
|
+
"<select>": "select",
|
|
7287
|
+
"<textarea>": "textarea",
|
|
7288
|
+
"<marquee>": "marquee",
|
|
7289
|
+
"<object>": "object",
|
|
7290
|
+
"<base>": "base"
|
|
7291
|
+
};
|
|
7292
|
+
var HTML_ELEMENT_SEVERITY = {
|
|
7293
|
+
"<style>": "error",
|
|
7294
|
+
"<link>": "error",
|
|
7295
|
+
"<svg>": "error",
|
|
7296
|
+
"<form>": "error",
|
|
7297
|
+
"<video>": "warning",
|
|
7298
|
+
"<audio>": "warning",
|
|
7299
|
+
"<picture>": "warning",
|
|
7300
|
+
"<dialog>": "warning",
|
|
7301
|
+
"<marquee>": "warning",
|
|
7302
|
+
"<meter>": "warning",
|
|
7303
|
+
"<progress>": "warning",
|
|
7304
|
+
"<select>": "warning",
|
|
7305
|
+
"<textarea>": "warning",
|
|
7306
|
+
"<object>": "warning",
|
|
7307
|
+
"<base>": "warning"
|
|
7308
|
+
};
|
|
7309
|
+
var HTML_ELEMENT_MESSAGES = {
|
|
7310
|
+
"<style>": (n) => `${n} strips <style> blocks. Styles must be inlined.`,
|
|
7311
|
+
"<link>": (n) => `${n} does not support external stylesheets.`,
|
|
7312
|
+
"<svg>": (n) => `${n} does not support inline SVG.`,
|
|
7313
|
+
"<video>": (n) => `${n} does not support <video> elements.`,
|
|
7314
|
+
"<form>": (n) => `${n} strips form elements.`
|
|
7315
|
+
};
|
|
7316
|
+
var COMPOUND_DETECTORS = [
|
|
7317
|
+
{ key: "display:flex", property: "display", valueIncludes: "flex" },
|
|
7318
|
+
{ key: "display:grid", property: "display", valueIncludes: "grid" },
|
|
7319
|
+
{ key: "display:none", property: "display", valueIncludes: "none" }
|
|
7320
|
+
];
|
|
7321
|
+
var CSS_FUNCTION_DETECTORS = CSS_FUNCTION_FEATURES.map((fn) => ({
|
|
7322
|
+
key: fn,
|
|
7323
|
+
pattern: `${fn}(`
|
|
7324
|
+
// require opening paren — matches "min(" but not "Minion"
|
|
7325
|
+
}));
|
|
7326
|
+
function analyzeEmailFromDom($, framework) {
|
|
7327
|
+
const warnings = [];
|
|
7328
|
+
const seenWarnings = /* @__PURE__ */ new Set();
|
|
7329
|
+
function addWarning(w) {
|
|
7330
|
+
const key = `${w.client}:${w.property}:${w.severity}:${w.selector || ""}`;
|
|
7331
|
+
if (!seenWarnings.has(key)) {
|
|
7332
|
+
seenWarnings.add(key);
|
|
7333
|
+
warnings.push(w);
|
|
7334
|
+
}
|
|
7335
|
+
}
|
|
7336
|
+
function describeSelector(el) {
|
|
7337
|
+
var _a;
|
|
7338
|
+
const $el = $(el);
|
|
7339
|
+
const tag = ((_a = el.tagName) == null ? void 0 : _a.toLowerCase()) || "";
|
|
7340
|
+
const cls = $el.attr("class");
|
|
7341
|
+
const id = $el.attr("id");
|
|
7342
|
+
if (id) return `${tag}#${id}`;
|
|
7343
|
+
if (cls) return `${tag}.${cls.split(/\s+/)[0]}`;
|
|
7344
|
+
const href = $el.attr("href");
|
|
7345
|
+
if (href) return `${tag}[href]`;
|
|
7346
|
+
return tag;
|
|
7347
|
+
}
|
|
7348
|
+
for (const feature of HTML_ELEMENT_FEATURES) {
|
|
7349
|
+
const selector = HTML_ELEMENT_SELECTORS[feature];
|
|
7350
|
+
if (!selector) continue;
|
|
7351
|
+
if ($(selector).length === 0) continue;
|
|
7352
|
+
const supportData = CSS_SUPPORT[feature];
|
|
7353
|
+
if (!supportData) continue;
|
|
7354
|
+
const baseSeverity = HTML_ELEMENT_SEVERITY[feature] || "warning";
|
|
7355
|
+
for (const client of EMAIL_CLIENTS) {
|
|
7356
|
+
const support = supportData[client.id];
|
|
7357
|
+
if (support === "unsupported") {
|
|
7358
|
+
const msgFn = HTML_ELEMENT_MESSAGES[feature];
|
|
7359
|
+
const message = msgFn ? msgFn(client.name) : `${client.name} does not support ${feature}.`;
|
|
7360
|
+
const sug = getSuggestion(feature, client.id, framework);
|
|
7361
|
+
const fix = getCodeFix(feature, client.id, framework);
|
|
7362
|
+
addWarning(__spreadValues({
|
|
7363
|
+
severity: baseSeverity,
|
|
7364
|
+
client: client.id,
|
|
7365
|
+
property: feature,
|
|
7366
|
+
message,
|
|
7367
|
+
suggestion: sug.text,
|
|
7368
|
+
fix,
|
|
7369
|
+
fixType: getFixType(feature)
|
|
7370
|
+
}, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(feature, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
7371
|
+
} else if (support === "partial" && feature === "<style>") {
|
|
7372
|
+
const sug = getSuggestion("<style>:partial", client.id, framework);
|
|
7373
|
+
const fix = getCodeFix("<style>", client.id, framework);
|
|
7374
|
+
addWarning(__spreadValues({
|
|
7375
|
+
severity: "warning",
|
|
7376
|
+
client: client.id,
|
|
7377
|
+
property: "<style>",
|
|
7378
|
+
message: `${client.name} has partial <style> support (head only, with limitations). Inline styles recommended.`,
|
|
7379
|
+
suggestion: sug.text,
|
|
7380
|
+
fix,
|
|
7381
|
+
fixType: getFixType("<style>")
|
|
7382
|
+
}, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<style>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
7383
|
+
}
|
|
7384
|
+
}
|
|
7385
|
+
}
|
|
7386
|
+
const parsedAtRules = /* @__PURE__ */ new Set();
|
|
7387
|
+
const parsedProperties = /* @__PURE__ */ new Set();
|
|
7388
|
+
const propertyLines = /* @__PURE__ */ new Map();
|
|
7389
|
+
const detectedCssFunctions = /* @__PURE__ */ new Set();
|
|
7390
|
+
const detectedPseudoClasses = /* @__PURE__ */ new Set();
|
|
7391
|
+
const detectedPseudoElements = /* @__PURE__ */ new Set();
|
|
7392
|
+
$("style").each((_, el) => {
|
|
7393
|
+
const cssText = $(el).text();
|
|
7394
|
+
try {
|
|
7395
|
+
const ast = csstree3.parse(cssText, { parseCustomProperty: true, positions: true });
|
|
7396
|
+
csstree3.walk(ast, {
|
|
7397
|
+
enter(node) {
|
|
7398
|
+
if (node.type === "Atrule") {
|
|
7399
|
+
parsedAtRules.add(`@${node.name}`);
|
|
7400
|
+
}
|
|
7401
|
+
if (node.type === "PseudoClassSelector") {
|
|
7402
|
+
detectedPseudoClasses.add(`:${node.name}`);
|
|
7403
|
+
}
|
|
7404
|
+
if (node.type === "PseudoElementSelector") {
|
|
7405
|
+
detectedPseudoElements.add(`::${node.name}`);
|
|
7406
|
+
}
|
|
7407
|
+
if (node.type === "Declaration") {
|
|
7408
|
+
const prop = node.property.toLowerCase();
|
|
7409
|
+
parsedProperties.add(prop);
|
|
7410
|
+
if (node.loc && !propertyLines.has(prop)) {
|
|
7411
|
+
propertyLines.set(prop, node.loc.start.line);
|
|
7412
|
+
}
|
|
7413
|
+
const valueStr = csstree3.generate(node.value);
|
|
7414
|
+
for (const det of COMPOUND_DETECTORS) {
|
|
7415
|
+
if (prop === det.property && valueStr.includes(det.valueIncludes)) {
|
|
7416
|
+
parsedProperties.add(det.key);
|
|
7417
|
+
if (node.loc && !propertyLines.has(det.key)) {
|
|
7418
|
+
propertyLines.set(det.key, node.loc.start.line);
|
|
7419
|
+
}
|
|
7420
|
+
}
|
|
7421
|
+
}
|
|
7422
|
+
for (const fn of CSS_FUNCTION_DETECTORS) {
|
|
7423
|
+
if (valueStr.includes(fn.pattern)) {
|
|
7424
|
+
detectedCssFunctions.add(fn.key);
|
|
7425
|
+
if (node.loc && !propertyLines.has(fn.key)) {
|
|
7426
|
+
propertyLines.set(fn.key, node.loc.start.line);
|
|
7427
|
+
}
|
|
7428
|
+
}
|
|
7429
|
+
}
|
|
7430
|
+
}
|
|
7431
|
+
}
|
|
7432
|
+
});
|
|
7433
|
+
} catch (e) {
|
|
7434
|
+
}
|
|
7435
|
+
});
|
|
7436
|
+
for (const atRule of AT_RULE_FEATURES) {
|
|
7437
|
+
if (!parsedAtRules.has(atRule)) continue;
|
|
7438
|
+
checkPropertySupport(atRule, addWarning, framework);
|
|
6877
7439
|
}
|
|
6878
|
-
const
|
|
6879
|
-
|
|
6880
|
-
|
|
7440
|
+
const cssPropertiesToCheck = Object.keys(CSS_SUPPORT).filter(
|
|
7441
|
+
(k) => !k.startsWith("<") && !k.startsWith("@")
|
|
7442
|
+
);
|
|
7443
|
+
$("[style]").each((_, el) => {
|
|
7444
|
+
const style = $(el).attr("style") || "";
|
|
7445
|
+
const props = parseStyleProperties(style);
|
|
7446
|
+
const selector = describeSelector(el);
|
|
7447
|
+
for (const prop of props) {
|
|
7448
|
+
for (const det of COMPOUND_DETECTORS) {
|
|
7449
|
+
if (prop === det.property) {
|
|
7450
|
+
const value2 = getStyleValue(style, prop);
|
|
7451
|
+
if (value2 == null ? void 0 : value2.includes(det.valueIncludes)) {
|
|
7452
|
+
checkPropertySupport(det.key, addWarning, framework, selector);
|
|
7453
|
+
}
|
|
7454
|
+
}
|
|
7455
|
+
}
|
|
7456
|
+
if (cssPropertiesToCheck.includes(prop)) {
|
|
7457
|
+
checkPropertySupport(prop, addWarning, framework, selector);
|
|
7458
|
+
}
|
|
7459
|
+
const value = getStyleValue(style, prop);
|
|
7460
|
+
if (value) {
|
|
7461
|
+
for (const fn of CSS_FUNCTION_DETECTORS) {
|
|
7462
|
+
if (value.includes(fn.pattern)) {
|
|
7463
|
+
checkPropertySupport(fn.key, addWarning, framework, selector);
|
|
7464
|
+
}
|
|
7465
|
+
}
|
|
7466
|
+
}
|
|
7467
|
+
}
|
|
7468
|
+
});
|
|
7469
|
+
for (const prop of parsedProperties) {
|
|
7470
|
+
if (prop.includes(":")) continue;
|
|
7471
|
+
if (!cssPropertiesToCheck.includes(prop)) continue;
|
|
7472
|
+
checkPropertySupport(prop, addWarning, framework, void 0, propertyLines.get(prop));
|
|
6881
7473
|
}
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
if (
|
|
6885
|
-
|
|
6886
|
-
r: parseInt(hex[0] + hex[0], 16),
|
|
6887
|
-
g: parseInt(hex[1] + hex[1], 16),
|
|
6888
|
-
b: parseInt(hex[2] + hex[2], 16),
|
|
6889
|
-
a: 1
|
|
6890
|
-
};
|
|
7474
|
+
for (const compound of COMPOUND_VALUE_FEATURES) {
|
|
7475
|
+
if (compound.startsWith(":") || compound.startsWith("::")) continue;
|
|
7476
|
+
if (parsedProperties.has(compound)) {
|
|
7477
|
+
checkPropertySupport(compound, addWarning, framework, void 0, propertyLines.get(compound));
|
|
6891
7478
|
}
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
b: parseInt(hex.slice(4, 6), 16),
|
|
6897
|
-
a: 1
|
|
6898
|
-
};
|
|
7479
|
+
}
|
|
7480
|
+
for (const pseudo of detectedPseudoClasses) {
|
|
7481
|
+
if (CSS_SUPPORT[pseudo]) {
|
|
7482
|
+
checkPropertySupport(pseudo, addWarning, framework);
|
|
6899
7483
|
}
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
b: parseInt(hex.slice(4, 6), 16),
|
|
6905
|
-
a: parseInt(hex.slice(6, 8), 16) / 255
|
|
6906
|
-
};
|
|
7484
|
+
}
|
|
7485
|
+
for (const pseudo of detectedPseudoElements) {
|
|
7486
|
+
if (CSS_SUPPORT[pseudo]) {
|
|
7487
|
+
checkPropertySupport(pseudo, addWarning, framework);
|
|
6907
7488
|
}
|
|
6908
|
-
return null;
|
|
6909
7489
|
}
|
|
6910
|
-
const
|
|
6911
|
-
|
|
6912
|
-
return {
|
|
6913
|
-
r: Math.min(255, parseInt(rgbMatch[1], 10)),
|
|
6914
|
-
g: Math.min(255, parseInt(rgbMatch[2], 10)),
|
|
6915
|
-
b: Math.min(255, parseInt(rgbMatch[3], 10)),
|
|
6916
|
-
a: rgbMatch[4] !== void 0 ? Math.min(1, parseFloat(rgbMatch[4])) : 1
|
|
6917
|
-
};
|
|
7490
|
+
for (const fn of detectedCssFunctions) {
|
|
7491
|
+
checkPropertySupport(fn, addWarning, framework, void 0, propertyLines.get(fn));
|
|
6918
7492
|
}
|
|
6919
|
-
|
|
7493
|
+
const severityOrder = { error: 0, warning: 1, info: 2 };
|
|
7494
|
+
warnings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
7495
|
+
return warnings;
|
|
6920
7496
|
}
|
|
6921
|
-
function
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
7497
|
+
function analyzeEmail(html, framework) {
|
|
7498
|
+
if (!html || !html.trim()) {
|
|
7499
|
+
return [];
|
|
7500
|
+
}
|
|
7501
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
7502
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
7503
|
+
}
|
|
7504
|
+
const $ = cheerio3.load(html);
|
|
7505
|
+
return analyzeEmailFromDom($, framework);
|
|
6927
7506
|
}
|
|
6928
|
-
function
|
|
6929
|
-
|
|
6930
|
-
const darker = Math.min(l1, l2);
|
|
6931
|
-
return (lighter + 0.05) / (darker + 0.05);
|
|
7507
|
+
function getFixType(prop) {
|
|
7508
|
+
return STRUCTURAL_FIX_PROPERTIES.has(prop) ? "structural" : "css";
|
|
6932
7509
|
}
|
|
6933
|
-
function
|
|
6934
|
-
|
|
6935
|
-
if (
|
|
6936
|
-
|
|
6937
|
-
|
|
7510
|
+
function checkPropertySupport(prop, addWarning, framework, selector, line) {
|
|
7511
|
+
const supportData = CSS_SUPPORT[prop];
|
|
7512
|
+
if (!supportData) return;
|
|
7513
|
+
const fixType = getFixType(prop);
|
|
7514
|
+
for (const client of EMAIL_CLIENTS) {
|
|
7515
|
+
const support = supportData[client.id] || "unknown";
|
|
7516
|
+
if (support === "unsupported") {
|
|
7517
|
+
const sug = getSuggestion(prop, client.id, framework);
|
|
7518
|
+
const fix = getCodeFix(prop, client.id, framework);
|
|
7519
|
+
addWarning(__spreadValues(__spreadValues(__spreadValues({
|
|
7520
|
+
severity: "warning",
|
|
7521
|
+
client: client.id,
|
|
7522
|
+
property: prop,
|
|
7523
|
+
message: `${client.name} does not support "${prop}".`,
|
|
7524
|
+
suggestion: sug.text,
|
|
7525
|
+
fix,
|
|
7526
|
+
fixType
|
|
7527
|
+
}, selector ? { selector } : {}), line !== void 0 ? { line } : {}), framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(prop, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
7528
|
+
} else if (support === "partial") {
|
|
7529
|
+
const sug = getSuggestion(prop, client.id, framework);
|
|
7530
|
+
const fix = getCodeFix(prop, client.id, framework);
|
|
7531
|
+
addWarning(__spreadValues(__spreadValues(__spreadValues({
|
|
7532
|
+
severity: "info",
|
|
7533
|
+
client: client.id,
|
|
7534
|
+
property: prop,
|
|
7535
|
+
message: `${client.name} has partial support for "${prop}".`,
|
|
7536
|
+
suggestion: sug.text,
|
|
7537
|
+
fix,
|
|
7538
|
+
fixType
|
|
7539
|
+
}, selector ? { selector } : {}), line !== void 0 ? { line } : {}), framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(prop, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
7540
|
+
}
|
|
7541
|
+
}
|
|
6938
7542
|
}
|
|
6939
|
-
function
|
|
6940
|
-
const
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
7543
|
+
function generateCompatibilityScore(warnings) {
|
|
7544
|
+
const result = {};
|
|
7545
|
+
for (const client of EMAIL_CLIENTS) {
|
|
7546
|
+
const clientWarnings = warnings.filter((w) => w.client === client.id);
|
|
7547
|
+
const errorProps = new Set(clientWarnings.filter((w) => w.severity === "error").map((w) => w.property));
|
|
7548
|
+
const warnProps = new Set(clientWarnings.filter((w) => w.severity === "warning").map((w) => w.property));
|
|
7549
|
+
const infoProps = new Set(clientWarnings.filter((w) => w.severity === "info").map((w) => w.property));
|
|
7550
|
+
const errors = errorProps.size;
|
|
7551
|
+
const warns = warnProps.size;
|
|
7552
|
+
const info = infoProps.size;
|
|
7553
|
+
const score = Math.max(0, Math.min(100, 100 - errors * 10 - warns * 3));
|
|
7554
|
+
result[client.id] = { score, errors, warnings: warns, info };
|
|
7555
|
+
}
|
|
7556
|
+
return result;
|
|
7557
|
+
}
|
|
7558
|
+
function warningsForClient(warnings, clientId) {
|
|
7559
|
+
return warnings.filter((w) => w.client === clientId);
|
|
7560
|
+
}
|
|
7561
|
+
function errorWarnings(warnings) {
|
|
7562
|
+
return warnings.filter((w) => w.severity === "error");
|
|
7563
|
+
}
|
|
7564
|
+
function structuralWarnings(warnings) {
|
|
7565
|
+
return warnings.filter((w) => w.fixType === "structural");
|
|
6946
7566
|
}
|
|
6947
7567
|
|
|
6948
7568
|
// src/dark-mode.ts
|
|
7569
|
+
var cheerio4 = __toESM(require("cheerio"), 1);
|
|
7570
|
+
var csstree4 = __toESM(require("css-tree"), 1);
|
|
6949
7571
|
var LIGHT_THRESHOLD = 0.7;
|
|
6950
7572
|
var DARK_THRESHOLD = 0.15;
|
|
6951
7573
|
function simulateDarkMode(html, clientId) {
|
|
@@ -6955,7 +7577,7 @@ function simulateDarkMode(html, clientId) {
|
|
|
6955
7577
|
if (html.length > MAX_HTML_SIZE) {
|
|
6956
7578
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
6957
7579
|
}
|
|
6958
|
-
const $ =
|
|
7580
|
+
const $ = cheerio4.load(html);
|
|
6959
7581
|
const warnings = [];
|
|
6960
7582
|
$("img").each((_, el) => {
|
|
6961
7583
|
const src = $(el).attr("src") || "";
|
|
@@ -7152,35 +7774,35 @@ function applyColorInversion($, mode) {
|
|
|
7152
7774
|
$("style").each((_, el) => {
|
|
7153
7775
|
const cssText = $(el).text();
|
|
7154
7776
|
try {
|
|
7155
|
-
const ast =
|
|
7777
|
+
const ast = csstree4.parse(cssText, { parseCustomProperty: true });
|
|
7156
7778
|
let modified = false;
|
|
7157
|
-
|
|
7779
|
+
csstree4.walk(ast, {
|
|
7158
7780
|
enter(node) {
|
|
7159
7781
|
if (node.type !== "Declaration") return;
|
|
7160
7782
|
const prop = node.property.toLowerCase();
|
|
7161
7783
|
if (!COLOR_PROPS.has(prop) && prop !== "background") return;
|
|
7162
|
-
const valueStr =
|
|
7784
|
+
const valueStr = csstree4.generate(node.value);
|
|
7163
7785
|
if (prop === "background") {
|
|
7164
7786
|
const bgColor = extractBackgroundColor(valueStr);
|
|
7165
7787
|
if (bgColor) {
|
|
7166
7788
|
const inverted = invertColor(bgColor, mode);
|
|
7167
7789
|
if (inverted) {
|
|
7168
7790
|
const newValue = valueStr.replace(bgColor, inverted);
|
|
7169
|
-
node.value =
|
|
7791
|
+
node.value = csstree4.parse(newValue, { context: "value" });
|
|
7170
7792
|
modified = true;
|
|
7171
7793
|
}
|
|
7172
7794
|
}
|
|
7173
7795
|
} else {
|
|
7174
7796
|
const inverted = invertColor(valueStr, mode);
|
|
7175
7797
|
if (inverted) {
|
|
7176
|
-
node.value =
|
|
7798
|
+
node.value = csstree4.parse(inverted, { context: "value" });
|
|
7177
7799
|
modified = true;
|
|
7178
7800
|
}
|
|
7179
7801
|
}
|
|
7180
7802
|
}
|
|
7181
7803
|
});
|
|
7182
7804
|
if (modified) {
|
|
7183
|
-
$(el).text(
|
|
7805
|
+
$(el).text(csstree4.generate(ast));
|
|
7184
7806
|
}
|
|
7185
7807
|
} catch (e) {
|
|
7186
7808
|
}
|
|
@@ -7492,7 +8114,7 @@ function extractCode(response) {
|
|
|
7492
8114
|
}
|
|
7493
8115
|
|
|
7494
8116
|
// src/spam-scorer.ts
|
|
7495
|
-
var
|
|
8117
|
+
var cheerio5 = __toESM(require("cheerio"), 1);
|
|
7496
8118
|
var SPAM_TRIGGER_PHRASES = [
|
|
7497
8119
|
"act now",
|
|
7498
8120
|
"limited time",
|
|
@@ -7923,12 +8545,12 @@ function analyzeSpam(html, options) {
|
|
|
7923
8545
|
if (html.length > MAX_HTML_SIZE) {
|
|
7924
8546
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
7925
8547
|
}
|
|
7926
|
-
const $ =
|
|
8548
|
+
const $ = cheerio5.load(html);
|
|
7927
8549
|
return analyzeSpamFromDom($, options);
|
|
7928
8550
|
}
|
|
7929
8551
|
|
|
7930
8552
|
// src/link-validator.ts
|
|
7931
|
-
var
|
|
8553
|
+
var cheerio6 = __toESM(require("cheerio"), 1);
|
|
7932
8554
|
function classifyHref(href) {
|
|
7933
8555
|
if (!href || !href.trim()) return "empty";
|
|
7934
8556
|
const h = href.trim().toLowerCase();
|
|
@@ -8124,12 +8746,12 @@ function validateLinks(html) {
|
|
|
8124
8746
|
if (html.length > MAX_HTML_SIZE) {
|
|
8125
8747
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
8126
8748
|
}
|
|
8127
|
-
const $ =
|
|
8749
|
+
const $ = cheerio6.load(html);
|
|
8128
8750
|
return validateLinksFromDom($);
|
|
8129
8751
|
}
|
|
8130
8752
|
|
|
8131
8753
|
// src/accessibility-checker.ts
|
|
8132
|
-
var
|
|
8754
|
+
var cheerio7 = __toESM(require("cheerio"), 1);
|
|
8133
8755
|
var RULE_PENALTY_CAPS = {
|
|
8134
8756
|
"img-missing-alt": 3,
|
|
8135
8757
|
"link-generic-text": 3,
|
|
@@ -8432,12 +9054,12 @@ function checkAccessibility(html) {
|
|
|
8432
9054
|
if (html.length > MAX_HTML_SIZE) {
|
|
8433
9055
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
8434
9056
|
}
|
|
8435
|
-
const $ =
|
|
9057
|
+
const $ = cheerio7.load(html);
|
|
8436
9058
|
return checkAccessibilityFromDom($);
|
|
8437
9059
|
}
|
|
8438
9060
|
|
|
8439
9061
|
// src/image-analyzer.ts
|
|
8440
|
-
var
|
|
9062
|
+
var cheerio8 = __toESM(require("cheerio"), 1);
|
|
8441
9063
|
var DATA_URI_WARN_BYTES = 100 * 1024;
|
|
8442
9064
|
var TOTAL_DATA_URI_WARN_BYTES = 500 * 1024;
|
|
8443
9065
|
var HIGH_IMAGE_COUNT = 10;
|
|
@@ -8601,12 +9223,12 @@ function analyzeImages(html) {
|
|
|
8601
9223
|
if (html.length > MAX_HTML_SIZE) {
|
|
8602
9224
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
8603
9225
|
}
|
|
8604
|
-
const $ =
|
|
9226
|
+
const $ = cheerio8.load(html);
|
|
8605
9227
|
return analyzeImagesFromDom($);
|
|
8606
9228
|
}
|
|
8607
9229
|
|
|
8608
9230
|
// src/inbox-preview.ts
|
|
8609
|
-
var
|
|
9231
|
+
var cheerio9 = __toESM(require("cheerio"), 1);
|
|
8610
9232
|
var MAX_SUBJECT_LENGTH = 60;
|
|
8611
9233
|
var MAX_PREHEADER_LENGTH = 100;
|
|
8612
9234
|
var MIN_PREHEADER_LENGTH = 30;
|
|
@@ -8626,7 +9248,7 @@ function extractInboxPreview(html) {
|
|
|
8626
9248
|
if (html.length > MAX_HTML_SIZE) {
|
|
8627
9249
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
8628
9250
|
}
|
|
8629
|
-
const $ =
|
|
9251
|
+
const $ = cheerio9.load(html);
|
|
8630
9252
|
return extractInboxPreviewFromDom($);
|
|
8631
9253
|
}
|
|
8632
9254
|
function extractInboxPreviewFromDom($) {
|
|
@@ -8794,7 +9416,7 @@ function getFirstVisibleText($, body) {
|
|
|
8794
9416
|
}
|
|
8795
9417
|
|
|
8796
9418
|
// src/size-checker.ts
|
|
8797
|
-
var
|
|
9419
|
+
var cheerio10 = __toESM(require("cheerio"), 1);
|
|
8798
9420
|
function humanizeBytes(bytes) {
|
|
8799
9421
|
if (bytes < 1024) return `${bytes} B`;
|
|
8800
9422
|
const kb = bytes / 1024;
|
|
@@ -8832,12 +9454,12 @@ function checkSize(html) {
|
|
|
8832
9454
|
if (html.length > MAX_HTML_SIZE) {
|
|
8833
9455
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
8834
9456
|
}
|
|
8835
|
-
const $ =
|
|
9457
|
+
const $ = cheerio10.load(html);
|
|
8836
9458
|
return checkSizeFromDom($, html);
|
|
8837
9459
|
}
|
|
8838
9460
|
|
|
8839
9461
|
// src/template-checker.ts
|
|
8840
|
-
var
|
|
9462
|
+
var cheerio11 = __toESM(require("cheerio"), 1);
|
|
8841
9463
|
function checkTemplateVariablesFromDom($) {
|
|
8842
9464
|
const issues = [];
|
|
8843
9465
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -8900,12 +9522,12 @@ function checkTemplateVariables(html) {
|
|
|
8900
9522
|
if (html.length > MAX_HTML_SIZE) {
|
|
8901
9523
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
8902
9524
|
}
|
|
8903
|
-
const $ =
|
|
9525
|
+
const $ = cheerio11.load(html);
|
|
8904
9526
|
return checkTemplateVariablesFromDom($);
|
|
8905
9527
|
}
|
|
8906
9528
|
|
|
8907
9529
|
// src/audit.ts
|
|
8908
|
-
var
|
|
9530
|
+
var cheerio12 = __toESM(require("cheerio"), 1);
|
|
8909
9531
|
function auditEmail(html, options) {
|
|
8910
9532
|
var _a;
|
|
8911
9533
|
if (!html || !html.trim()) {
|
|
@@ -8925,7 +9547,7 @@ function auditEmail(html, options) {
|
|
|
8925
9547
|
}
|
|
8926
9548
|
const framework = options == null ? void 0 : options.framework;
|
|
8927
9549
|
const skip = new Set((_a = options == null ? void 0 : options.skip) != null ? _a : []);
|
|
8928
|
-
const $ =
|
|
9550
|
+
const $ = cheerio12.load(html);
|
|
8929
9551
|
const warnings = skip.has("compatibility") ? [] : analyzeEmailFromDom($, framework);
|
|
8930
9552
|
const scores = skip.has("compatibility") ? {} : generateCompatibilityScore(warnings);
|
|
8931
9553
|
const spam = skip.has("spam") ? EMPTY_SPAM : analyzeSpamFromDom($, options == null ? void 0 : options.spam);
|
|
@@ -8938,8 +9560,53 @@ function auditEmail(html, options) {
|
|
|
8938
9560
|
return { compatibility: { warnings, scores }, spam, links, accessibility, images, inboxPreview, size, templateVariables };
|
|
8939
9561
|
}
|
|
8940
9562
|
|
|
9563
|
+
// src/plain-text.ts
|
|
9564
|
+
var cheerio13 = __toESM(require("cheerio"), 1);
|
|
9565
|
+
function toPlainText(html) {
|
|
9566
|
+
const $ = cheerio13.load(html);
|
|
9567
|
+
$("style, script").remove();
|
|
9568
|
+
$("[data-skip-in-text='true']").remove();
|
|
9569
|
+
$("hr").replaceWith("\n---\n");
|
|
9570
|
+
$("br").replaceWith("\n");
|
|
9571
|
+
$("img").each((_, el) => {
|
|
9572
|
+
const alt = $(el).attr("alt");
|
|
9573
|
+
$(el).replaceWith(alt ? alt : "");
|
|
9574
|
+
});
|
|
9575
|
+
$("a").each((_, el) => {
|
|
9576
|
+
const $el = $(el);
|
|
9577
|
+
const href = $el.attr("href") || "";
|
|
9578
|
+
const text2 = $el.text().trim();
|
|
9579
|
+
if (!href || href === text2) {
|
|
9580
|
+
$el.replaceWith(text2 || href);
|
|
9581
|
+
} else if (!text2) {
|
|
9582
|
+
$el.replaceWith(href);
|
|
9583
|
+
} else {
|
|
9584
|
+
$el.replaceWith(`${text2} (${href})`);
|
|
9585
|
+
}
|
|
9586
|
+
});
|
|
9587
|
+
$("li").each((_, el) => {
|
|
9588
|
+
const $el = $(el);
|
|
9589
|
+
const text2 = $el.text().trim();
|
|
9590
|
+
$el.replaceWith(`
|
|
9591
|
+
- ${text2}
|
|
9592
|
+
`);
|
|
9593
|
+
});
|
|
9594
|
+
const blockTags = "p, div, h1, h2, h3, h4, h5, h6, tr, blockquote, ul, ol";
|
|
9595
|
+
$(blockTags).each((_, el) => {
|
|
9596
|
+
const $el = $(el);
|
|
9597
|
+
$el.prepend("\n\n");
|
|
9598
|
+
$el.append("\n\n");
|
|
9599
|
+
});
|
|
9600
|
+
let text = $("body").text();
|
|
9601
|
+
if (!text.trim()) {
|
|
9602
|
+
text = $.root().text();
|
|
9603
|
+
}
|
|
9604
|
+
text = text.replace(/\n{3,}/g, "\n\n");
|
|
9605
|
+
return text.trim();
|
|
9606
|
+
}
|
|
9607
|
+
|
|
8941
9608
|
// src/session.ts
|
|
8942
|
-
var
|
|
9609
|
+
var cheerio14 = __toESM(require("cheerio"), 1);
|
|
8943
9610
|
function createSession(html, options) {
|
|
8944
9611
|
if (!html || !html.trim()) {
|
|
8945
9612
|
const fw = options == null ? void 0 : options.framework;
|
|
@@ -8973,7 +9640,7 @@ function createSession(html, options) {
|
|
|
8973
9640
|
if (html.length > MAX_HTML_SIZE) {
|
|
8974
9641
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
8975
9642
|
}
|
|
8976
|
-
const $ =
|
|
9643
|
+
const $ = cheerio14.load(html);
|
|
8977
9644
|
const framework = options == null ? void 0 : options.framework;
|
|
8978
9645
|
return {
|
|
8979
9646
|
html,
|
|
@@ -9066,9 +9733,11 @@ var CompileError = class extends Error {
|
|
|
9066
9733
|
contrastRatio,
|
|
9067
9734
|
createSession,
|
|
9068
9735
|
diffResults,
|
|
9736
|
+
downlevelCSS,
|
|
9069
9737
|
errorWarnings,
|
|
9070
9738
|
estimateAiFixTokens,
|
|
9071
9739
|
extractInboxPreview,
|
|
9740
|
+
formatRgb,
|
|
9072
9741
|
generateAiFix,
|
|
9073
9742
|
generateCompatibilityScore,
|
|
9074
9743
|
generateFixPrompt,
|
|
@@ -9080,6 +9749,7 @@ var CompileError = class extends Error {
|
|
|
9080
9749
|
relativeLuminance,
|
|
9081
9750
|
simulateDarkMode,
|
|
9082
9751
|
structuralWarnings,
|
|
9752
|
+
toPlainText,
|
|
9083
9753
|
transformForAllClients,
|
|
9084
9754
|
transformForClient,
|
|
9085
9755
|
validateLinks,
|