@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.js
CHANGED
|
@@ -120,8 +120,8 @@ function getClient(id) {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
// src/transform.ts
|
|
123
|
-
import * as
|
|
124
|
-
import * as
|
|
123
|
+
import * as cheerio2 from "cheerio";
|
|
124
|
+
import * as csstree2 from "css-tree";
|
|
125
125
|
|
|
126
126
|
// src/rules/css-support.ts
|
|
127
127
|
var CSS_SUPPORT = {
|
|
@@ -4646,6 +4646,48 @@ h1 {
|
|
|
4646
4646
|
Shortened content...
|
|
4647
4647
|
<a href="https://example.com/full">Read more</a>
|
|
4648
4648
|
</div>`
|
|
4649
|
+
},
|
|
4650
|
+
// ── padding (Outlook MSO font-width trick) ─────────────────────────────
|
|
4651
|
+
"padding::outlook": {
|
|
4652
|
+
language: "html",
|
|
4653
|
+
description: "Use MSO font-width trick for reliable button padding in Outlook",
|
|
4654
|
+
before: `<a href="https://example.com"
|
|
4655
|
+
style="background-color: #6d28d9; color: #fff;
|
|
4656
|
+
padding: 12px 32px; display: inline-block;
|
|
4657
|
+
text-decoration: none;">
|
|
4658
|
+
Click Here
|
|
4659
|
+
</a>`,
|
|
4660
|
+
after: `<!--[if mso]>
|
|
4661
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0"
|
|
4662
|
+
style="border-collapse: collapse;">
|
|
4663
|
+
<tr>
|
|
4664
|
+
<td style="background-color: #6d28d9; padding: 0;
|
|
4665
|
+
mso-padding-alt: 12px 32px;">
|
|
4666
|
+
<!--[if mso]>
|
|
4667
|
+
<i style="letter-spacing: 32px; mso-font-width: -100%;
|
|
4668
|
+
mso-text-raise: 18pt;" hidden> </i>
|
|
4669
|
+
<![endif]-->
|
|
4670
|
+
<a href="https://example.com"
|
|
4671
|
+
style="background-color: #6d28d9; color: #fff;
|
|
4672
|
+
mso-text-raise: 9pt; text-decoration: none;
|
|
4673
|
+
font-family: Arial, sans-serif; font-size: 14px;
|
|
4674
|
+
font-weight: bold;">Click Here</a>
|
|
4675
|
+
<!--[if mso]>
|
|
4676
|
+
<i style="letter-spacing: 32px; mso-font-width: -100%;"
|
|
4677
|
+
hidden> </i>
|
|
4678
|
+
<![endif]-->
|
|
4679
|
+
</td>
|
|
4680
|
+
</tr>
|
|
4681
|
+
</table>
|
|
4682
|
+
<![endif]-->
|
|
4683
|
+
<!--[if !mso]><!-->
|
|
4684
|
+
<a href="https://example.com"
|
|
4685
|
+
style="background-color: #6d28d9; color: #fff;
|
|
4686
|
+
padding: 12px 32px; display: inline-block;
|
|
4687
|
+
text-decoration: none;">
|
|
4688
|
+
Click Here
|
|
4689
|
+
</a>
|
|
4690
|
+
<!--<![endif]-->`
|
|
4649
4691
|
}
|
|
4650
4692
|
};
|
|
4651
4693
|
|
|
@@ -5188,6 +5230,35 @@ var JSX_FIX_DATABASE = {
|
|
|
5188
5230
|
Content \u2014 padding on inner element
|
|
5189
5231
|
</div>
|
|
5190
5232
|
</div>`
|
|
5233
|
+
},
|
|
5234
|
+
// ── padding (Outlook MSO font-width trick, JSX) ───────────────────────
|
|
5235
|
+
"padding::outlook::jsx": {
|
|
5236
|
+
language: "jsx",
|
|
5237
|
+
description: "Use React Email Button component which handles MSO padding automatically",
|
|
5238
|
+
before: `<a
|
|
5239
|
+
href="https://example.com"
|
|
5240
|
+
style={{ backgroundColor: "#6d28d9", color: "#fff",
|
|
5241
|
+
padding: "12px 32px", display: "inline-block",
|
|
5242
|
+
textDecoration: "none" }}>
|
|
5243
|
+
Click Here
|
|
5244
|
+
</a>`,
|
|
5245
|
+
after: `import { Button } from "@react-email/components";
|
|
5246
|
+
|
|
5247
|
+
{/* The Button component automatically handles MSO padding
|
|
5248
|
+
via the mso-font-width +   technique */}
|
|
5249
|
+
<Button
|
|
5250
|
+
href="https://example.com"
|
|
5251
|
+
style={{
|
|
5252
|
+
backgroundColor: "#6d28d9",
|
|
5253
|
+
color: "#fff",
|
|
5254
|
+
padding: "12px 32px",
|
|
5255
|
+
borderRadius: "6px",
|
|
5256
|
+
textDecoration: "none",
|
|
5257
|
+
fontWeight: "bold",
|
|
5258
|
+
}}
|
|
5259
|
+
>
|
|
5260
|
+
Click Here
|
|
5261
|
+
</Button>`
|
|
5191
5262
|
}
|
|
5192
5263
|
};
|
|
5193
5264
|
|
|
@@ -5485,7 +5556,9 @@ var HTML_SUGGESTION_DATABASE = {
|
|
|
5485
5556
|
"transition": "CSS transitions are not supported in email.",
|
|
5486
5557
|
"box-sizing": "Account for padding in your width calculations (use padding on a nested element).",
|
|
5487
5558
|
"object-fit": "Use width/height attributes on <img> directly.",
|
|
5488
|
-
"display": "Use tables for layout in email clients."
|
|
5559
|
+
"display": "Use tables for layout in email clients.",
|
|
5560
|
+
// ── padding ─────────────────────────────────────────────────────────────
|
|
5561
|
+
"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."
|
|
5489
5562
|
};
|
|
5490
5563
|
|
|
5491
5564
|
// src/fix-snippets/jsx-suggestions.ts
|
|
@@ -5530,7 +5603,9 @@ var JSX_SUGGESTION_DATABASE = {
|
|
|
5530
5603
|
// ── word-break ────────────────────────────────────────────────────────
|
|
5531
5604
|
"word-break::jsx": "Wrap long text in a <table><tr><td> element. Outlook ignores wordBreak but respects table cell widths.",
|
|
5532
5605
|
// ── overflow-wrap ─────────────────────────────────────────────────────
|
|
5533
|
-
"overflow-wrap::jsx": "Wrap text in a <table><tr><td> element. Outlook ignores overflowWrap but respects table cell widths."
|
|
5606
|
+
"overflow-wrap::jsx": "Wrap text in a <table><tr><td> element. Outlook ignores overflowWrap but respects table cell widths.",
|
|
5607
|
+
// ── padding ─────────────────────────────────────────────────────────────
|
|
5608
|
+
"padding::outlook::jsx": "Use React Email's <Button> component which automatically generates MSO-compatible padding using the mso-font-width trick."
|
|
5534
5609
|
};
|
|
5535
5610
|
|
|
5536
5611
|
// src/fix-snippets/mjml-suggestions.ts
|
|
@@ -5752,1103 +5827,1647 @@ function serializeStyle(map) {
|
|
|
5752
5827
|
return parts.join("; ");
|
|
5753
5828
|
}
|
|
5754
5829
|
|
|
5755
|
-
// src/
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
"click here",
|
|
5759
|
-
"here",
|
|
5760
|
-
"read more",
|
|
5761
|
-
"learn more",
|
|
5762
|
-
"more",
|
|
5763
|
-
"link",
|
|
5764
|
-
"this link",
|
|
5765
|
-
"click",
|
|
5766
|
-
"tap here",
|
|
5767
|
-
"this"
|
|
5768
|
-
]);
|
|
5769
|
-
var GMAIL_CLIP_THRESHOLD = 102 * 1024;
|
|
5770
|
-
var GMAIL_CLIP_WARNING_THRESHOLD = 90 * 1024;
|
|
5771
|
-
var CLIENT_DISPLAY_LIMITS = [
|
|
5772
|
-
{ client: "Gmail (Web)", subjectLimit: 70, preheaderLimit: 90 },
|
|
5773
|
-
{ client: "Gmail (Mobile)", subjectLimit: 40, preheaderLimit: 45 },
|
|
5774
|
-
{ client: "Outlook (Web)", subjectLimit: 60, preheaderLimit: 90 },
|
|
5775
|
-
{ client: "Outlook (Desktop)", subjectLimit: 55, preheaderLimit: 35 },
|
|
5776
|
-
{ client: "Apple Mail (macOS)", subjectLimit: 78, preheaderLimit: 140 },
|
|
5777
|
-
{ client: "Apple Mail (iOS)", subjectLimit: 41, preheaderLimit: 90 },
|
|
5778
|
-
{ client: "Yahoo Mail", subjectLimit: 46, preheaderLimit: 100 },
|
|
5779
|
-
{ client: "Samsung Email", subjectLimit: 40, preheaderLimit: 70 }
|
|
5780
|
-
];
|
|
5781
|
-
var TEMPLATE_VARIABLE_PATTERNS = [
|
|
5782
|
-
[/\{\{[\s\S]*?\}\}/g, "Handlebars/Mustache"],
|
|
5783
|
-
// {{var}}
|
|
5784
|
-
[/\$\{[^}]+\}/g, "ES template literal"],
|
|
5785
|
-
// ${var}
|
|
5786
|
-
[/<%=?\s*[^%]+%>/g, "ERB/EJS"],
|
|
5787
|
-
// <% %> / <%= %>
|
|
5788
|
-
[/\*\|[A-Z_][A-Z0-9_]*\|\*/g, "Mailchimp merge tag"],
|
|
5789
|
-
// *|TAG|*
|
|
5790
|
-
[/%%[A-Za-z_][A-Za-z0-9_]*%%/g, "Salesforce AMPscript"],
|
|
5791
|
-
// %%tag%%
|
|
5792
|
-
[/\{[A-Za-z_][A-Za-z0-9_.]{2,}\}/g, "Single-brace merge field"]
|
|
5793
|
-
// {merge_field} (3+ char names)
|
|
5794
|
-
];
|
|
5795
|
-
var EMPTY_SPAM = { score: 100, level: "low", issues: [] };
|
|
5796
|
-
var EMPTY_LINKS = {
|
|
5797
|
-
totalLinks: 0,
|
|
5798
|
-
issues: [],
|
|
5799
|
-
breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
|
|
5800
|
-
};
|
|
5801
|
-
var EMPTY_ACCESSIBILITY = { score: 100, issues: [] };
|
|
5802
|
-
var EMPTY_IMAGES = { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
|
|
5803
|
-
var EMPTY_INBOX_PREVIEW = { subject: null, preheader: null, subjectLength: 0, preheaderLength: 0, truncation: [], issues: [] };
|
|
5804
|
-
var EMPTY_SIZE = { htmlBytes: 0, humanSize: "0 B", clipped: false, issues: [] };
|
|
5805
|
-
var EMPTY_TEMPLATE = { unresolvedCount: 0, issues: [] };
|
|
5806
|
-
var EMPTY_DELIVERABILITY = { domain: "", checks: [], score: 0, issues: [] };
|
|
5830
|
+
// src/downlevel.ts
|
|
5831
|
+
import * as cheerio from "cheerio";
|
|
5832
|
+
import * as csstree from "css-tree";
|
|
5807
5833
|
|
|
5808
|
-
// src/
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
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
|
-
}, "background-image", clientId, framework));
|
|
5965
|
-
}
|
|
5966
|
-
return warnings;
|
|
5967
|
-
}
|
|
5968
|
-
function appleMailAdditionalChecks($, clientId) {
|
|
5969
|
-
const warnings = [];
|
|
5970
|
-
const imgsWithTransparentBg = $("img").filter((_, el) => {
|
|
5971
|
-
const src = $(el).attr("src") || "";
|
|
5972
|
-
return src.endsWith(".png") || src.endsWith(".svg");
|
|
5973
|
-
});
|
|
5974
|
-
if (imgsWithTransparentBg.length > 0) {
|
|
5975
|
-
warnings.push({
|
|
5976
|
-
severity: "info",
|
|
5977
|
-
client: clientId,
|
|
5978
|
-
property: "dark-mode",
|
|
5979
|
-
message: "PNG/SVG images with transparent backgrounds may become invisible in Apple Mail dark mode.",
|
|
5980
|
-
suggestion: "Add a white background or padding around images, or use dark-mode-friendly image variants."
|
|
5981
|
-
});
|
|
5834
|
+
// src/color-utils.ts
|
|
5835
|
+
var NAMED_COLORS = {
|
|
5836
|
+
aliceblue: [240, 248, 255],
|
|
5837
|
+
antiquewhite: [250, 235, 215],
|
|
5838
|
+
aqua: [0, 255, 255],
|
|
5839
|
+
aquamarine: [127, 255, 212],
|
|
5840
|
+
azure: [240, 255, 255],
|
|
5841
|
+
beige: [245, 245, 220],
|
|
5842
|
+
bisque: [255, 228, 196],
|
|
5843
|
+
black: [0, 0, 0],
|
|
5844
|
+
blanchedalmond: [255, 235, 205],
|
|
5845
|
+
blue: [0, 0, 255],
|
|
5846
|
+
blueviolet: [138, 43, 226],
|
|
5847
|
+
brown: [165, 42, 42],
|
|
5848
|
+
burlywood: [222, 184, 135],
|
|
5849
|
+
cadetblue: [95, 158, 160],
|
|
5850
|
+
chartreuse: [127, 255, 0],
|
|
5851
|
+
chocolate: [210, 105, 30],
|
|
5852
|
+
coral: [255, 127, 80],
|
|
5853
|
+
cornflowerblue: [100, 149, 237],
|
|
5854
|
+
cornsilk: [255, 248, 220],
|
|
5855
|
+
crimson: [220, 20, 60],
|
|
5856
|
+
cyan: [0, 255, 255],
|
|
5857
|
+
darkblue: [0, 0, 139],
|
|
5858
|
+
darkcyan: [0, 139, 139],
|
|
5859
|
+
darkgoldenrod: [184, 134, 11],
|
|
5860
|
+
darkgray: [169, 169, 169],
|
|
5861
|
+
darkgreen: [0, 100, 0],
|
|
5862
|
+
darkgrey: [169, 169, 169],
|
|
5863
|
+
darkkhaki: [189, 183, 107],
|
|
5864
|
+
darkmagenta: [139, 0, 139],
|
|
5865
|
+
darkolivegreen: [85, 107, 47],
|
|
5866
|
+
darkorange: [255, 140, 0],
|
|
5867
|
+
darkorchid: [153, 50, 204],
|
|
5868
|
+
darkred: [139, 0, 0],
|
|
5869
|
+
darksalmon: [233, 150, 122],
|
|
5870
|
+
darkseagreen: [143, 188, 143],
|
|
5871
|
+
darkslateblue: [72, 61, 139],
|
|
5872
|
+
darkslategray: [47, 79, 79],
|
|
5873
|
+
darkslategrey: [47, 79, 79],
|
|
5874
|
+
darkturquoise: [0, 206, 209],
|
|
5875
|
+
darkviolet: [148, 0, 211],
|
|
5876
|
+
deeppink: [255, 20, 147],
|
|
5877
|
+
deepskyblue: [0, 191, 255],
|
|
5878
|
+
dimgray: [105, 105, 105],
|
|
5879
|
+
dimgrey: [105, 105, 105],
|
|
5880
|
+
dodgerblue: [30, 144, 255],
|
|
5881
|
+
firebrick: [178, 34, 34],
|
|
5882
|
+
floralwhite: [255, 250, 240],
|
|
5883
|
+
forestgreen: [34, 139, 34],
|
|
5884
|
+
fuchsia: [255, 0, 255],
|
|
5885
|
+
gainsboro: [220, 220, 220],
|
|
5886
|
+
ghostwhite: [248, 248, 255],
|
|
5887
|
+
gold: [255, 215, 0],
|
|
5888
|
+
goldenrod: [218, 165, 32],
|
|
5889
|
+
gray: [128, 128, 128],
|
|
5890
|
+
green: [0, 128, 0],
|
|
5891
|
+
greenyellow: [173, 255, 47],
|
|
5892
|
+
grey: [128, 128, 128],
|
|
5893
|
+
honeydew: [240, 255, 240],
|
|
5894
|
+
hotpink: [255, 105, 180],
|
|
5895
|
+
indianred: [205, 92, 92],
|
|
5896
|
+
indigo: [75, 0, 130],
|
|
5897
|
+
ivory: [255, 255, 240],
|
|
5898
|
+
khaki: [240, 230, 140],
|
|
5899
|
+
lavender: [230, 230, 250],
|
|
5900
|
+
lavenderblush: [255, 240, 245],
|
|
5901
|
+
lawngreen: [124, 252, 0],
|
|
5902
|
+
lemonchiffon: [255, 250, 205],
|
|
5903
|
+
lightblue: [173, 216, 230],
|
|
5904
|
+
lightcoral: [240, 128, 128],
|
|
5905
|
+
lightcyan: [224, 255, 255],
|
|
5906
|
+
lightgoldenrodyellow: [250, 250, 210],
|
|
5907
|
+
lightgray: [211, 211, 211],
|
|
5908
|
+
lightgreen: [144, 238, 144],
|
|
5909
|
+
lightgrey: [211, 211, 211],
|
|
5910
|
+
lightpink: [255, 182, 193],
|
|
5911
|
+
lightsalmon: [255, 160, 122],
|
|
5912
|
+
lightseagreen: [32, 178, 170],
|
|
5913
|
+
lightskyblue: [135, 206, 250],
|
|
5914
|
+
lightslategray: [119, 136, 153],
|
|
5915
|
+
lightslategrey: [119, 136, 153],
|
|
5916
|
+
lightsteelblue: [176, 196, 222],
|
|
5917
|
+
lightyellow: [255, 255, 224],
|
|
5918
|
+
lime: [0, 255, 0],
|
|
5919
|
+
limegreen: [50, 205, 50],
|
|
5920
|
+
linen: [250, 240, 230],
|
|
5921
|
+
magenta: [255, 0, 255],
|
|
5922
|
+
maroon: [128, 0, 0],
|
|
5923
|
+
mediumaquamarine: [102, 205, 170],
|
|
5924
|
+
mediumblue: [0, 0, 205],
|
|
5925
|
+
mediumorchid: [186, 85, 211],
|
|
5926
|
+
mediumpurple: [147, 111, 219],
|
|
5927
|
+
mediumseagreen: [60, 179, 113],
|
|
5928
|
+
mediumslateblue: [123, 104, 238],
|
|
5929
|
+
mediumspringgreen: [0, 250, 154],
|
|
5930
|
+
mediumturquoise: [72, 209, 204],
|
|
5931
|
+
mediumvioletred: [199, 21, 133],
|
|
5932
|
+
midnightblue: [25, 25, 112],
|
|
5933
|
+
mintcream: [245, 255, 250],
|
|
5934
|
+
mistyrose: [255, 228, 225],
|
|
5935
|
+
moccasin: [255, 228, 181],
|
|
5936
|
+
navajowhite: [255, 222, 173],
|
|
5937
|
+
navy: [0, 0, 128],
|
|
5938
|
+
oldlace: [253, 245, 230],
|
|
5939
|
+
olive: [128, 128, 0],
|
|
5940
|
+
olivedrab: [107, 142, 35],
|
|
5941
|
+
orange: [255, 165, 0],
|
|
5942
|
+
orangered: [255, 69, 0],
|
|
5943
|
+
orchid: [218, 112, 214],
|
|
5944
|
+
palegoldenrod: [238, 232, 170],
|
|
5945
|
+
palegreen: [152, 251, 152],
|
|
5946
|
+
paleturquoise: [175, 238, 238],
|
|
5947
|
+
palevioletred: [219, 112, 147],
|
|
5948
|
+
papayawhip: [255, 239, 213],
|
|
5949
|
+
peachpuff: [255, 218, 185],
|
|
5950
|
+
peru: [205, 133, 63],
|
|
5951
|
+
pink: [255, 192, 203],
|
|
5952
|
+
plum: [221, 160, 221],
|
|
5953
|
+
powderblue: [176, 224, 230],
|
|
5954
|
+
purple: [128, 0, 128],
|
|
5955
|
+
rebeccapurple: [102, 51, 153],
|
|
5956
|
+
red: [255, 0, 0],
|
|
5957
|
+
rosybrown: [188, 143, 143],
|
|
5958
|
+
royalblue: [65, 105, 225],
|
|
5959
|
+
saddlebrown: [139, 69, 19],
|
|
5960
|
+
salmon: [250, 128, 114],
|
|
5961
|
+
sandybrown: [244, 164, 96],
|
|
5962
|
+
seagreen: [46, 139, 87],
|
|
5963
|
+
seashell: [255, 245, 238],
|
|
5964
|
+
sienna: [160, 82, 45],
|
|
5965
|
+
silver: [192, 192, 192],
|
|
5966
|
+
skyblue: [135, 206, 235],
|
|
5967
|
+
slateblue: [106, 90, 205],
|
|
5968
|
+
slategray: [112, 128, 144],
|
|
5969
|
+
slategrey: [112, 128, 144],
|
|
5970
|
+
snow: [255, 250, 250],
|
|
5971
|
+
springgreen: [0, 255, 127],
|
|
5972
|
+
steelblue: [70, 130, 180],
|
|
5973
|
+
tan: [210, 180, 140],
|
|
5974
|
+
teal: [0, 128, 128],
|
|
5975
|
+
thistle: [216, 191, 216],
|
|
5976
|
+
tomato: [255, 99, 71],
|
|
5977
|
+
turquoise: [64, 224, 208],
|
|
5978
|
+
violet: [238, 130, 238],
|
|
5979
|
+
wheat: [245, 222, 179],
|
|
5980
|
+
white: [255, 255, 255],
|
|
5981
|
+
whitesmoke: [245, 245, 245],
|
|
5982
|
+
yellow: [255, 255, 0],
|
|
5983
|
+
yellowgreen: [154, 205, 50]
|
|
5984
|
+
};
|
|
5985
|
+
function parseAlpha(raw) {
|
|
5986
|
+
if (raw === void 0) return 1;
|
|
5987
|
+
const s = raw.trim();
|
|
5988
|
+
if (s.endsWith("%")) {
|
|
5989
|
+
return Math.min(1, Math.max(0, parseFloat(s) / 100));
|
|
5982
5990
|
}
|
|
5983
|
-
return
|
|
5991
|
+
return Math.min(1, Math.max(0, parseFloat(s)));
|
|
5984
5992
|
}
|
|
5985
|
-
function
|
|
5986
|
-
|
|
5987
|
-
warnings.push({
|
|
5988
|
-
severity: "info",
|
|
5989
|
-
client: clientId,
|
|
5990
|
-
property: "class",
|
|
5991
|
-
message: "Yahoo Mail rewrites CSS class names with a prefix. Class-based selectors in <style> blocks will still work but the names change."
|
|
5992
|
-
});
|
|
5993
|
-
if ($("[style*='background']").filter(
|
|
5994
|
-
(_, el) => ($(el).attr("style") || "").includes("url(")
|
|
5995
|
-
).length > 0) {
|
|
5996
|
-
warnings.push(makeWarning({
|
|
5997
|
-
severity: "warning",
|
|
5998
|
-
client: clientId,
|
|
5999
|
-
property: "background-image",
|
|
6000
|
-
message: "Yahoo Mail has inconsistent support for CSS background images."
|
|
6001
|
-
}, "background-image", clientId, framework));
|
|
6002
|
-
}
|
|
6003
|
-
return warnings;
|
|
5993
|
+
function clamp255(n) {
|
|
5994
|
+
return Math.round(Math.min(255, Math.max(0, n)));
|
|
6004
5995
|
}
|
|
6005
|
-
function
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
5996
|
+
function hslToRgb(h, s, l) {
|
|
5997
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
5998
|
+
const hp = h / 60;
|
|
5999
|
+
const x = c * (1 - Math.abs(hp % 2 - 1));
|
|
6000
|
+
const m = l - c / 2;
|
|
6001
|
+
let r1 = 0, g1 = 0, b1 = 0;
|
|
6002
|
+
if (hp >= 0 && hp < 1) {
|
|
6003
|
+
r1 = c;
|
|
6004
|
+
g1 = x;
|
|
6005
|
+
b1 = 0;
|
|
6006
|
+
} else if (hp >= 1 && hp < 2) {
|
|
6007
|
+
r1 = x;
|
|
6008
|
+
g1 = c;
|
|
6009
|
+
b1 = 0;
|
|
6010
|
+
} else if (hp >= 2 && hp < 3) {
|
|
6011
|
+
r1 = 0;
|
|
6012
|
+
g1 = c;
|
|
6013
|
+
b1 = x;
|
|
6014
|
+
} else if (hp >= 3 && hp < 4) {
|
|
6015
|
+
r1 = 0;
|
|
6016
|
+
g1 = x;
|
|
6017
|
+
b1 = c;
|
|
6018
|
+
} else if (hp >= 4 && hp < 5) {
|
|
6019
|
+
r1 = x;
|
|
6020
|
+
g1 = 0;
|
|
6021
|
+
b1 = c;
|
|
6022
|
+
} else {
|
|
6023
|
+
r1 = c;
|
|
6024
|
+
g1 = 0;
|
|
6025
|
+
b1 = x;
|
|
6013
6026
|
}
|
|
6014
|
-
return [
|
|
6027
|
+
return [
|
|
6028
|
+
clamp255((r1 + m) * 255),
|
|
6029
|
+
clamp255((g1 + m) * 255),
|
|
6030
|
+
clamp255((b1 + m) * 255)
|
|
6031
|
+
];
|
|
6015
6032
|
}
|
|
6016
|
-
function
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
severity: "info",
|
|
6021
|
-
client: clientId,
|
|
6022
|
-
property: "dark-mode",
|
|
6023
|
-
message: "HEY Mail supports @media (prefers-color-scheme: dark). Consider adding dark mode styles.",
|
|
6024
|
-
suggestion: "Add a @media (prefers-color-scheme: dark) block to optimize for HEY's audience."
|
|
6025
|
-
});
|
|
6033
|
+
function hwbToRgb(h, w, b) {
|
|
6034
|
+
if (w + b >= 1) {
|
|
6035
|
+
const gray = clamp255(w / (w + b) * 255);
|
|
6036
|
+
return [gray, gray, gray];
|
|
6026
6037
|
}
|
|
6027
|
-
|
|
6038
|
+
const [r0, g0, b0] = hslToRgb(h, 1, 0.5);
|
|
6039
|
+
return [
|
|
6040
|
+
clamp255(r0 / 255 * (1 - w - b) * 255 + w * 255),
|
|
6041
|
+
clamp255(g0 / 255 * (1 - w - b) * 255 + w * 255),
|
|
6042
|
+
clamp255(b0 / 255 * (1 - w - b) * 255 + w * 255)
|
|
6043
|
+
];
|
|
6028
6044
|
}
|
|
6029
|
-
function
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
warnings.push({
|
|
6033
|
-
severity: "info",
|
|
6034
|
-
client: clientId,
|
|
6035
|
-
property: "animation",
|
|
6036
|
-
message: "Superhuman may honor OS-level 'reduce motion' preferences, disabling animations.",
|
|
6037
|
-
suggestion: "Use @media (prefers-reduced-motion: reduce) to provide static fallbacks."
|
|
6038
|
-
});
|
|
6039
|
-
}
|
|
6040
|
-
warnings.push({
|
|
6041
|
-
severity: "info",
|
|
6042
|
-
client: clientId,
|
|
6043
|
-
property: "<style>",
|
|
6044
|
-
message: "Superhuman uses Chromium rendering with excellent CSS support. Flexbox, Grid, CSS variables, and modern properties all work."
|
|
6045
|
-
});
|
|
6046
|
-
return warnings;
|
|
6045
|
+
function linearToGamma(c) {
|
|
6046
|
+
if (c <= 31308e-7) return 12.92 * c;
|
|
6047
|
+
return 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
6047
6048
|
}
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
inlineAndStripStyles: true,
|
|
6073
|
-
stripExternalStylesheets: true,
|
|
6074
|
-
stripForms: true,
|
|
6075
|
-
stripSvg: true,
|
|
6076
|
-
additionalChecks: gmailAdditionalChecks
|
|
6077
|
-
},
|
|
6078
|
-
"gmail-ios": {
|
|
6079
|
-
id: "gmail-ios",
|
|
6080
|
-
strippedProperties: GMAIL_STRIPPED_PROPERTIES,
|
|
6081
|
-
stripMode: "strip",
|
|
6082
|
-
valueStrips: [
|
|
6083
|
-
{ prop: "display", pattern: /grid/ },
|
|
6084
|
-
{ prop: "background", pattern: /linear-gradient|radial-gradient/ }
|
|
6085
|
-
],
|
|
6086
|
-
inlineAndStripStyles: true,
|
|
6087
|
-
stripExternalStylesheets: true,
|
|
6088
|
-
stripForms: true,
|
|
6089
|
-
stripSvg: true,
|
|
6090
|
-
additionalChecks: gmailAdditionalChecks
|
|
6091
|
-
},
|
|
6092
|
-
"outlook-windows": {
|
|
6093
|
-
id: "outlook-windows",
|
|
6094
|
-
strippedProperties: /* @__PURE__ */ new Set(["position", "transform", "animation", "transition"]),
|
|
6095
|
-
stripMode: "strip",
|
|
6096
|
-
inlineAndStripStyles: false,
|
|
6097
|
-
stripExternalStylesheets: false,
|
|
6098
|
-
stripForms: false,
|
|
6099
|
-
stripSvg: false
|
|
6100
|
-
},
|
|
6101
|
-
"outlook-windows-legacy": {
|
|
6102
|
-
id: "outlook-windows-legacy",
|
|
6103
|
-
strippedProperties: OUTLOOK_WORD_UNSUPPORTED,
|
|
6104
|
-
stripMode: "strip",
|
|
6105
|
-
valueStrips: [
|
|
6106
|
-
{ prop: "background", pattern: /linear-gradient|radial-gradient/ },
|
|
6107
|
-
{ prop: "background-image", pattern: /linear-gradient|radial-gradient/ }
|
|
6108
|
-
],
|
|
6109
|
-
inlineAndStripStyles: false,
|
|
6110
|
-
stripExternalStylesheets: false,
|
|
6111
|
-
stripForms: false,
|
|
6112
|
-
stripSvg: false,
|
|
6113
|
-
additionalChecks: outlookWindowsAdditionalChecks
|
|
6114
|
-
},
|
|
6115
|
-
"outlook-web": {
|
|
6116
|
-
id: "outlook-web",
|
|
6117
|
-
strippedProperties: /* @__PURE__ */ new Set(["position", "transform", "animation", "transition"]),
|
|
6118
|
-
stripMode: "strip",
|
|
6119
|
-
inlineAndStripStyles: false,
|
|
6120
|
-
stripExternalStylesheets: false,
|
|
6121
|
-
stripForms: false,
|
|
6122
|
-
stripSvg: false
|
|
6123
|
-
},
|
|
6124
|
-
"apple-mail-macos": {
|
|
6125
|
-
id: "apple-mail-macos",
|
|
6126
|
-
strippedProperties: EMPTY_SET,
|
|
6127
|
-
stripMode: "strip",
|
|
6128
|
-
inlineAndStripStyles: false,
|
|
6129
|
-
stripExternalStylesheets: false,
|
|
6130
|
-
stripForms: false,
|
|
6131
|
-
stripSvg: false,
|
|
6132
|
-
additionalChecks: appleMailAdditionalChecks
|
|
6133
|
-
},
|
|
6134
|
-
"apple-mail-ios": {
|
|
6135
|
-
id: "apple-mail-ios",
|
|
6136
|
-
strippedProperties: EMPTY_SET,
|
|
6137
|
-
stripMode: "strip",
|
|
6138
|
-
inlineAndStripStyles: false,
|
|
6139
|
-
stripExternalStylesheets: false,
|
|
6140
|
-
stripForms: false,
|
|
6141
|
-
stripSvg: false,
|
|
6142
|
-
additionalChecks: appleMailAdditionalChecks
|
|
6143
|
-
},
|
|
6144
|
-
"yahoo-mail": {
|
|
6145
|
-
id: "yahoo-mail",
|
|
6146
|
-
strippedProperties: /* @__PURE__ */ new Set(["position", "box-shadow", "transform", "animation", "transition", "opacity"]),
|
|
6147
|
-
stripMode: "strip",
|
|
6148
|
-
inlineAndStripStyles: false,
|
|
6149
|
-
stripExternalStylesheets: false,
|
|
6150
|
-
stripForms: false,
|
|
6151
|
-
stripSvg: false,
|
|
6152
|
-
additionalChecks: yahooAdditionalChecks
|
|
6153
|
-
},
|
|
6154
|
-
"samsung-mail": {
|
|
6155
|
-
id: "samsung-mail",
|
|
6156
|
-
strippedProperties: /* @__PURE__ */ new Set(["box-shadow", "transform", "animation", "transition", "opacity"]),
|
|
6157
|
-
stripMode: "info",
|
|
6158
|
-
inlineAndStripStyles: false,
|
|
6159
|
-
stripExternalStylesheets: false,
|
|
6160
|
-
stripForms: false,
|
|
6161
|
-
stripSvg: false
|
|
6162
|
-
},
|
|
6163
|
-
"thunderbird": {
|
|
6164
|
-
id: "thunderbird",
|
|
6165
|
-
strippedProperties: EMPTY_SET,
|
|
6166
|
-
stripMode: "strip",
|
|
6167
|
-
inlineAndStripStyles: false,
|
|
6168
|
-
stripExternalStylesheets: false,
|
|
6169
|
-
stripForms: false,
|
|
6170
|
-
stripSvg: false,
|
|
6171
|
-
additionalChecks: thunderbirdAdditionalChecks
|
|
6172
|
-
},
|
|
6173
|
-
"hey-mail": {
|
|
6174
|
-
id: "hey-mail",
|
|
6175
|
-
strippedProperties: /* @__PURE__ */ new Set(["transform", "animation", "transition"]),
|
|
6176
|
-
stripMode: "strip",
|
|
6177
|
-
valueStrips: [
|
|
6178
|
-
{ prop: "position", pattern: /fixed|sticky/ }
|
|
6179
|
-
],
|
|
6180
|
-
inlineAndStripStyles: false,
|
|
6181
|
-
stripExternalStylesheets: true,
|
|
6182
|
-
stripForms: true,
|
|
6183
|
-
stripSvg: false,
|
|
6184
|
-
additionalChecks: heyAdditionalChecks
|
|
6185
|
-
},
|
|
6186
|
-
"superhuman": {
|
|
6187
|
-
id: "superhuman",
|
|
6188
|
-
strippedProperties: EMPTY_SET,
|
|
6189
|
-
stripMode: "strip",
|
|
6190
|
-
inlineAndStripStyles: false,
|
|
6191
|
-
stripExternalStylesheets: true,
|
|
6192
|
-
stripForms: true,
|
|
6193
|
-
stripSvg: false,
|
|
6194
|
-
additionalChecks: superhumanAdditionalChecks
|
|
6049
|
+
function oklchToRgb(L, C, H) {
|
|
6050
|
+
const hRad = H * Math.PI / 180;
|
|
6051
|
+
const a = C * Math.cos(hRad);
|
|
6052
|
+
const b = C * Math.sin(hRad);
|
|
6053
|
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
6054
|
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
6055
|
+
const s_ = L - 0.0894841775 * a - 1.291485548 * b;
|
|
6056
|
+
const l = l_ * l_ * l_;
|
|
6057
|
+
const m = m_ * m_ * m_;
|
|
6058
|
+
const s = s_ * s_ * s_;
|
|
6059
|
+
const rLin = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
|
6060
|
+
const gLin = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
|
6061
|
+
const bLin = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;
|
|
6062
|
+
return [
|
|
6063
|
+
clamp255(linearToGamma(rLin) * 255),
|
|
6064
|
+
clamp255(linearToGamma(gLin) * 255),
|
|
6065
|
+
clamp255(linearToGamma(bLin) * 255)
|
|
6066
|
+
];
|
|
6067
|
+
}
|
|
6068
|
+
function parseColor(value) {
|
|
6069
|
+
if (!value) return null;
|
|
6070
|
+
const v = value.trim().toLowerCase();
|
|
6071
|
+
if (v === "inherit" || v === "currentcolor" || v === "initial" || v === "unset" || v.startsWith("var(")) {
|
|
6072
|
+
return null;
|
|
6195
6073
|
}
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
const $ = cheerio.load(html);
|
|
6199
|
-
const warnings = [];
|
|
6200
|
-
const clientId = config.id;
|
|
6201
|
-
if (config.additionalChecks && config.inlineAndStripStyles) {
|
|
6202
|
-
warnings.push(...config.additionalChecks($, clientId, html, framework));
|
|
6074
|
+
if (v === "transparent") {
|
|
6075
|
+
return { r: 0, g: 0, b: 0, a: 0 };
|
|
6203
6076
|
}
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6077
|
+
const named = NAMED_COLORS[v];
|
|
6078
|
+
if (named) {
|
|
6079
|
+
return { r: named[0], g: named[1], b: named[2], a: 1 };
|
|
6207
6080
|
}
|
|
6208
|
-
if (
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6081
|
+
if (v.startsWith("#")) {
|
|
6082
|
+
const hex = v.slice(1);
|
|
6083
|
+
if (hex.length === 3) {
|
|
6084
|
+
return {
|
|
6085
|
+
r: parseInt(hex[0] + hex[0], 16),
|
|
6086
|
+
g: parseInt(hex[1] + hex[1], 16),
|
|
6087
|
+
b: parseInt(hex[2] + hex[2], 16),
|
|
6088
|
+
a: 1
|
|
6089
|
+
};
|
|
6217
6090
|
}
|
|
6091
|
+
if (hex.length === 4) {
|
|
6092
|
+
return {
|
|
6093
|
+
r: parseInt(hex[0] + hex[0], 16),
|
|
6094
|
+
g: parseInt(hex[1] + hex[1], 16),
|
|
6095
|
+
b: parseInt(hex[2] + hex[2], 16),
|
|
6096
|
+
a: parseInt(hex[3] + hex[3], 16) / 255
|
|
6097
|
+
};
|
|
6098
|
+
}
|
|
6099
|
+
if (hex.length === 6) {
|
|
6100
|
+
return {
|
|
6101
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
6102
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
6103
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
6104
|
+
a: 1
|
|
6105
|
+
};
|
|
6106
|
+
}
|
|
6107
|
+
if (hex.length === 8) {
|
|
6108
|
+
return {
|
|
6109
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
6110
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
6111
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
6112
|
+
a: parseInt(hex.slice(6, 8), 16) / 255
|
|
6113
|
+
};
|
|
6114
|
+
}
|
|
6115
|
+
return null;
|
|
6218
6116
|
}
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
$(el).replaceWith($(el).html() || "");
|
|
6228
|
-
});
|
|
6229
|
-
}
|
|
6230
|
-
if (config.stripSvg && $("svg").length > 0) {
|
|
6231
|
-
warnings.push(makeWarning({
|
|
6232
|
-
severity: "error",
|
|
6233
|
-
client: clientId,
|
|
6234
|
-
property: "<svg>",
|
|
6235
|
-
message: `${clientId} does not support inline SVG elements.`
|
|
6236
|
-
}, "<svg>", clientId, framework));
|
|
6237
|
-
$("svg").each((_, el) => {
|
|
6238
|
-
$(el).replaceWith('<img alt="[SVG not supported]" />');
|
|
6239
|
-
});
|
|
6240
|
-
}
|
|
6241
|
-
if (config.strippedProperties.size > 0 || config.valueStrips && config.valueStrips.length > 0) {
|
|
6242
|
-
$("[style]").each((_, el) => {
|
|
6243
|
-
const style = $(el).attr("style") || "";
|
|
6244
|
-
const props = parseInlineStyle(style);
|
|
6245
|
-
const removed = [];
|
|
6246
|
-
props.forEach((value, prop) => {
|
|
6247
|
-
var _a;
|
|
6248
|
-
if (config.strippedProperties.has(prop)) {
|
|
6249
|
-
if (config.stripMode === "strip") {
|
|
6250
|
-
removed.push(prop);
|
|
6251
|
-
props.delete(prop);
|
|
6252
|
-
} else {
|
|
6253
|
-
warnings.push(makeWarning({
|
|
6254
|
-
severity: "info",
|
|
6255
|
-
client: clientId,
|
|
6256
|
-
property: prop,
|
|
6257
|
-
message: `${clientId} has limited support for "${prop}".`
|
|
6258
|
-
}, prop, clientId, framework));
|
|
6259
|
-
}
|
|
6260
|
-
return;
|
|
6261
|
-
}
|
|
6262
|
-
for (const vs of (_a = config.valueStrips) != null ? _a : []) {
|
|
6263
|
-
if (prop === vs.prop && vs.pattern.test(value)) {
|
|
6264
|
-
removed.push(prop);
|
|
6265
|
-
props.delete(prop);
|
|
6266
|
-
return;
|
|
6267
|
-
}
|
|
6268
|
-
}
|
|
6269
|
-
});
|
|
6270
|
-
if (removed.length > 0) {
|
|
6271
|
-
$(el).attr("style", serializeStyle(props));
|
|
6272
|
-
for (const prop of removed) {
|
|
6273
|
-
warnings.push(makeWarning({
|
|
6274
|
-
severity: "warning",
|
|
6275
|
-
client: clientId,
|
|
6276
|
-
property: prop,
|
|
6277
|
-
message: `${clientId} strips "${prop}" from styles.`
|
|
6278
|
-
}, prop, clientId, framework));
|
|
6279
|
-
}
|
|
6280
|
-
}
|
|
6281
|
-
});
|
|
6117
|
+
const rgbComma = v.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+%?))?\s*\)$/);
|
|
6118
|
+
if (rgbComma) {
|
|
6119
|
+
return {
|
|
6120
|
+
r: Math.min(255, parseInt(rgbComma[1], 10)),
|
|
6121
|
+
g: Math.min(255, parseInt(rgbComma[2], 10)),
|
|
6122
|
+
b: Math.min(255, parseInt(rgbComma[3], 10)),
|
|
6123
|
+
a: rgbComma[4] !== void 0 ? parseAlpha(rgbComma[4]) : 1
|
|
6124
|
+
};
|
|
6282
6125
|
}
|
|
6283
|
-
|
|
6284
|
-
|
|
6126
|
+
const rgbSpace = v.match(/^rgba?\(\s*(\d+)\s+(\d+)\s+(\d+)(?:\s*\/\s*([\d.]+%?))?\s*\)$/);
|
|
6127
|
+
if (rgbSpace) {
|
|
6128
|
+
return {
|
|
6129
|
+
r: Math.min(255, parseInt(rgbSpace[1], 10)),
|
|
6130
|
+
g: Math.min(255, parseInt(rgbSpace[2], 10)),
|
|
6131
|
+
b: Math.min(255, parseInt(rgbSpace[3], 10)),
|
|
6132
|
+
a: rgbSpace[4] !== void 0 ? parseAlpha(rgbSpace[4]) : 1
|
|
6133
|
+
};
|
|
6285
6134
|
}
|
|
6286
|
-
|
|
6287
|
-
|
|
6135
|
+
const hslComma = v.match(/^hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%(?:\s*,\s*([\d.]+%?))?\s*\)$/);
|
|
6136
|
+
if (hslComma) {
|
|
6137
|
+
const h = (parseFloat(hslComma[1]) % 360 + 360) % 360;
|
|
6138
|
+
const s = Math.min(100, parseFloat(hslComma[2])) / 100;
|
|
6139
|
+
const l = Math.min(100, parseFloat(hslComma[3])) / 100;
|
|
6140
|
+
const [r, g, b] = hslToRgb(h, s, l);
|
|
6141
|
+
return { r, g, b, a: hslComma[4] !== void 0 ? parseAlpha(hslComma[4]) : 1 };
|
|
6142
|
+
}
|
|
6143
|
+
const hslSpace = v.match(/^hsla?\(\s*([\d.]+)\s+([\d.]+)%\s+([\d.]+)%(?:\s*\/\s*([\d.]+%?))?\s*\)$/);
|
|
6144
|
+
if (hslSpace) {
|
|
6145
|
+
const h = (parseFloat(hslSpace[1]) % 360 + 360) % 360;
|
|
6146
|
+
const s = Math.min(100, parseFloat(hslSpace[2])) / 100;
|
|
6147
|
+
const l = Math.min(100, parseFloat(hslSpace[3])) / 100;
|
|
6148
|
+
const [r, g, b] = hslToRgb(h, s, l);
|
|
6149
|
+
return { r, g, b, a: hslSpace[4] !== void 0 ? parseAlpha(hslSpace[4]) : 1 };
|
|
6150
|
+
}
|
|
6151
|
+
const hwbMatch = v.match(/^hwb\(\s*([\d.]+)\s+([\d.]+)%\s+([\d.]+)%(?:\s*\/\s*([\d.]+%?))?\s*\)$/);
|
|
6152
|
+
if (hwbMatch) {
|
|
6153
|
+
const h = (parseFloat(hwbMatch[1]) % 360 + 360) % 360;
|
|
6154
|
+
const w = Math.min(100, parseFloat(hwbMatch[2])) / 100;
|
|
6155
|
+
const bk = Math.min(100, parseFloat(hwbMatch[3])) / 100;
|
|
6156
|
+
const [r, g, b] = hwbToRgb(h, w, bk);
|
|
6157
|
+
return { r, g, b, a: hwbMatch[4] !== void 0 ? parseAlpha(hwbMatch[4]) : 1 };
|
|
6158
|
+
}
|
|
6159
|
+
const oklchMatch = v.match(/^oklch\(\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*([\d.]+%?))?\s*\)$/);
|
|
6160
|
+
if (oklchMatch) {
|
|
6161
|
+
const L = Math.min(1, Math.max(0, parseFloat(oklchMatch[1])));
|
|
6162
|
+
const C = Math.max(0, parseFloat(oklchMatch[2]));
|
|
6163
|
+
const H = (parseFloat(oklchMatch[3]) % 360 + 360) % 360;
|
|
6164
|
+
const [r, g, b] = oklchToRgb(L, C, H);
|
|
6165
|
+
return { r, g, b, a: oklchMatch[4] !== void 0 ? parseAlpha(oklchMatch[4]) : 1 };
|
|
6288
6166
|
}
|
|
6289
|
-
return
|
|
6167
|
+
return null;
|
|
6290
6168
|
}
|
|
6291
|
-
function
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
if (
|
|
6296
|
-
|
|
6297
|
-
}
|
|
6298
|
-
|
|
6299
|
-
if (!config) {
|
|
6300
|
-
return {
|
|
6301
|
-
clientId,
|
|
6302
|
-
html,
|
|
6303
|
-
warnings: [
|
|
6304
|
-
{
|
|
6305
|
-
severity: "info",
|
|
6306
|
-
client: clientId,
|
|
6307
|
-
property: "unknown",
|
|
6308
|
-
message: `No transformation rules available for client "${clientId}".`
|
|
6309
|
-
}
|
|
6310
|
-
]
|
|
6311
|
-
};
|
|
6312
|
-
}
|
|
6313
|
-
return applyTransform(html, config, framework);
|
|
6169
|
+
function formatRgb(rgba) {
|
|
6170
|
+
const r = clamp255(rgba.r);
|
|
6171
|
+
const g = clamp255(rgba.g);
|
|
6172
|
+
const b = clamp255(rgba.b);
|
|
6173
|
+
if (rgba.a < 1) {
|
|
6174
|
+
return `rgba(${r}, ${g}, ${b}, ${rgba.a})`;
|
|
6175
|
+
}
|
|
6176
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
6314
6177
|
}
|
|
6315
|
-
function
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6178
|
+
function relativeLuminance(r, g, b) {
|
|
6179
|
+
const [rs, gs, bs] = [r, g, b].map((c) => {
|
|
6180
|
+
const s = c / 255;
|
|
6181
|
+
return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
6182
|
+
});
|
|
6183
|
+
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
|
6184
|
+
}
|
|
6185
|
+
function contrastRatio(l1, l2) {
|
|
6186
|
+
const lighter = Math.max(l1, l2);
|
|
6187
|
+
const darker = Math.min(l1, l2);
|
|
6188
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
6189
|
+
}
|
|
6190
|
+
function wcagGrade(ratio) {
|
|
6191
|
+
if (ratio >= 7) return "AAA";
|
|
6192
|
+
if (ratio >= 4.5) return "AA";
|
|
6193
|
+
if (ratio >= 3) return "AA Large";
|
|
6194
|
+
return "Fail";
|
|
6195
|
+
}
|
|
6196
|
+
function alphaBlend(fg, bgR, bgG, bgB) {
|
|
6197
|
+
const a = fg.a;
|
|
6198
|
+
return [
|
|
6199
|
+
Math.round(fg.r * a + bgR * (1 - a)),
|
|
6200
|
+
Math.round(fg.g * a + bgG * (1 - a)),
|
|
6201
|
+
Math.round(fg.b * a + bgB * (1 - a))
|
|
6202
|
+
];
|
|
6319
6203
|
}
|
|
6320
6204
|
|
|
6321
|
-
// src/
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
"
|
|
6326
|
-
"
|
|
6327
|
-
"
|
|
6328
|
-
"
|
|
6329
|
-
"
|
|
6330
|
-
"
|
|
6331
|
-
"<picture>": "picture",
|
|
6332
|
-
"<dialog>": "dialog",
|
|
6333
|
-
"<meter>": "meter",
|
|
6334
|
-
"<progress>": "progress",
|
|
6335
|
-
"<select>": "select",
|
|
6336
|
-
"<textarea>": "textarea",
|
|
6337
|
-
"<marquee>": "marquee",
|
|
6338
|
-
"<object>": "object",
|
|
6339
|
-
"<base>": "base"
|
|
6340
|
-
};
|
|
6341
|
-
var HTML_ELEMENT_SEVERITY = {
|
|
6342
|
-
"<style>": "error",
|
|
6343
|
-
"<link>": "error",
|
|
6344
|
-
"<svg>": "error",
|
|
6345
|
-
"<form>": "error",
|
|
6346
|
-
"<video>": "warning",
|
|
6347
|
-
"<audio>": "warning",
|
|
6348
|
-
"<picture>": "warning",
|
|
6349
|
-
"<dialog>": "warning",
|
|
6350
|
-
"<marquee>": "warning",
|
|
6351
|
-
"<meter>": "warning",
|
|
6352
|
-
"<progress>": "warning",
|
|
6353
|
-
"<select>": "warning",
|
|
6354
|
-
"<textarea>": "warning",
|
|
6355
|
-
"<object>": "warning",
|
|
6356
|
-
"<base>": "warning"
|
|
6357
|
-
};
|
|
6358
|
-
var HTML_ELEMENT_MESSAGES = {
|
|
6359
|
-
"<style>": (n) => `${n} strips <style> blocks. Styles must be inlined.`,
|
|
6360
|
-
"<link>": (n) => `${n} does not support external stylesheets.`,
|
|
6361
|
-
"<svg>": (n) => `${n} does not support inline SVG.`,
|
|
6362
|
-
"<video>": (n) => `${n} does not support <video> elements.`,
|
|
6363
|
-
"<form>": (n) => `${n} strips form elements.`
|
|
6205
|
+
// src/downlevel.ts
|
|
6206
|
+
var LOGICAL_PROPERTY_MAP = {
|
|
6207
|
+
"padding-inline": ["padding-left", "padding-right"],
|
|
6208
|
+
"padding-block": ["padding-top", "padding-bottom"],
|
|
6209
|
+
"margin-inline": ["margin-left", "margin-right"],
|
|
6210
|
+
"margin-block": ["margin-top", "margin-bottom"],
|
|
6211
|
+
"inset-inline": ["left", "right"],
|
|
6212
|
+
"inset-block": ["top", "bottom"],
|
|
6213
|
+
"border-inline": ["border-left", "border-right"],
|
|
6214
|
+
"border-block": ["border-top", "border-bottom"]
|
|
6364
6215
|
};
|
|
6365
|
-
var
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
]
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
}
|
|
6375
|
-
function analyzeEmailFromDom($, framework) {
|
|
6376
|
-
const warnings = [];
|
|
6377
|
-
const seenWarnings = /* @__PURE__ */ new Set();
|
|
6378
|
-
function addWarning(w) {
|
|
6379
|
-
const key = `${w.client}:${w.property}:${w.severity}:${w.selector || ""}`;
|
|
6380
|
-
if (!seenWarnings.has(key)) {
|
|
6381
|
-
seenWarnings.add(key);
|
|
6382
|
-
warnings.push(w);
|
|
6216
|
+
var MODERN_COLOR_RE = /\b(oklch|hsl|hsla|hwb|rgba?)\s*\(/g;
|
|
6217
|
+
function extractFunctionCall(str, fnStart, openParenIndex) {
|
|
6218
|
+
let depth = 0;
|
|
6219
|
+
for (let i = openParenIndex; i < str.length; i++) {
|
|
6220
|
+
if (str[i] === "(") depth++;
|
|
6221
|
+
else if (str[i] === ")") {
|
|
6222
|
+
depth--;
|
|
6223
|
+
if (depth === 0) {
|
|
6224
|
+
return str.slice(fnStart, i + 1);
|
|
6225
|
+
}
|
|
6383
6226
|
}
|
|
6384
6227
|
}
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6228
|
+
return null;
|
|
6229
|
+
}
|
|
6230
|
+
function isSpaceSyntaxRgb(args) {
|
|
6231
|
+
const inner = args.trim();
|
|
6232
|
+
return !inner.includes(",") && /^\d/.test(inner);
|
|
6233
|
+
}
|
|
6234
|
+
function convertModernColors(value) {
|
|
6235
|
+
let result = value;
|
|
6236
|
+
let match;
|
|
6237
|
+
MODERN_COLOR_RE.lastIndex = 0;
|
|
6238
|
+
const replacements = [];
|
|
6239
|
+
while ((match = MODERN_COLOR_RE.exec(result)) !== null) {
|
|
6240
|
+
const fnName = match[1].toLowerCase();
|
|
6241
|
+
const fnStart = match.index;
|
|
6242
|
+
const openParen = fnStart + match[0].length - 1;
|
|
6243
|
+
const fullCall = extractFunctionCall(result, fnStart, openParen);
|
|
6244
|
+
if (!fullCall) continue;
|
|
6245
|
+
if (fnName === "rgb" || fnName === "rgba") {
|
|
6246
|
+
const argsStr = fullCall.slice(fullCall.indexOf("(") + 1, -1);
|
|
6247
|
+
if (!isSpaceSyntaxRgb(argsStr)) continue;
|
|
6248
|
+
}
|
|
6249
|
+
const parsed = parseColor(fullCall);
|
|
6250
|
+
if (parsed) {
|
|
6251
|
+
replacements.push({
|
|
6252
|
+
start: fnStart,
|
|
6253
|
+
end: fnStart + fullCall.length,
|
|
6254
|
+
replacement: formatRgb(parsed)
|
|
6255
|
+
});
|
|
6256
|
+
}
|
|
6396
6257
|
}
|
|
6397
|
-
for (
|
|
6398
|
-
const
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6258
|
+
for (let i = replacements.length - 1; i >= 0; i--) {
|
|
6259
|
+
const r = replacements[i];
|
|
6260
|
+
result = result.slice(0, r.start) + r.replacement + result.slice(r.end);
|
|
6261
|
+
}
|
|
6262
|
+
return result;
|
|
6263
|
+
}
|
|
6264
|
+
function convertLogicalProperty(prop, value) {
|
|
6265
|
+
const mapping = LOGICAL_PROPERTY_MAP[prop];
|
|
6266
|
+
if (!mapping) return null;
|
|
6267
|
+
const parts = value.trim().split(/\s+/);
|
|
6268
|
+
const [first, second] = mapping;
|
|
6269
|
+
if (parts.length >= 2) {
|
|
6270
|
+
return [
|
|
6271
|
+
[first, parts[0]],
|
|
6272
|
+
[second, parts[1]]
|
|
6273
|
+
];
|
|
6274
|
+
}
|
|
6275
|
+
return [
|
|
6276
|
+
[first, parts[0]],
|
|
6277
|
+
[second, parts[0]]
|
|
6278
|
+
];
|
|
6279
|
+
}
|
|
6280
|
+
var CALC_INFINITY_RE = /calc\(\s*infinity\s*\*\s*1(px|em|rem|%|vh|vw)\s*\)/gi;
|
|
6281
|
+
var CALC_INFINITY_SIMPLE_RE = /calc\(\s*infinity\s*\)/gi;
|
|
6282
|
+
function resolveCalcInfinity(value) {
|
|
6283
|
+
let result = value.replace(CALC_INFINITY_RE, (_match, unit) => {
|
|
6284
|
+
return `9999${unit}`;
|
|
6285
|
+
});
|
|
6286
|
+
result = result.replace(CALC_INFINITY_SIMPLE_RE, "9999px");
|
|
6287
|
+
return result;
|
|
6288
|
+
}
|
|
6289
|
+
function unnestCSSRules(css) {
|
|
6290
|
+
let result = "";
|
|
6291
|
+
let i = 0;
|
|
6292
|
+
while (i < css.length) {
|
|
6293
|
+
const atMediaInside = findNestedAtRule(css, i);
|
|
6294
|
+
if (atMediaInside) {
|
|
6295
|
+
result += css.slice(i, atMediaInside.outerStart);
|
|
6296
|
+
if (atMediaInside.preamble) {
|
|
6297
|
+
result += `${atMediaInside.selector}{${atMediaInside.preamble}}`;
|
|
6432
6298
|
}
|
|
6299
|
+
result += `${atMediaInside.atRule}{${atMediaInside.selector}{${atMediaInside.body}}}`;
|
|
6300
|
+
i = atMediaInside.outerEnd;
|
|
6301
|
+
} else {
|
|
6302
|
+
result += css.slice(i);
|
|
6303
|
+
break;
|
|
6433
6304
|
}
|
|
6434
6305
|
}
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6306
|
+
return result;
|
|
6307
|
+
}
|
|
6308
|
+
function findNestedAtRule(css, startPos) {
|
|
6309
|
+
for (let i = startPos; i < css.length; i++) {
|
|
6310
|
+
if (css[i] === "{") {
|
|
6311
|
+
const selector = css.slice(startPos, i).trim();
|
|
6312
|
+
if (!selector || selector.startsWith("@")) {
|
|
6313
|
+
const closeIdx2 = findMatchingBrace(css, i);
|
|
6314
|
+
if (closeIdx2 === -1) return null;
|
|
6315
|
+
const rest = findNestedAtRule(css, closeIdx2 + 1);
|
|
6316
|
+
if (rest) return rest;
|
|
6317
|
+
return null;
|
|
6318
|
+
}
|
|
6319
|
+
const innerStart = i + 1;
|
|
6320
|
+
const closeIdx = findMatchingBrace(css, i);
|
|
6321
|
+
if (closeIdx === -1) return null;
|
|
6322
|
+
const innerContent = css.slice(innerStart, closeIdx).trim();
|
|
6323
|
+
const atMatch = innerContent.match(/@(media|supports)\s/);
|
|
6324
|
+
if (atMatch && atMatch.index !== void 0) {
|
|
6325
|
+
const atPos = atMatch.index;
|
|
6326
|
+
const atBraceIdx = innerContent.indexOf("{", atPos);
|
|
6327
|
+
if (atBraceIdx === -1) continue;
|
|
6328
|
+
const atRule = innerContent.slice(atPos, atBraceIdx).trim();
|
|
6329
|
+
const atCloseIdx = findMatchingBrace(innerContent, atBraceIdx);
|
|
6330
|
+
if (atCloseIdx === -1) continue;
|
|
6331
|
+
const body = innerContent.slice(atBraceIdx + 1, atCloseIdx).trim();
|
|
6332
|
+
const beforeAt = innerContent.slice(0, atPos).trim();
|
|
6333
|
+
return __spreadValues({
|
|
6334
|
+
outerStart: findSelectorStart(css, i, startPos),
|
|
6335
|
+
outerEnd: closeIdx + 1,
|
|
6336
|
+
selector,
|
|
6337
|
+
atRule,
|
|
6338
|
+
body
|
|
6339
|
+
}, beforeAt ? { preamble: beforeAt } : {});
|
|
6340
|
+
}
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
return null;
|
|
6344
|
+
}
|
|
6345
|
+
function findSelectorStart(css, braceIdx, minPos) {
|
|
6346
|
+
let i = braceIdx - 1;
|
|
6347
|
+
while (i >= minPos && css[i] !== "}" && css[i] !== ";") {
|
|
6348
|
+
i--;
|
|
6349
|
+
}
|
|
6350
|
+
return i + 1;
|
|
6351
|
+
}
|
|
6352
|
+
function findMatchingBrace(css, openIdx) {
|
|
6353
|
+
let depth = 0;
|
|
6354
|
+
for (let i = openIdx; i < css.length; i++) {
|
|
6355
|
+
if (css[i] === "{") depth++;
|
|
6356
|
+
else if (css[i] === "}") {
|
|
6357
|
+
depth--;
|
|
6358
|
+
if (depth === 0) return i;
|
|
6359
|
+
}
|
|
6360
|
+
}
|
|
6361
|
+
return -1;
|
|
6362
|
+
}
|
|
6363
|
+
function convertRangeMediaQueries(css) {
|
|
6364
|
+
return css.replace(
|
|
6365
|
+
/\(\s*width\s*(>=?|<=?)\s*([\d.]+\w*)\s*\)/g,
|
|
6366
|
+
(_match, op, value) => {
|
|
6367
|
+
if (op === ">=" || op === ">") {
|
|
6368
|
+
return `(min-width: ${value})`;
|
|
6369
|
+
}
|
|
6370
|
+
return `(max-width: ${value})`;
|
|
6371
|
+
}
|
|
6372
|
+
);
|
|
6373
|
+
}
|
|
6374
|
+
var UNIVERSAL_SELECTORS = /* @__PURE__ */ new Set([":root", "*", "html"]);
|
|
6375
|
+
function selectorsCouldMatch(defSelector, useSelector) {
|
|
6376
|
+
if (defSelector === useSelector) return true;
|
|
6377
|
+
if (UNIVERSAL_SELECTORS.has(defSelector)) return true;
|
|
6378
|
+
return false;
|
|
6379
|
+
}
|
|
6380
|
+
function resolveCSSVariables(ast) {
|
|
6381
|
+
const varDefs = [];
|
|
6382
|
+
csstree.walk(ast, {
|
|
6383
|
+
visit: "Rule",
|
|
6384
|
+
enter(rule) {
|
|
6385
|
+
if (rule.type !== "Rule" || !rule.prelude || !rule.block) return;
|
|
6386
|
+
const selector = csstree.generate(rule.prelude);
|
|
6387
|
+
rule.block.children.forEach((node) => {
|
|
6388
|
+
if (node.type === "Declaration" && node.property.startsWith("--")) {
|
|
6389
|
+
varDefs.push({
|
|
6390
|
+
selector,
|
|
6391
|
+
varName: node.property,
|
|
6392
|
+
value: csstree.generate(node.value)
|
|
6393
|
+
});
|
|
6394
|
+
}
|
|
6395
|
+
});
|
|
6396
|
+
}
|
|
6397
|
+
});
|
|
6398
|
+
if (varDefs.length === 0) return;
|
|
6399
|
+
csstree.walk(ast, {
|
|
6400
|
+
visit: "Rule",
|
|
6401
|
+
enter(rule) {
|
|
6402
|
+
if (rule.type !== "Rule" || !rule.prelude || !rule.block) return;
|
|
6403
|
+
const useSelector = csstree.generate(rule.prelude);
|
|
6404
|
+
csstree.walk(rule.block, {
|
|
6405
|
+
visit: "Function",
|
|
6406
|
+
enter(node, item, list) {
|
|
6407
|
+
if (node.name !== "var" || !list) return;
|
|
6408
|
+
const args = [];
|
|
6409
|
+
if (node.children) {
|
|
6410
|
+
node.children.forEach((child) => {
|
|
6411
|
+
args.push(csstree.generate(child));
|
|
6412
|
+
});
|
|
6455
6413
|
}
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6414
|
+
const fullArgs = args.join("");
|
|
6415
|
+
const commaIdx = fullArgs.indexOf(",");
|
|
6416
|
+
const varName = commaIdx !== -1 ? fullArgs.slice(0, commaIdx).trim() : fullArgs.trim();
|
|
6417
|
+
const fallback = commaIdx !== -1 ? fullArgs.slice(commaIdx + 1).trim() : null;
|
|
6418
|
+
let resolved = null;
|
|
6419
|
+
for (const def of varDefs) {
|
|
6420
|
+
if (def.varName === varName && def.selector === useSelector) {
|
|
6421
|
+
resolved = def.value;
|
|
6422
|
+
break;
|
|
6461
6423
|
}
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
}
|
|
6424
|
+
}
|
|
6425
|
+
if (!resolved) {
|
|
6426
|
+
for (const def of varDefs) {
|
|
6427
|
+
if (def.varName === varName && selectorsCouldMatch(def.selector, useSelector)) {
|
|
6428
|
+
resolved = def.value;
|
|
6429
|
+
break;
|
|
6469
6430
|
}
|
|
6470
6431
|
}
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6432
|
+
}
|
|
6433
|
+
if (!resolved && fallback) {
|
|
6434
|
+
resolved = fallback;
|
|
6435
|
+
}
|
|
6436
|
+
if (resolved && item) {
|
|
6437
|
+
const replacement = csstree.parse(resolved, {
|
|
6438
|
+
context: "value"
|
|
6439
|
+
});
|
|
6440
|
+
if (replacement.children) {
|
|
6441
|
+
replacement.children.forEach((child) => {
|
|
6442
|
+
list.insertData(child, item);
|
|
6443
|
+
});
|
|
6444
|
+
list.remove(item);
|
|
6478
6445
|
}
|
|
6479
6446
|
}
|
|
6480
6447
|
}
|
|
6481
6448
|
});
|
|
6482
|
-
} catch (e) {
|
|
6483
6449
|
}
|
|
6484
6450
|
});
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6451
|
+
}
|
|
6452
|
+
function sanitizeDeclarations(ast) {
|
|
6453
|
+
csstree.walk(ast, {
|
|
6454
|
+
visit: "Declaration",
|
|
6455
|
+
enter(decl) {
|
|
6456
|
+
if (decl.property.startsWith("--")) return;
|
|
6457
|
+
if (decl.value.type === "Value") {
|
|
6458
|
+
const raw = csstree.generate(decl.value);
|
|
6459
|
+
let converted = convertModernColors(raw);
|
|
6460
|
+
converted = resolveCalcInfinity(converted);
|
|
6461
|
+
if (converted !== raw) {
|
|
6462
|
+
try {
|
|
6463
|
+
const newValue = csstree.parse(converted, {
|
|
6464
|
+
context: "value"
|
|
6465
|
+
});
|
|
6466
|
+
decl.value = newValue;
|
|
6467
|
+
} catch (e) {
|
|
6502
6468
|
}
|
|
6503
6469
|
}
|
|
6504
6470
|
}
|
|
6505
|
-
|
|
6506
|
-
|
|
6471
|
+
}
|
|
6472
|
+
});
|
|
6473
|
+
csstree.walk(ast, {
|
|
6474
|
+
visit: "Rule",
|
|
6475
|
+
enter(rule) {
|
|
6476
|
+
if (rule.type !== "Rule" || !rule.block) return;
|
|
6477
|
+
const toInsert = [];
|
|
6478
|
+
rule.block.children.forEach(function(node, item) {
|
|
6479
|
+
if (node.type !== "Declaration") return;
|
|
6480
|
+
const mapping = LOGICAL_PROPERTY_MAP[node.property];
|
|
6481
|
+
if (!mapping) return;
|
|
6482
|
+
const valueStr = csstree.generate(node.value);
|
|
6483
|
+
const parts = valueStr.trim().split(/\s+/);
|
|
6484
|
+
const [first, second] = mapping;
|
|
6485
|
+
const val1 = parts[0];
|
|
6486
|
+
const val2 = parts.length >= 2 ? parts[1] : parts[0];
|
|
6487
|
+
toInsert.push(
|
|
6488
|
+
{ property: first, value: val1, important: !!node.important, item },
|
|
6489
|
+
{ property: second, value: val2, important: !!node.important, item }
|
|
6490
|
+
);
|
|
6491
|
+
});
|
|
6492
|
+
for (const { property, value, important, item } of toInsert) {
|
|
6493
|
+
try {
|
|
6494
|
+
const newDecl = {
|
|
6495
|
+
type: "Declaration",
|
|
6496
|
+
important,
|
|
6497
|
+
property,
|
|
6498
|
+
value: csstree.parse(value, { context: "value" })
|
|
6499
|
+
};
|
|
6500
|
+
rule.block.children.insertData(newDecl, item);
|
|
6501
|
+
} catch (e) {
|
|
6502
|
+
}
|
|
6507
6503
|
}
|
|
6508
|
-
const
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
}
|
|
6504
|
+
const removed = /* @__PURE__ */ new Set();
|
|
6505
|
+
for (const { item } of toInsert) {
|
|
6506
|
+
if (!removed.has(item)) {
|
|
6507
|
+
removed.add(item);
|
|
6508
|
+
rule.block.children.remove(item);
|
|
6514
6509
|
}
|
|
6515
6510
|
}
|
|
6516
6511
|
}
|
|
6517
6512
|
});
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6513
|
+
}
|
|
6514
|
+
function downlevelCSS(html) {
|
|
6515
|
+
const $ = cheerio.load(html);
|
|
6516
|
+
$("style").each((_, el) => {
|
|
6517
|
+
let css = $(el).text();
|
|
6518
|
+
css = unnestCSSRules(css);
|
|
6519
|
+
css = convertRangeMediaQueries(css);
|
|
6520
|
+
try {
|
|
6521
|
+
const ast = csstree.parse(css, { parseCustomProperty: true });
|
|
6522
|
+
resolveCSSVariables(ast);
|
|
6523
|
+
sanitizeDeclarations(ast);
|
|
6524
|
+
$(el).text(csstree.generate(ast));
|
|
6525
|
+
} catch (e) {
|
|
6526
|
+
$(el).text(css);
|
|
6527
6527
|
}
|
|
6528
|
-
}
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6528
|
+
});
|
|
6529
|
+
$("[style]").each((_, el) => {
|
|
6530
|
+
const style = $(el).attr("style") || "";
|
|
6531
|
+
const props = parseInlineStyle(style);
|
|
6532
|
+
let changed = false;
|
|
6533
|
+
const newProps = /* @__PURE__ */ new Map();
|
|
6534
|
+
props.forEach((value, prop) => {
|
|
6535
|
+
const converted = convertModernColors(value);
|
|
6536
|
+
if (converted !== value) changed = true;
|
|
6537
|
+
const physical = convertLogicalProperty(prop, converted);
|
|
6538
|
+
if (physical) {
|
|
6539
|
+
changed = true;
|
|
6540
|
+
for (const [p, v] of physical) {
|
|
6541
|
+
newProps.set(p, v);
|
|
6542
|
+
}
|
|
6543
|
+
} else {
|
|
6544
|
+
const calcFixed = resolveCalcInfinity(converted);
|
|
6545
|
+
if (calcFixed !== converted) changed = true;
|
|
6546
|
+
newProps.set(prop, calcFixed);
|
|
6547
|
+
}
|
|
6548
|
+
});
|
|
6549
|
+
if (changed) {
|
|
6550
|
+
$(el).attr("style", serializeStyle(newProps));
|
|
6532
6551
|
}
|
|
6533
|
-
}
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6552
|
+
});
|
|
6553
|
+
return $.html();
|
|
6554
|
+
}
|
|
6555
|
+
|
|
6556
|
+
// src/constants.ts
|
|
6557
|
+
var MAX_HTML_SIZE = 2 * 1024 * 1024;
|
|
6558
|
+
var GENERIC_LINK_TEXT = /* @__PURE__ */ new Set([
|
|
6559
|
+
"click here",
|
|
6560
|
+
"here",
|
|
6561
|
+
"read more",
|
|
6562
|
+
"learn more",
|
|
6563
|
+
"more",
|
|
6564
|
+
"link",
|
|
6565
|
+
"this link",
|
|
6566
|
+
"click",
|
|
6567
|
+
"tap here",
|
|
6568
|
+
"this"
|
|
6569
|
+
]);
|
|
6570
|
+
var GMAIL_CLIP_THRESHOLD = 102 * 1024;
|
|
6571
|
+
var GMAIL_CLIP_WARNING_THRESHOLD = 90 * 1024;
|
|
6572
|
+
var CLIENT_DISPLAY_LIMITS = [
|
|
6573
|
+
{ client: "Gmail (Web)", subjectLimit: 70, preheaderLimit: 90 },
|
|
6574
|
+
{ client: "Gmail (Mobile)", subjectLimit: 40, preheaderLimit: 45 },
|
|
6575
|
+
{ client: "Outlook (Web)", subjectLimit: 60, preheaderLimit: 90 },
|
|
6576
|
+
{ client: "Outlook (Desktop)", subjectLimit: 55, preheaderLimit: 35 },
|
|
6577
|
+
{ client: "Apple Mail (macOS)", subjectLimit: 78, preheaderLimit: 140 },
|
|
6578
|
+
{ client: "Apple Mail (iOS)", subjectLimit: 41, preheaderLimit: 90 },
|
|
6579
|
+
{ client: "Yahoo Mail", subjectLimit: 46, preheaderLimit: 100 },
|
|
6580
|
+
{ client: "Samsung Email", subjectLimit: 40, preheaderLimit: 70 }
|
|
6581
|
+
];
|
|
6582
|
+
var TEMPLATE_VARIABLE_PATTERNS = [
|
|
6583
|
+
[/\{\{[\s\S]*?\}\}/g, "Handlebars/Mustache"],
|
|
6584
|
+
// {{var}}
|
|
6585
|
+
[/\$\{[^}]+\}/g, "ES template literal"],
|
|
6586
|
+
// ${var}
|
|
6587
|
+
[/<%=?\s*[^%]+%>/g, "ERB/EJS"],
|
|
6588
|
+
// <% %> / <%= %>
|
|
6589
|
+
[/\*\|[A-Z_][A-Z0-9_]*\|\*/g, "Mailchimp merge tag"],
|
|
6590
|
+
// *|TAG|*
|
|
6591
|
+
[/%%[A-Za-z_][A-Za-z0-9_]*%%/g, "Salesforce AMPscript"],
|
|
6592
|
+
// %%tag%%
|
|
6593
|
+
[/\{[A-Za-z_][A-Za-z0-9_.]{2,}\}/g, "Single-brace merge field"]
|
|
6594
|
+
// {merge_field} (3+ char names)
|
|
6595
|
+
];
|
|
6596
|
+
var EMPTY_SPAM = { score: 100, level: "low", issues: [] };
|
|
6597
|
+
var EMPTY_LINKS = {
|
|
6598
|
+
totalLinks: 0,
|
|
6599
|
+
issues: [],
|
|
6600
|
+
breakdown: { https: 0, http: 0, mailto: 0, tel: 0, anchor: 0, javascript: 0, protocolRelative: 0, other: 0 }
|
|
6601
|
+
};
|
|
6602
|
+
var EMPTY_ACCESSIBILITY = { score: 100, issues: [] };
|
|
6603
|
+
var EMPTY_IMAGES = { total: 0, totalDataUriBytes: 0, issues: [], images: [] };
|
|
6604
|
+
var EMPTY_INBOX_PREVIEW = { subject: null, preheader: null, subjectLength: 0, preheaderLength: 0, truncation: [], issues: [] };
|
|
6605
|
+
var EMPTY_SIZE = { htmlBytes: 0, humanSize: "0 B", clipped: false, issues: [] };
|
|
6606
|
+
var EMPTY_TEMPLATE = { unresolvedCount: 0, issues: [] };
|
|
6607
|
+
var EMPTY_DELIVERABILITY = { domain: "", checks: [], score: 0, issues: [] };
|
|
6608
|
+
|
|
6609
|
+
// src/transform.ts
|
|
6610
|
+
function hasPseudoSelector(selector) {
|
|
6611
|
+
return new RegExp("(?<![[\\w])::?[a-z][\\w-]*(?:\\(|(?=[^(]))", "i").test(selector);
|
|
6612
|
+
}
|
|
6613
|
+
function addImportantToDeclarations(node) {
|
|
6614
|
+
csstree2.walk(node, {
|
|
6615
|
+
visit: "Declaration",
|
|
6616
|
+
enter(decl) {
|
|
6617
|
+
if (decl.type !== "Declaration") return;
|
|
6618
|
+
if (decl.important) return;
|
|
6619
|
+
decl.important = true;
|
|
6537
6620
|
}
|
|
6538
|
-
}
|
|
6539
|
-
for (const fn of detectedCssFunctions) {
|
|
6540
|
-
checkPropertySupport(fn, addWarning, framework, void 0, propertyLines.get(fn));
|
|
6541
|
-
}
|
|
6542
|
-
const severityOrder = { error: 0, warning: 1, info: 2 };
|
|
6543
|
-
warnings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
6544
|
-
return warnings;
|
|
6621
|
+
});
|
|
6545
6622
|
}
|
|
6546
|
-
function
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6623
|
+
function inlineStyles($) {
|
|
6624
|
+
const styleBlocks = [];
|
|
6625
|
+
$("style").each((_, el) => {
|
|
6626
|
+
styleBlocks.push($(el).text());
|
|
6627
|
+
});
|
|
6628
|
+
if (styleBlocks.length === 0) return "";
|
|
6629
|
+
const preserved = [];
|
|
6630
|
+
for (const block of styleBlocks) {
|
|
6631
|
+
let ast;
|
|
6632
|
+
try {
|
|
6633
|
+
ast = csstree2.parse(block, { parseCustomProperty: true });
|
|
6634
|
+
} catch (e) {
|
|
6635
|
+
continue;
|
|
6636
|
+
}
|
|
6637
|
+
csstree2.walk(ast, {
|
|
6638
|
+
visit: "Atrule",
|
|
6639
|
+
enter(node) {
|
|
6640
|
+
if (node.type !== "Atrule") return;
|
|
6641
|
+
const name = node.name.toLowerCase();
|
|
6642
|
+
if (name === "media" || name === "supports" || name === "keyframes" || name === "-webkit-keyframes" || name === "font-face") {
|
|
6643
|
+
if (name === "media" || name === "supports") {
|
|
6644
|
+
addImportantToDeclarations(node);
|
|
6645
|
+
}
|
|
6646
|
+
preserved.push(csstree2.generate(node));
|
|
6647
|
+
return this.skip;
|
|
6648
|
+
}
|
|
6649
|
+
}
|
|
6650
|
+
});
|
|
6651
|
+
csstree2.walk(ast, {
|
|
6652
|
+
visit: "Rule",
|
|
6653
|
+
enter(node) {
|
|
6654
|
+
if (node.type !== "Rule" || node.prelude.type !== "SelectorList") return;
|
|
6655
|
+
const declarations = csstree2.generate(node.block);
|
|
6656
|
+
const declText = declarations.slice(1, -1).trim();
|
|
6657
|
+
if (!declText) return;
|
|
6658
|
+
const selectorText = csstree2.generate(node.prelude);
|
|
6659
|
+
if (hasPseudoSelector(selectorText)) {
|
|
6660
|
+
preserved.push(csstree2.generate(node));
|
|
6661
|
+
return;
|
|
6662
|
+
}
|
|
6663
|
+
try {
|
|
6664
|
+
$(selectorText).each((_, el) => {
|
|
6665
|
+
const existing = $(el).attr("style") || "";
|
|
6666
|
+
$(el).attr("style", existing ? `${existing}; ${declText}` : declText);
|
|
6667
|
+
});
|
|
6668
|
+
} catch (e) {
|
|
6669
|
+
}
|
|
6670
|
+
}
|
|
6671
|
+
});
|
|
6552
6672
|
}
|
|
6553
|
-
|
|
6554
|
-
return analyzeEmailFromDom($, framework);
|
|
6673
|
+
return preserved.join("\n");
|
|
6555
6674
|
}
|
|
6556
|
-
function
|
|
6557
|
-
|
|
6675
|
+
function makeWarning(base, prop, clientId, framework) {
|
|
6676
|
+
const sug = getSuggestion(prop, clientId, framework);
|
|
6677
|
+
const fix = getCodeFix(prop, clientId, framework);
|
|
6678
|
+
const isFallback = framework && ((sug == null ? void 0 : sug.isGenericFallback) || fix && isCodeFixGenericFallback(prop, clientId, framework));
|
|
6679
|
+
return __spreadProps(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, base), sug ? { suggestion: sug.text } : {}), fix ? { fix } : {}), isFallback ? { fixIsGenericFallback: true } : {}), {
|
|
6680
|
+
fixType: STRUCTURAL_FIX_PROPERTIES.has(prop) ? "structural" : "css"
|
|
6681
|
+
});
|
|
6558
6682
|
}
|
|
6559
|
-
function
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
fixType
|
|
6588
|
-
}, selector ? { selector } : {}), line !== void 0 ? { line } : {}), framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(prop, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
6589
|
-
}
|
|
6683
|
+
function detectAnimations($) {
|
|
6684
|
+
let found = false;
|
|
6685
|
+
$("[style]").each((_, el) => {
|
|
6686
|
+
const style = $(el).attr("style") || "";
|
|
6687
|
+
const props = parseInlineStyle(style);
|
|
6688
|
+
props.forEach((_2, prop) => {
|
|
6689
|
+
if (prop === "animation" || prop === "transition" || prop.startsWith("animation-") || prop.startsWith("transition-")) {
|
|
6690
|
+
found = true;
|
|
6691
|
+
}
|
|
6692
|
+
});
|
|
6693
|
+
});
|
|
6694
|
+
if (!found) {
|
|
6695
|
+
$("style").each((_, el) => {
|
|
6696
|
+
try {
|
|
6697
|
+
const ast = csstree2.parse($(el).text());
|
|
6698
|
+
csstree2.walk(ast, {
|
|
6699
|
+
enter(node) {
|
|
6700
|
+
if (node.type === "Declaration") {
|
|
6701
|
+
const prop = node.property.toLowerCase();
|
|
6702
|
+
if (prop === "animation" || prop === "transition" || prop.startsWith("animation-") || prop.startsWith("transition-")) {
|
|
6703
|
+
found = true;
|
|
6704
|
+
}
|
|
6705
|
+
}
|
|
6706
|
+
}
|
|
6707
|
+
});
|
|
6708
|
+
} catch (e) {
|
|
6709
|
+
}
|
|
6710
|
+
});
|
|
6590
6711
|
}
|
|
6712
|
+
return found;
|
|
6591
6713
|
}
|
|
6592
|
-
function
|
|
6593
|
-
const
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6714
|
+
function gmailAdditionalChecks($, clientId, _html, framework) {
|
|
6715
|
+
const warnings = [];
|
|
6716
|
+
let hasAtFontFace = false;
|
|
6717
|
+
$("style").each((_, el) => {
|
|
6718
|
+
try {
|
|
6719
|
+
const ast = csstree2.parse($(el).text());
|
|
6720
|
+
csstree2.walk(ast, {
|
|
6721
|
+
enter(node) {
|
|
6722
|
+
if (node.type === "Atrule" && node.name === "font-face") {
|
|
6723
|
+
hasAtFontFace = true;
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6726
|
+
});
|
|
6727
|
+
} catch (e) {
|
|
6728
|
+
}
|
|
6729
|
+
});
|
|
6730
|
+
if (hasAtFontFace) {
|
|
6731
|
+
warnings.push(makeWarning({
|
|
6732
|
+
severity: "warning",
|
|
6733
|
+
client: clientId,
|
|
6734
|
+
property: "@font-face",
|
|
6735
|
+
message: "Gmail does not support custom web fonts."
|
|
6736
|
+
}, "@font-face", clientId, framework));
|
|
6604
6737
|
}
|
|
6605
|
-
return
|
|
6738
|
+
return warnings;
|
|
6606
6739
|
}
|
|
6607
|
-
function
|
|
6608
|
-
|
|
6740
|
+
function gmailPostChecks($, clientId, _html, framework) {
|
|
6741
|
+
const warnings = [];
|
|
6742
|
+
$("*").contents().filter(function() {
|
|
6743
|
+
return this.type === "comment";
|
|
6744
|
+
}).each(function() {
|
|
6745
|
+
const commentText = this.data || "";
|
|
6746
|
+
if (commentText.includes("<style") || commentText.includes("[if mso]") || commentText.includes("[if gte mso")) {
|
|
6747
|
+
$(this).remove();
|
|
6748
|
+
}
|
|
6749
|
+
});
|
|
6750
|
+
const styleSug = getSuggestion("<style>:partial", clientId, framework);
|
|
6751
|
+
warnings.push({
|
|
6752
|
+
severity: "info",
|
|
6753
|
+
client: clientId,
|
|
6754
|
+
property: "<style>",
|
|
6755
|
+
message: "Gmail partially supports <style> blocks (head only, 16KB limit). Inlining recommended for safety.",
|
|
6756
|
+
suggestion: styleSug.text
|
|
6757
|
+
});
|
|
6758
|
+
return warnings;
|
|
6609
6759
|
}
|
|
6610
|
-
function
|
|
6611
|
-
|
|
6760
|
+
function outlookWindowsAdditionalChecks($, clientId, _html, framework) {
|
|
6761
|
+
const warnings = [];
|
|
6762
|
+
if ($("[style*='border-radius']").length > 0) {
|
|
6763
|
+
warnings.push(makeWarning({
|
|
6764
|
+
severity: "warning",
|
|
6765
|
+
client: clientId,
|
|
6766
|
+
property: "border-radius",
|
|
6767
|
+
message: "Outlook Classic ignores border-radius. Buttons and containers will have sharp corners."
|
|
6768
|
+
}, "border-radius", clientId, framework));
|
|
6769
|
+
}
|
|
6770
|
+
if ($("[style*='max-width']").length > 0) {
|
|
6771
|
+
warnings.push(makeWarning({
|
|
6772
|
+
severity: "warning",
|
|
6773
|
+
client: clientId,
|
|
6774
|
+
property: "max-width",
|
|
6775
|
+
message: "Outlook Classic ignores max-width."
|
|
6776
|
+
}, "max-width", clientId, framework));
|
|
6777
|
+
}
|
|
6778
|
+
const hasDivLayout = $("div[style*='display']").length > 0 || $("div[style*='flex']").length > 0 || $("div[style*='grid']").length > 0;
|
|
6779
|
+
if (hasDivLayout) {
|
|
6780
|
+
warnings.push(makeWarning({
|
|
6781
|
+
severity: "error",
|
|
6782
|
+
client: clientId,
|
|
6783
|
+
property: "display:flex",
|
|
6784
|
+
message: "Outlook Classic uses Microsoft Word for rendering. Flexbox and Grid layouts will break."
|
|
6785
|
+
}, "display:flex", clientId, framework));
|
|
6786
|
+
}
|
|
6787
|
+
if ($("[style*='background-image']").length > 0 || $("[style*='background:']").filter(
|
|
6788
|
+
(_, el) => ($(el).attr("style") || "").includes("url(")
|
|
6789
|
+
).length > 0) {
|
|
6790
|
+
warnings.push(makeWarning({
|
|
6791
|
+
severity: "warning",
|
|
6792
|
+
client: clientId,
|
|
6793
|
+
property: "background-image",
|
|
6794
|
+
message: "Outlook Classic requires VML for background images."
|
|
6795
|
+
}, "background-image", clientId, framework));
|
|
6796
|
+
}
|
|
6797
|
+
return warnings;
|
|
6612
6798
|
}
|
|
6613
|
-
function
|
|
6614
|
-
|
|
6799
|
+
function appleMailAdditionalChecks($, clientId) {
|
|
6800
|
+
const warnings = [];
|
|
6801
|
+
const imgsWithTransparentBg = $("img").filter((_, el) => {
|
|
6802
|
+
const src = $(el).attr("src") || "";
|
|
6803
|
+
return src.endsWith(".png") || src.endsWith(".svg");
|
|
6804
|
+
});
|
|
6805
|
+
if (imgsWithTransparentBg.length > 0) {
|
|
6806
|
+
warnings.push({
|
|
6807
|
+
severity: "info",
|
|
6808
|
+
client: clientId,
|
|
6809
|
+
property: "dark-mode",
|
|
6810
|
+
message: "PNG/SVG images with transparent backgrounds may become invisible in Apple Mail dark mode.",
|
|
6811
|
+
suggestion: "Add a white background or padding around images, or use dark-mode-friendly image variants."
|
|
6812
|
+
});
|
|
6813
|
+
}
|
|
6814
|
+
return warnings;
|
|
6615
6815
|
}
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
blueviolet: [138, 43, 226],
|
|
6634
|
-
brown: [165, 42, 42],
|
|
6635
|
-
burlywood: [222, 184, 135],
|
|
6636
|
-
cadetblue: [95, 158, 160],
|
|
6637
|
-
chartreuse: [127, 255, 0],
|
|
6638
|
-
chocolate: [210, 105, 30],
|
|
6639
|
-
coral: [255, 127, 80],
|
|
6640
|
-
cornflowerblue: [100, 149, 237],
|
|
6641
|
-
cornsilk: [255, 248, 220],
|
|
6642
|
-
crimson: [220, 20, 60],
|
|
6643
|
-
cyan: [0, 255, 255],
|
|
6644
|
-
darkblue: [0, 0, 139],
|
|
6645
|
-
darkcyan: [0, 139, 139],
|
|
6646
|
-
darkgoldenrod: [184, 134, 11],
|
|
6647
|
-
darkgray: [169, 169, 169],
|
|
6648
|
-
darkgreen: [0, 100, 0],
|
|
6649
|
-
darkgrey: [169, 169, 169],
|
|
6650
|
-
darkkhaki: [189, 183, 107],
|
|
6651
|
-
darkmagenta: [139, 0, 139],
|
|
6652
|
-
darkolivegreen: [85, 107, 47],
|
|
6653
|
-
darkorange: [255, 140, 0],
|
|
6654
|
-
darkorchid: [153, 50, 204],
|
|
6655
|
-
darkred: [139, 0, 0],
|
|
6656
|
-
darksalmon: [233, 150, 122],
|
|
6657
|
-
darkseagreen: [143, 188, 143],
|
|
6658
|
-
darkslateblue: [72, 61, 139],
|
|
6659
|
-
darkslategray: [47, 79, 79],
|
|
6660
|
-
darkslategrey: [47, 79, 79],
|
|
6661
|
-
darkturquoise: [0, 206, 209],
|
|
6662
|
-
darkviolet: [148, 0, 211],
|
|
6663
|
-
deeppink: [255, 20, 147],
|
|
6664
|
-
deepskyblue: [0, 191, 255],
|
|
6665
|
-
dimgray: [105, 105, 105],
|
|
6666
|
-
dimgrey: [105, 105, 105],
|
|
6667
|
-
dodgerblue: [30, 144, 255],
|
|
6668
|
-
firebrick: [178, 34, 34],
|
|
6669
|
-
floralwhite: [255, 250, 240],
|
|
6670
|
-
forestgreen: [34, 139, 34],
|
|
6671
|
-
fuchsia: [255, 0, 255],
|
|
6672
|
-
gainsboro: [220, 220, 220],
|
|
6673
|
-
ghostwhite: [248, 248, 255],
|
|
6674
|
-
gold: [255, 215, 0],
|
|
6675
|
-
goldenrod: [218, 165, 32],
|
|
6676
|
-
gray: [128, 128, 128],
|
|
6677
|
-
green: [0, 128, 0],
|
|
6678
|
-
greenyellow: [173, 255, 47],
|
|
6679
|
-
grey: [128, 128, 128],
|
|
6680
|
-
honeydew: [240, 255, 240],
|
|
6681
|
-
hotpink: [255, 105, 180],
|
|
6682
|
-
indianred: [205, 92, 92],
|
|
6683
|
-
indigo: [75, 0, 130],
|
|
6684
|
-
ivory: [255, 255, 240],
|
|
6685
|
-
khaki: [240, 230, 140],
|
|
6686
|
-
lavender: [230, 230, 250],
|
|
6687
|
-
lavenderblush: [255, 240, 245],
|
|
6688
|
-
lawngreen: [124, 252, 0],
|
|
6689
|
-
lemonchiffon: [255, 250, 205],
|
|
6690
|
-
lightblue: [173, 216, 230],
|
|
6691
|
-
lightcoral: [240, 128, 128],
|
|
6692
|
-
lightcyan: [224, 255, 255],
|
|
6693
|
-
lightgoldenrodyellow: [250, 250, 210],
|
|
6694
|
-
lightgray: [211, 211, 211],
|
|
6695
|
-
lightgreen: [144, 238, 144],
|
|
6696
|
-
lightgrey: [211, 211, 211],
|
|
6697
|
-
lightpink: [255, 182, 193],
|
|
6698
|
-
lightsalmon: [255, 160, 122],
|
|
6699
|
-
lightseagreen: [32, 178, 170],
|
|
6700
|
-
lightskyblue: [135, 206, 250],
|
|
6701
|
-
lightslategray: [119, 136, 153],
|
|
6702
|
-
lightslategrey: [119, 136, 153],
|
|
6703
|
-
lightsteelblue: [176, 196, 222],
|
|
6704
|
-
lightyellow: [255, 255, 224],
|
|
6705
|
-
lime: [0, 255, 0],
|
|
6706
|
-
limegreen: [50, 205, 50],
|
|
6707
|
-
linen: [250, 240, 230],
|
|
6708
|
-
magenta: [255, 0, 255],
|
|
6709
|
-
maroon: [128, 0, 0],
|
|
6710
|
-
mediumaquamarine: [102, 205, 170],
|
|
6711
|
-
mediumblue: [0, 0, 205],
|
|
6712
|
-
mediumorchid: [186, 85, 211],
|
|
6713
|
-
mediumpurple: [147, 111, 219],
|
|
6714
|
-
mediumseagreen: [60, 179, 113],
|
|
6715
|
-
mediumslateblue: [123, 104, 238],
|
|
6716
|
-
mediumspringgreen: [0, 250, 154],
|
|
6717
|
-
mediumturquoise: [72, 209, 204],
|
|
6718
|
-
mediumvioletred: [199, 21, 133],
|
|
6719
|
-
midnightblue: [25, 25, 112],
|
|
6720
|
-
mintcream: [245, 255, 250],
|
|
6721
|
-
mistyrose: [255, 228, 225],
|
|
6722
|
-
moccasin: [255, 228, 181],
|
|
6723
|
-
navajowhite: [255, 222, 173],
|
|
6724
|
-
navy: [0, 0, 128],
|
|
6725
|
-
oldlace: [253, 245, 230],
|
|
6726
|
-
olive: [128, 128, 0],
|
|
6727
|
-
olivedrab: [107, 142, 35],
|
|
6728
|
-
orange: [255, 165, 0],
|
|
6729
|
-
orangered: [255, 69, 0],
|
|
6730
|
-
orchid: [218, 112, 214],
|
|
6731
|
-
palegoldenrod: [238, 232, 170],
|
|
6732
|
-
palegreen: [152, 251, 152],
|
|
6733
|
-
paleturquoise: [175, 238, 238],
|
|
6734
|
-
palevioletred: [219, 112, 147],
|
|
6735
|
-
papayawhip: [255, 239, 213],
|
|
6736
|
-
peachpuff: [255, 218, 185],
|
|
6737
|
-
peru: [205, 133, 63],
|
|
6738
|
-
pink: [255, 192, 203],
|
|
6739
|
-
plum: [221, 160, 221],
|
|
6740
|
-
powderblue: [176, 224, 230],
|
|
6741
|
-
purple: [128, 0, 128],
|
|
6742
|
-
rebeccapurple: [102, 51, 153],
|
|
6743
|
-
red: [255, 0, 0],
|
|
6744
|
-
rosybrown: [188, 143, 143],
|
|
6745
|
-
royalblue: [65, 105, 225],
|
|
6746
|
-
saddlebrown: [139, 69, 19],
|
|
6747
|
-
salmon: [250, 128, 114],
|
|
6748
|
-
sandybrown: [244, 164, 96],
|
|
6749
|
-
seagreen: [46, 139, 87],
|
|
6750
|
-
seashell: [255, 245, 238],
|
|
6751
|
-
sienna: [160, 82, 45],
|
|
6752
|
-
silver: [192, 192, 192],
|
|
6753
|
-
skyblue: [135, 206, 235],
|
|
6754
|
-
slateblue: [106, 90, 205],
|
|
6755
|
-
slategray: [112, 128, 144],
|
|
6756
|
-
slategrey: [112, 128, 144],
|
|
6757
|
-
snow: [255, 250, 250],
|
|
6758
|
-
springgreen: [0, 255, 127],
|
|
6759
|
-
steelblue: [70, 130, 180],
|
|
6760
|
-
tan: [210, 180, 140],
|
|
6761
|
-
teal: [0, 128, 128],
|
|
6762
|
-
thistle: [216, 191, 216],
|
|
6763
|
-
tomato: [255, 99, 71],
|
|
6764
|
-
turquoise: [64, 224, 208],
|
|
6765
|
-
violet: [238, 130, 238],
|
|
6766
|
-
wheat: [245, 222, 179],
|
|
6767
|
-
white: [255, 255, 255],
|
|
6768
|
-
whitesmoke: [245, 245, 245],
|
|
6769
|
-
yellow: [255, 255, 0],
|
|
6770
|
-
yellowgreen: [154, 205, 50]
|
|
6771
|
-
};
|
|
6772
|
-
function parseColor(value) {
|
|
6773
|
-
if (!value) return null;
|
|
6774
|
-
const v = value.trim().toLowerCase();
|
|
6775
|
-
if (v === "inherit" || v === "currentcolor" || v === "initial" || v === "unset" || v.startsWith("var(")) {
|
|
6776
|
-
return null;
|
|
6816
|
+
function yahooAdditionalChecks($, clientId, _html, framework) {
|
|
6817
|
+
const warnings = [];
|
|
6818
|
+
warnings.push({
|
|
6819
|
+
severity: "info",
|
|
6820
|
+
client: clientId,
|
|
6821
|
+
property: "class",
|
|
6822
|
+
message: "Yahoo Mail rewrites CSS class names with a prefix. Class-based selectors in <style> blocks will still work but the names change."
|
|
6823
|
+
});
|
|
6824
|
+
if ($("[style*='background']").filter(
|
|
6825
|
+
(_, el) => ($(el).attr("style") || "").includes("url(")
|
|
6826
|
+
).length > 0) {
|
|
6827
|
+
warnings.push(makeWarning({
|
|
6828
|
+
severity: "warning",
|
|
6829
|
+
client: clientId,
|
|
6830
|
+
property: "background-image",
|
|
6831
|
+
message: "Yahoo Mail has inconsistent support for CSS background images."
|
|
6832
|
+
}, "background-image", clientId, framework));
|
|
6777
6833
|
}
|
|
6778
|
-
|
|
6779
|
-
|
|
6834
|
+
return warnings;
|
|
6835
|
+
}
|
|
6836
|
+
function thunderbirdAdditionalChecks($, clientId) {
|
|
6837
|
+
if (detectAnimations($)) {
|
|
6838
|
+
return [{
|
|
6839
|
+
severity: "info",
|
|
6840
|
+
client: clientId,
|
|
6841
|
+
property: "animation",
|
|
6842
|
+
message: "Thunderbird does not support CSS animations or transitions."
|
|
6843
|
+
}];
|
|
6844
|
+
}
|
|
6845
|
+
return [];
|
|
6846
|
+
}
|
|
6847
|
+
function heyAdditionalChecks(_$, clientId, html) {
|
|
6848
|
+
const warnings = [];
|
|
6849
|
+
if (!html.includes("prefers-color-scheme")) {
|
|
6850
|
+
warnings.push({
|
|
6851
|
+
severity: "info",
|
|
6852
|
+
client: clientId,
|
|
6853
|
+
property: "dark-mode",
|
|
6854
|
+
message: "HEY Mail supports @media (prefers-color-scheme: dark). Consider adding dark mode styles.",
|
|
6855
|
+
suggestion: "Add a @media (prefers-color-scheme: dark) block to optimize for HEY's audience."
|
|
6856
|
+
});
|
|
6857
|
+
}
|
|
6858
|
+
return warnings;
|
|
6859
|
+
}
|
|
6860
|
+
function superhumanAdditionalChecks($, clientId, html) {
|
|
6861
|
+
const warnings = [];
|
|
6862
|
+
if (detectAnimations($)) {
|
|
6863
|
+
warnings.push({
|
|
6864
|
+
severity: "info",
|
|
6865
|
+
client: clientId,
|
|
6866
|
+
property: "animation",
|
|
6867
|
+
message: "Superhuman may honor OS-level 'reduce motion' preferences, disabling animations.",
|
|
6868
|
+
suggestion: "Use @media (prefers-reduced-motion: reduce) to provide static fallbacks."
|
|
6869
|
+
});
|
|
6870
|
+
}
|
|
6871
|
+
warnings.push({
|
|
6872
|
+
severity: "info",
|
|
6873
|
+
client: clientId,
|
|
6874
|
+
property: "<style>",
|
|
6875
|
+
message: "Superhuman uses Chromium rendering with excellent CSS support. Flexbox, Grid, CSS variables, and modern properties all work."
|
|
6876
|
+
});
|
|
6877
|
+
return warnings;
|
|
6878
|
+
}
|
|
6879
|
+
var EMPTY_SET = /* @__PURE__ */ new Set();
|
|
6880
|
+
var CLIENT_CONFIGS = {
|
|
6881
|
+
"gmail-web": {
|
|
6882
|
+
id: "gmail-web",
|
|
6883
|
+
strippedProperties: GMAIL_STRIPPED_PROPERTIES,
|
|
6884
|
+
stripMode: "strip",
|
|
6885
|
+
valueStrips: [
|
|
6886
|
+
{ prop: "display", pattern: /grid/ },
|
|
6887
|
+
{ prop: "background", pattern: /linear-gradient|radial-gradient/ }
|
|
6888
|
+
],
|
|
6889
|
+
inlineAndStripStyles: true,
|
|
6890
|
+
stripExternalStylesheets: true,
|
|
6891
|
+
stripForms: true,
|
|
6892
|
+
stripSvg: true,
|
|
6893
|
+
additionalChecks: gmailAdditionalChecks
|
|
6894
|
+
},
|
|
6895
|
+
"gmail-android": {
|
|
6896
|
+
id: "gmail-android",
|
|
6897
|
+
strippedProperties: GMAIL_STRIPPED_PROPERTIES,
|
|
6898
|
+
stripMode: "strip",
|
|
6899
|
+
valueStrips: [
|
|
6900
|
+
{ prop: "display", pattern: /grid/ },
|
|
6901
|
+
{ prop: "background", pattern: /linear-gradient|radial-gradient/ }
|
|
6902
|
+
],
|
|
6903
|
+
inlineAndStripStyles: true,
|
|
6904
|
+
stripExternalStylesheets: true,
|
|
6905
|
+
stripForms: true,
|
|
6906
|
+
stripSvg: true,
|
|
6907
|
+
additionalChecks: gmailAdditionalChecks
|
|
6908
|
+
},
|
|
6909
|
+
"gmail-ios": {
|
|
6910
|
+
id: "gmail-ios",
|
|
6911
|
+
strippedProperties: GMAIL_STRIPPED_PROPERTIES,
|
|
6912
|
+
stripMode: "strip",
|
|
6913
|
+
valueStrips: [
|
|
6914
|
+
{ prop: "display", pattern: /grid/ },
|
|
6915
|
+
{ prop: "background", pattern: /linear-gradient|radial-gradient/ }
|
|
6916
|
+
],
|
|
6917
|
+
inlineAndStripStyles: true,
|
|
6918
|
+
stripExternalStylesheets: true,
|
|
6919
|
+
stripForms: true,
|
|
6920
|
+
stripSvg: true,
|
|
6921
|
+
additionalChecks: gmailAdditionalChecks
|
|
6922
|
+
},
|
|
6923
|
+
"outlook-windows": {
|
|
6924
|
+
id: "outlook-windows",
|
|
6925
|
+
strippedProperties: /* @__PURE__ */ new Set(["position", "transform", "animation", "transition"]),
|
|
6926
|
+
stripMode: "strip",
|
|
6927
|
+
inlineAndStripStyles: false,
|
|
6928
|
+
stripExternalStylesheets: false,
|
|
6929
|
+
stripForms: false,
|
|
6930
|
+
stripSvg: false
|
|
6931
|
+
},
|
|
6932
|
+
"outlook-windows-legacy": {
|
|
6933
|
+
id: "outlook-windows-legacy",
|
|
6934
|
+
strippedProperties: OUTLOOK_WORD_UNSUPPORTED,
|
|
6935
|
+
stripMode: "strip",
|
|
6936
|
+
valueStrips: [
|
|
6937
|
+
{ prop: "background", pattern: /linear-gradient|radial-gradient/ },
|
|
6938
|
+
{ prop: "background-image", pattern: /linear-gradient|radial-gradient/ }
|
|
6939
|
+
],
|
|
6940
|
+
inlineAndStripStyles: false,
|
|
6941
|
+
stripExternalStylesheets: false,
|
|
6942
|
+
stripForms: false,
|
|
6943
|
+
stripSvg: false,
|
|
6944
|
+
additionalChecks: outlookWindowsAdditionalChecks
|
|
6945
|
+
},
|
|
6946
|
+
"outlook-web": {
|
|
6947
|
+
id: "outlook-web",
|
|
6948
|
+
strippedProperties: /* @__PURE__ */ new Set(["position", "transform", "animation", "transition"]),
|
|
6949
|
+
stripMode: "strip",
|
|
6950
|
+
inlineAndStripStyles: false,
|
|
6951
|
+
stripExternalStylesheets: false,
|
|
6952
|
+
stripForms: false,
|
|
6953
|
+
stripSvg: false
|
|
6954
|
+
},
|
|
6955
|
+
"apple-mail-macos": {
|
|
6956
|
+
id: "apple-mail-macos",
|
|
6957
|
+
strippedProperties: EMPTY_SET,
|
|
6958
|
+
stripMode: "strip",
|
|
6959
|
+
inlineAndStripStyles: false,
|
|
6960
|
+
stripExternalStylesheets: false,
|
|
6961
|
+
stripForms: false,
|
|
6962
|
+
stripSvg: false,
|
|
6963
|
+
additionalChecks: appleMailAdditionalChecks
|
|
6964
|
+
},
|
|
6965
|
+
"apple-mail-ios": {
|
|
6966
|
+
id: "apple-mail-ios",
|
|
6967
|
+
strippedProperties: EMPTY_SET,
|
|
6968
|
+
stripMode: "strip",
|
|
6969
|
+
inlineAndStripStyles: false,
|
|
6970
|
+
stripExternalStylesheets: false,
|
|
6971
|
+
stripForms: false,
|
|
6972
|
+
stripSvg: false,
|
|
6973
|
+
additionalChecks: appleMailAdditionalChecks
|
|
6974
|
+
},
|
|
6975
|
+
"yahoo-mail": {
|
|
6976
|
+
id: "yahoo-mail",
|
|
6977
|
+
strippedProperties: /* @__PURE__ */ new Set(["position", "box-shadow", "transform", "animation", "transition", "opacity"]),
|
|
6978
|
+
stripMode: "strip",
|
|
6979
|
+
inlineAndStripStyles: false,
|
|
6980
|
+
stripExternalStylesheets: false,
|
|
6981
|
+
stripForms: false,
|
|
6982
|
+
stripSvg: false,
|
|
6983
|
+
additionalChecks: yahooAdditionalChecks
|
|
6984
|
+
},
|
|
6985
|
+
"samsung-mail": {
|
|
6986
|
+
id: "samsung-mail",
|
|
6987
|
+
strippedProperties: /* @__PURE__ */ new Set(["box-shadow", "transform", "animation", "transition", "opacity"]),
|
|
6988
|
+
stripMode: "info",
|
|
6989
|
+
inlineAndStripStyles: false,
|
|
6990
|
+
stripExternalStylesheets: false,
|
|
6991
|
+
stripForms: false,
|
|
6992
|
+
stripSvg: false
|
|
6993
|
+
},
|
|
6994
|
+
"thunderbird": {
|
|
6995
|
+
id: "thunderbird",
|
|
6996
|
+
strippedProperties: EMPTY_SET,
|
|
6997
|
+
stripMode: "strip",
|
|
6998
|
+
inlineAndStripStyles: false,
|
|
6999
|
+
stripExternalStylesheets: false,
|
|
7000
|
+
stripForms: false,
|
|
7001
|
+
stripSvg: false,
|
|
7002
|
+
additionalChecks: thunderbirdAdditionalChecks
|
|
7003
|
+
},
|
|
7004
|
+
"hey-mail": {
|
|
7005
|
+
id: "hey-mail",
|
|
7006
|
+
strippedProperties: /* @__PURE__ */ new Set(["transform", "animation", "transition"]),
|
|
7007
|
+
stripMode: "strip",
|
|
7008
|
+
valueStrips: [
|
|
7009
|
+
{ prop: "position", pattern: /fixed|sticky/ }
|
|
7010
|
+
],
|
|
7011
|
+
inlineAndStripStyles: false,
|
|
7012
|
+
stripExternalStylesheets: true,
|
|
7013
|
+
stripForms: true,
|
|
7014
|
+
stripSvg: false,
|
|
7015
|
+
additionalChecks: heyAdditionalChecks
|
|
7016
|
+
},
|
|
7017
|
+
"superhuman": {
|
|
7018
|
+
id: "superhuman",
|
|
7019
|
+
strippedProperties: EMPTY_SET,
|
|
7020
|
+
stripMode: "strip",
|
|
7021
|
+
inlineAndStripStyles: false,
|
|
7022
|
+
stripExternalStylesheets: true,
|
|
7023
|
+
stripForms: true,
|
|
7024
|
+
stripSvg: false,
|
|
7025
|
+
additionalChecks: superhumanAdditionalChecks
|
|
7026
|
+
}
|
|
7027
|
+
};
|
|
7028
|
+
function applyTransform(html, config, framework) {
|
|
7029
|
+
const $ = cheerio2.load(html);
|
|
7030
|
+
const warnings = [];
|
|
7031
|
+
const clientId = config.id;
|
|
7032
|
+
if (config.additionalChecks && config.inlineAndStripStyles) {
|
|
7033
|
+
warnings.push(...config.additionalChecks($, clientId, html, framework));
|
|
7034
|
+
}
|
|
7035
|
+
if (config.inlineAndStripStyles) {
|
|
7036
|
+
const preserved = inlineStyles($);
|
|
7037
|
+
$("style").remove();
|
|
7038
|
+
if (preserved.trim()) {
|
|
7039
|
+
const head = $("head");
|
|
7040
|
+
if (head.length > 0) {
|
|
7041
|
+
head.append(`<style>${preserved}</style>`);
|
|
7042
|
+
} else {
|
|
7043
|
+
$("body").prepend(`<style>${preserved}</style>`);
|
|
7044
|
+
}
|
|
7045
|
+
}
|
|
7046
|
+
}
|
|
7047
|
+
if (config.stripExternalStylesheets) {
|
|
7048
|
+
if ($("link[rel='stylesheet']").length > 0) {
|
|
7049
|
+
warnings.push(makeWarning({
|
|
7050
|
+
severity: "error",
|
|
7051
|
+
client: clientId,
|
|
7052
|
+
property: "<link>",
|
|
7053
|
+
message: `${clientId} does not load external stylesheets.`
|
|
7054
|
+
}, "<link>", clientId, framework));
|
|
7055
|
+
$("link[rel='stylesheet']").remove();
|
|
7056
|
+
}
|
|
7057
|
+
}
|
|
7058
|
+
if (config.stripForms && $("form").length > 0) {
|
|
7059
|
+
warnings.push(makeWarning({
|
|
7060
|
+
severity: "error",
|
|
7061
|
+
client: clientId,
|
|
7062
|
+
property: "<form>",
|
|
7063
|
+
message: `${clientId} removes form elements.`
|
|
7064
|
+
}, "<form>", clientId, framework));
|
|
7065
|
+
$("form").each((_, el) => {
|
|
7066
|
+
$(el).replaceWith($(el).html() || "");
|
|
7067
|
+
});
|
|
7068
|
+
}
|
|
7069
|
+
if (config.stripSvg && $("svg").length > 0) {
|
|
7070
|
+
warnings.push(makeWarning({
|
|
7071
|
+
severity: "error",
|
|
7072
|
+
client: clientId,
|
|
7073
|
+
property: "<svg>",
|
|
7074
|
+
message: `${clientId} does not support inline SVG elements.`
|
|
7075
|
+
}, "<svg>", clientId, framework));
|
|
7076
|
+
$("svg").each((_, el) => {
|
|
7077
|
+
$(el).replaceWith('<img alt="[SVG not supported]" />');
|
|
7078
|
+
});
|
|
7079
|
+
}
|
|
7080
|
+
if (config.strippedProperties.size > 0 || config.valueStrips && config.valueStrips.length > 0) {
|
|
7081
|
+
$("[style]").each((_, el) => {
|
|
7082
|
+
const style = $(el).attr("style") || "";
|
|
7083
|
+
const props = parseInlineStyle(style);
|
|
7084
|
+
const removed = [];
|
|
7085
|
+
props.forEach((value, prop) => {
|
|
7086
|
+
var _a;
|
|
7087
|
+
if (config.strippedProperties.has(prop)) {
|
|
7088
|
+
if (config.stripMode === "strip") {
|
|
7089
|
+
removed.push(prop);
|
|
7090
|
+
props.delete(prop);
|
|
7091
|
+
} else {
|
|
7092
|
+
warnings.push(makeWarning({
|
|
7093
|
+
severity: "info",
|
|
7094
|
+
client: clientId,
|
|
7095
|
+
property: prop,
|
|
7096
|
+
message: `${clientId} has limited support for "${prop}".`
|
|
7097
|
+
}, prop, clientId, framework));
|
|
7098
|
+
}
|
|
7099
|
+
return;
|
|
7100
|
+
}
|
|
7101
|
+
for (const vs of (_a = config.valueStrips) != null ? _a : []) {
|
|
7102
|
+
if (prop === vs.prop && vs.pattern.test(value)) {
|
|
7103
|
+
removed.push(prop);
|
|
7104
|
+
props.delete(prop);
|
|
7105
|
+
return;
|
|
7106
|
+
}
|
|
7107
|
+
}
|
|
7108
|
+
});
|
|
7109
|
+
if (removed.length > 0) {
|
|
7110
|
+
$(el).attr("style", serializeStyle(props));
|
|
7111
|
+
for (const prop of removed) {
|
|
7112
|
+
warnings.push(makeWarning({
|
|
7113
|
+
severity: "warning",
|
|
7114
|
+
client: clientId,
|
|
7115
|
+
property: prop,
|
|
7116
|
+
message: `${clientId} strips "${prop}" from styles.`
|
|
7117
|
+
}, prop, clientId, framework));
|
|
7118
|
+
}
|
|
7119
|
+
}
|
|
7120
|
+
});
|
|
7121
|
+
}
|
|
7122
|
+
if (config.inlineAndStripStyles) {
|
|
7123
|
+
warnings.push(...gmailPostChecks($, clientId, html, framework));
|
|
7124
|
+
}
|
|
7125
|
+
if (config.additionalChecks && !config.inlineAndStripStyles) {
|
|
7126
|
+
warnings.push(...config.additionalChecks($, clientId, html, framework));
|
|
7127
|
+
}
|
|
7128
|
+
return { clientId, html: $.html(), warnings };
|
|
7129
|
+
}
|
|
7130
|
+
function transformForClient(html, clientId, framework) {
|
|
7131
|
+
if (!html || !html.trim()) {
|
|
7132
|
+
return { clientId, html: html || "", warnings: [] };
|
|
7133
|
+
}
|
|
7134
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
7135
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
7136
|
+
}
|
|
7137
|
+
const config = CLIENT_CONFIGS[clientId];
|
|
7138
|
+
if (!config) {
|
|
7139
|
+
return {
|
|
7140
|
+
clientId,
|
|
7141
|
+
html,
|
|
7142
|
+
warnings: [
|
|
7143
|
+
{
|
|
7144
|
+
severity: "info",
|
|
7145
|
+
client: clientId,
|
|
7146
|
+
property: "unknown",
|
|
7147
|
+
message: `No transformation rules available for client "${clientId}".`
|
|
7148
|
+
}
|
|
7149
|
+
]
|
|
7150
|
+
};
|
|
7151
|
+
}
|
|
7152
|
+
const downleveled = downlevelCSS(html);
|
|
7153
|
+
return applyTransform(downleveled, config, framework);
|
|
7154
|
+
}
|
|
7155
|
+
function transformForAllClients(html, framework) {
|
|
7156
|
+
if (!html || !html.trim()) {
|
|
7157
|
+
return Object.keys(CLIENT_CONFIGS).map((clientId) => ({
|
|
7158
|
+
clientId,
|
|
7159
|
+
html: html || "",
|
|
7160
|
+
warnings: []
|
|
7161
|
+
}));
|
|
7162
|
+
}
|
|
7163
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
7164
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
7165
|
+
}
|
|
7166
|
+
const downleveled = downlevelCSS(html);
|
|
7167
|
+
return Object.keys(CLIENT_CONFIGS).map(
|
|
7168
|
+
(clientId) => applyTransform(downleveled, CLIENT_CONFIGS[clientId], framework)
|
|
7169
|
+
);
|
|
7170
|
+
}
|
|
7171
|
+
|
|
7172
|
+
// src/analyze.ts
|
|
7173
|
+
import * as cheerio3 from "cheerio";
|
|
7174
|
+
import * as csstree3 from "css-tree";
|
|
7175
|
+
var HTML_ELEMENT_SELECTORS = {
|
|
7176
|
+
"<style>": "style",
|
|
7177
|
+
"<link>": "link[rel='stylesheet']",
|
|
7178
|
+
"<svg>": "svg",
|
|
7179
|
+
"<video>": "video",
|
|
7180
|
+
"<form>": "form, input, button[type='submit']",
|
|
7181
|
+
"<audio>": "audio",
|
|
7182
|
+
"<picture>": "picture",
|
|
7183
|
+
"<dialog>": "dialog",
|
|
7184
|
+
"<meter>": "meter",
|
|
7185
|
+
"<progress>": "progress",
|
|
7186
|
+
"<select>": "select",
|
|
7187
|
+
"<textarea>": "textarea",
|
|
7188
|
+
"<marquee>": "marquee",
|
|
7189
|
+
"<object>": "object",
|
|
7190
|
+
"<base>": "base"
|
|
7191
|
+
};
|
|
7192
|
+
var HTML_ELEMENT_SEVERITY = {
|
|
7193
|
+
"<style>": "error",
|
|
7194
|
+
"<link>": "error",
|
|
7195
|
+
"<svg>": "error",
|
|
7196
|
+
"<form>": "error",
|
|
7197
|
+
"<video>": "warning",
|
|
7198
|
+
"<audio>": "warning",
|
|
7199
|
+
"<picture>": "warning",
|
|
7200
|
+
"<dialog>": "warning",
|
|
7201
|
+
"<marquee>": "warning",
|
|
7202
|
+
"<meter>": "warning",
|
|
7203
|
+
"<progress>": "warning",
|
|
7204
|
+
"<select>": "warning",
|
|
7205
|
+
"<textarea>": "warning",
|
|
7206
|
+
"<object>": "warning",
|
|
7207
|
+
"<base>": "warning"
|
|
7208
|
+
};
|
|
7209
|
+
var HTML_ELEMENT_MESSAGES = {
|
|
7210
|
+
"<style>": (n) => `${n} strips <style> blocks. Styles must be inlined.`,
|
|
7211
|
+
"<link>": (n) => `${n} does not support external stylesheets.`,
|
|
7212
|
+
"<svg>": (n) => `${n} does not support inline SVG.`,
|
|
7213
|
+
"<video>": (n) => `${n} does not support <video> elements.`,
|
|
7214
|
+
"<form>": (n) => `${n} strips form elements.`
|
|
7215
|
+
};
|
|
7216
|
+
var COMPOUND_DETECTORS = [
|
|
7217
|
+
{ key: "display:flex", property: "display", valueIncludes: "flex" },
|
|
7218
|
+
{ key: "display:grid", property: "display", valueIncludes: "grid" },
|
|
7219
|
+
{ key: "display:none", property: "display", valueIncludes: "none" }
|
|
7220
|
+
];
|
|
7221
|
+
var CSS_FUNCTION_DETECTORS = CSS_FUNCTION_FEATURES.map((fn) => ({
|
|
7222
|
+
key: fn,
|
|
7223
|
+
pattern: `${fn}(`
|
|
7224
|
+
// require opening paren — matches "min(" but not "Minion"
|
|
7225
|
+
}));
|
|
7226
|
+
function analyzeEmailFromDom($, framework) {
|
|
7227
|
+
const warnings = [];
|
|
7228
|
+
const seenWarnings = /* @__PURE__ */ new Set();
|
|
7229
|
+
function addWarning(w) {
|
|
7230
|
+
const key = `${w.client}:${w.property}:${w.severity}:${w.selector || ""}`;
|
|
7231
|
+
if (!seenWarnings.has(key)) {
|
|
7232
|
+
seenWarnings.add(key);
|
|
7233
|
+
warnings.push(w);
|
|
7234
|
+
}
|
|
7235
|
+
}
|
|
7236
|
+
function describeSelector(el) {
|
|
7237
|
+
var _a;
|
|
7238
|
+
const $el = $(el);
|
|
7239
|
+
const tag = ((_a = el.tagName) == null ? void 0 : _a.toLowerCase()) || "";
|
|
7240
|
+
const cls = $el.attr("class");
|
|
7241
|
+
const id = $el.attr("id");
|
|
7242
|
+
if (id) return `${tag}#${id}`;
|
|
7243
|
+
if (cls) return `${tag}.${cls.split(/\s+/)[0]}`;
|
|
7244
|
+
const href = $el.attr("href");
|
|
7245
|
+
if (href) return `${tag}[href]`;
|
|
7246
|
+
return tag;
|
|
7247
|
+
}
|
|
7248
|
+
for (const feature of HTML_ELEMENT_FEATURES) {
|
|
7249
|
+
const selector = HTML_ELEMENT_SELECTORS[feature];
|
|
7250
|
+
if (!selector) continue;
|
|
7251
|
+
if ($(selector).length === 0) continue;
|
|
7252
|
+
const supportData = CSS_SUPPORT[feature];
|
|
7253
|
+
if (!supportData) continue;
|
|
7254
|
+
const baseSeverity = HTML_ELEMENT_SEVERITY[feature] || "warning";
|
|
7255
|
+
for (const client of EMAIL_CLIENTS) {
|
|
7256
|
+
const support = supportData[client.id];
|
|
7257
|
+
if (support === "unsupported") {
|
|
7258
|
+
const msgFn = HTML_ELEMENT_MESSAGES[feature];
|
|
7259
|
+
const message = msgFn ? msgFn(client.name) : `${client.name} does not support ${feature}.`;
|
|
7260
|
+
const sug = getSuggestion(feature, client.id, framework);
|
|
7261
|
+
const fix = getCodeFix(feature, client.id, framework);
|
|
7262
|
+
addWarning(__spreadValues({
|
|
7263
|
+
severity: baseSeverity,
|
|
7264
|
+
client: client.id,
|
|
7265
|
+
property: feature,
|
|
7266
|
+
message,
|
|
7267
|
+
suggestion: sug.text,
|
|
7268
|
+
fix,
|
|
7269
|
+
fixType: getFixType(feature)
|
|
7270
|
+
}, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(feature, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
7271
|
+
} else if (support === "partial" && feature === "<style>") {
|
|
7272
|
+
const sug = getSuggestion("<style>:partial", client.id, framework);
|
|
7273
|
+
const fix = getCodeFix("<style>", client.id, framework);
|
|
7274
|
+
addWarning(__spreadValues({
|
|
7275
|
+
severity: "warning",
|
|
7276
|
+
client: client.id,
|
|
7277
|
+
property: "<style>",
|
|
7278
|
+
message: `${client.name} has partial <style> support (head only, with limitations). Inline styles recommended.`,
|
|
7279
|
+
suggestion: sug.text,
|
|
7280
|
+
fix,
|
|
7281
|
+
fixType: getFixType("<style>")
|
|
7282
|
+
}, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<style>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
7283
|
+
}
|
|
7284
|
+
}
|
|
7285
|
+
}
|
|
7286
|
+
const parsedAtRules = /* @__PURE__ */ new Set();
|
|
7287
|
+
const parsedProperties = /* @__PURE__ */ new Set();
|
|
7288
|
+
const propertyLines = /* @__PURE__ */ new Map();
|
|
7289
|
+
const detectedCssFunctions = /* @__PURE__ */ new Set();
|
|
7290
|
+
const detectedPseudoClasses = /* @__PURE__ */ new Set();
|
|
7291
|
+
const detectedPseudoElements = /* @__PURE__ */ new Set();
|
|
7292
|
+
$("style").each((_, el) => {
|
|
7293
|
+
const cssText = $(el).text();
|
|
7294
|
+
try {
|
|
7295
|
+
const ast = csstree3.parse(cssText, { parseCustomProperty: true, positions: true });
|
|
7296
|
+
csstree3.walk(ast, {
|
|
7297
|
+
enter(node) {
|
|
7298
|
+
if (node.type === "Atrule") {
|
|
7299
|
+
parsedAtRules.add(`@${node.name}`);
|
|
7300
|
+
}
|
|
7301
|
+
if (node.type === "PseudoClassSelector") {
|
|
7302
|
+
detectedPseudoClasses.add(`:${node.name}`);
|
|
7303
|
+
}
|
|
7304
|
+
if (node.type === "PseudoElementSelector") {
|
|
7305
|
+
detectedPseudoElements.add(`::${node.name}`);
|
|
7306
|
+
}
|
|
7307
|
+
if (node.type === "Declaration") {
|
|
7308
|
+
const prop = node.property.toLowerCase();
|
|
7309
|
+
parsedProperties.add(prop);
|
|
7310
|
+
if (node.loc && !propertyLines.has(prop)) {
|
|
7311
|
+
propertyLines.set(prop, node.loc.start.line);
|
|
7312
|
+
}
|
|
7313
|
+
const valueStr = csstree3.generate(node.value);
|
|
7314
|
+
for (const det of COMPOUND_DETECTORS) {
|
|
7315
|
+
if (prop === det.property && valueStr.includes(det.valueIncludes)) {
|
|
7316
|
+
parsedProperties.add(det.key);
|
|
7317
|
+
if (node.loc && !propertyLines.has(det.key)) {
|
|
7318
|
+
propertyLines.set(det.key, node.loc.start.line);
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
7321
|
+
}
|
|
7322
|
+
for (const fn of CSS_FUNCTION_DETECTORS) {
|
|
7323
|
+
if (valueStr.includes(fn.pattern)) {
|
|
7324
|
+
detectedCssFunctions.add(fn.key);
|
|
7325
|
+
if (node.loc && !propertyLines.has(fn.key)) {
|
|
7326
|
+
propertyLines.set(fn.key, node.loc.start.line);
|
|
7327
|
+
}
|
|
7328
|
+
}
|
|
7329
|
+
}
|
|
7330
|
+
}
|
|
7331
|
+
}
|
|
7332
|
+
});
|
|
7333
|
+
} catch (e) {
|
|
7334
|
+
}
|
|
7335
|
+
});
|
|
7336
|
+
for (const atRule of AT_RULE_FEATURES) {
|
|
7337
|
+
if (!parsedAtRules.has(atRule)) continue;
|
|
7338
|
+
checkPropertySupport(atRule, addWarning, framework);
|
|
6780
7339
|
}
|
|
6781
|
-
const
|
|
6782
|
-
|
|
6783
|
-
|
|
7340
|
+
const cssPropertiesToCheck = Object.keys(CSS_SUPPORT).filter(
|
|
7341
|
+
(k) => !k.startsWith("<") && !k.startsWith("@")
|
|
7342
|
+
);
|
|
7343
|
+
$("[style]").each((_, el) => {
|
|
7344
|
+
const style = $(el).attr("style") || "";
|
|
7345
|
+
const props = parseStyleProperties(style);
|
|
7346
|
+
const selector = describeSelector(el);
|
|
7347
|
+
for (const prop of props) {
|
|
7348
|
+
for (const det of COMPOUND_DETECTORS) {
|
|
7349
|
+
if (prop === det.property) {
|
|
7350
|
+
const value2 = getStyleValue(style, prop);
|
|
7351
|
+
if (value2 == null ? void 0 : value2.includes(det.valueIncludes)) {
|
|
7352
|
+
checkPropertySupport(det.key, addWarning, framework, selector);
|
|
7353
|
+
}
|
|
7354
|
+
}
|
|
7355
|
+
}
|
|
7356
|
+
if (cssPropertiesToCheck.includes(prop)) {
|
|
7357
|
+
checkPropertySupport(prop, addWarning, framework, selector);
|
|
7358
|
+
}
|
|
7359
|
+
const value = getStyleValue(style, prop);
|
|
7360
|
+
if (value) {
|
|
7361
|
+
for (const fn of CSS_FUNCTION_DETECTORS) {
|
|
7362
|
+
if (value.includes(fn.pattern)) {
|
|
7363
|
+
checkPropertySupport(fn.key, addWarning, framework, selector);
|
|
7364
|
+
}
|
|
7365
|
+
}
|
|
7366
|
+
}
|
|
7367
|
+
}
|
|
7368
|
+
});
|
|
7369
|
+
for (const prop of parsedProperties) {
|
|
7370
|
+
if (prop.includes(":")) continue;
|
|
7371
|
+
if (!cssPropertiesToCheck.includes(prop)) continue;
|
|
7372
|
+
checkPropertySupport(prop, addWarning, framework, void 0, propertyLines.get(prop));
|
|
6784
7373
|
}
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
if (
|
|
6788
|
-
|
|
6789
|
-
r: parseInt(hex[0] + hex[0], 16),
|
|
6790
|
-
g: parseInt(hex[1] + hex[1], 16),
|
|
6791
|
-
b: parseInt(hex[2] + hex[2], 16),
|
|
6792
|
-
a: 1
|
|
6793
|
-
};
|
|
7374
|
+
for (const compound of COMPOUND_VALUE_FEATURES) {
|
|
7375
|
+
if (compound.startsWith(":") || compound.startsWith("::")) continue;
|
|
7376
|
+
if (parsedProperties.has(compound)) {
|
|
7377
|
+
checkPropertySupport(compound, addWarning, framework, void 0, propertyLines.get(compound));
|
|
6794
7378
|
}
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
b: parseInt(hex.slice(4, 6), 16),
|
|
6800
|
-
a: 1
|
|
6801
|
-
};
|
|
7379
|
+
}
|
|
7380
|
+
for (const pseudo of detectedPseudoClasses) {
|
|
7381
|
+
if (CSS_SUPPORT[pseudo]) {
|
|
7382
|
+
checkPropertySupport(pseudo, addWarning, framework);
|
|
6802
7383
|
}
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
b: parseInt(hex.slice(4, 6), 16),
|
|
6808
|
-
a: parseInt(hex.slice(6, 8), 16) / 255
|
|
6809
|
-
};
|
|
7384
|
+
}
|
|
7385
|
+
for (const pseudo of detectedPseudoElements) {
|
|
7386
|
+
if (CSS_SUPPORT[pseudo]) {
|
|
7387
|
+
checkPropertySupport(pseudo, addWarning, framework);
|
|
6810
7388
|
}
|
|
6811
|
-
return null;
|
|
6812
7389
|
}
|
|
6813
|
-
const
|
|
6814
|
-
|
|
6815
|
-
return {
|
|
6816
|
-
r: Math.min(255, parseInt(rgbMatch[1], 10)),
|
|
6817
|
-
g: Math.min(255, parseInt(rgbMatch[2], 10)),
|
|
6818
|
-
b: Math.min(255, parseInt(rgbMatch[3], 10)),
|
|
6819
|
-
a: rgbMatch[4] !== void 0 ? Math.min(1, parseFloat(rgbMatch[4])) : 1
|
|
6820
|
-
};
|
|
7390
|
+
for (const fn of detectedCssFunctions) {
|
|
7391
|
+
checkPropertySupport(fn, addWarning, framework, void 0, propertyLines.get(fn));
|
|
6821
7392
|
}
|
|
6822
|
-
|
|
7393
|
+
const severityOrder = { error: 0, warning: 1, info: 2 };
|
|
7394
|
+
warnings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
7395
|
+
return warnings;
|
|
6823
7396
|
}
|
|
6824
|
-
function
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
7397
|
+
function analyzeEmail(html, framework) {
|
|
7398
|
+
if (!html || !html.trim()) {
|
|
7399
|
+
return [];
|
|
7400
|
+
}
|
|
7401
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
7402
|
+
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
7403
|
+
}
|
|
7404
|
+
const $ = cheerio3.load(html);
|
|
7405
|
+
return analyzeEmailFromDom($, framework);
|
|
6830
7406
|
}
|
|
6831
|
-
function
|
|
6832
|
-
|
|
6833
|
-
const darker = Math.min(l1, l2);
|
|
6834
|
-
return (lighter + 0.05) / (darker + 0.05);
|
|
7407
|
+
function getFixType(prop) {
|
|
7408
|
+
return STRUCTURAL_FIX_PROPERTIES.has(prop) ? "structural" : "css";
|
|
6835
7409
|
}
|
|
6836
|
-
function
|
|
6837
|
-
|
|
6838
|
-
if (
|
|
6839
|
-
|
|
6840
|
-
|
|
7410
|
+
function checkPropertySupport(prop, addWarning, framework, selector, line) {
|
|
7411
|
+
const supportData = CSS_SUPPORT[prop];
|
|
7412
|
+
if (!supportData) return;
|
|
7413
|
+
const fixType = getFixType(prop);
|
|
7414
|
+
for (const client of EMAIL_CLIENTS) {
|
|
7415
|
+
const support = supportData[client.id] || "unknown";
|
|
7416
|
+
if (support === "unsupported") {
|
|
7417
|
+
const sug = getSuggestion(prop, client.id, framework);
|
|
7418
|
+
const fix = getCodeFix(prop, client.id, framework);
|
|
7419
|
+
addWarning(__spreadValues(__spreadValues(__spreadValues({
|
|
7420
|
+
severity: "warning",
|
|
7421
|
+
client: client.id,
|
|
7422
|
+
property: prop,
|
|
7423
|
+
message: `${client.name} does not support "${prop}".`,
|
|
7424
|
+
suggestion: sug.text,
|
|
7425
|
+
fix,
|
|
7426
|
+
fixType
|
|
7427
|
+
}, selector ? { selector } : {}), line !== void 0 ? { line } : {}), framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(prop, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
7428
|
+
} else if (support === "partial") {
|
|
7429
|
+
const sug = getSuggestion(prop, client.id, framework);
|
|
7430
|
+
const fix = getCodeFix(prop, client.id, framework);
|
|
7431
|
+
addWarning(__spreadValues(__spreadValues(__spreadValues({
|
|
7432
|
+
severity: "info",
|
|
7433
|
+
client: client.id,
|
|
7434
|
+
property: prop,
|
|
7435
|
+
message: `${client.name} has partial support for "${prop}".`,
|
|
7436
|
+
suggestion: sug.text,
|
|
7437
|
+
fix,
|
|
7438
|
+
fixType
|
|
7439
|
+
}, selector ? { selector } : {}), line !== void 0 ? { line } : {}), framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(prop, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
7440
|
+
}
|
|
7441
|
+
}
|
|
6841
7442
|
}
|
|
6842
|
-
function
|
|
6843
|
-
const
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
7443
|
+
function generateCompatibilityScore(warnings) {
|
|
7444
|
+
const result = {};
|
|
7445
|
+
for (const client of EMAIL_CLIENTS) {
|
|
7446
|
+
const clientWarnings = warnings.filter((w) => w.client === client.id);
|
|
7447
|
+
const errorProps = new Set(clientWarnings.filter((w) => w.severity === "error").map((w) => w.property));
|
|
7448
|
+
const warnProps = new Set(clientWarnings.filter((w) => w.severity === "warning").map((w) => w.property));
|
|
7449
|
+
const infoProps = new Set(clientWarnings.filter((w) => w.severity === "info").map((w) => w.property));
|
|
7450
|
+
const errors = errorProps.size;
|
|
7451
|
+
const warns = warnProps.size;
|
|
7452
|
+
const info = infoProps.size;
|
|
7453
|
+
const score = Math.max(0, Math.min(100, 100 - errors * 10 - warns * 3));
|
|
7454
|
+
result[client.id] = { score, errors, warnings: warns, info };
|
|
7455
|
+
}
|
|
7456
|
+
return result;
|
|
7457
|
+
}
|
|
7458
|
+
function warningsForClient(warnings, clientId) {
|
|
7459
|
+
return warnings.filter((w) => w.client === clientId);
|
|
7460
|
+
}
|
|
7461
|
+
function errorWarnings(warnings) {
|
|
7462
|
+
return warnings.filter((w) => w.severity === "error");
|
|
7463
|
+
}
|
|
7464
|
+
function structuralWarnings(warnings) {
|
|
7465
|
+
return warnings.filter((w) => w.fixType === "structural");
|
|
6849
7466
|
}
|
|
6850
7467
|
|
|
6851
7468
|
// src/dark-mode.ts
|
|
7469
|
+
import * as cheerio4 from "cheerio";
|
|
7470
|
+
import * as csstree4 from "css-tree";
|
|
6852
7471
|
var LIGHT_THRESHOLD = 0.7;
|
|
6853
7472
|
var DARK_THRESHOLD = 0.15;
|
|
6854
7473
|
function simulateDarkMode(html, clientId) {
|
|
@@ -6858,7 +7477,7 @@ function simulateDarkMode(html, clientId) {
|
|
|
6858
7477
|
if (html.length > MAX_HTML_SIZE) {
|
|
6859
7478
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
6860
7479
|
}
|
|
6861
|
-
const $ =
|
|
7480
|
+
const $ = cheerio4.load(html);
|
|
6862
7481
|
const warnings = [];
|
|
6863
7482
|
$("img").each((_, el) => {
|
|
6864
7483
|
const src = $(el).attr("src") || "";
|
|
@@ -7055,35 +7674,35 @@ function applyColorInversion($, mode) {
|
|
|
7055
7674
|
$("style").each((_, el) => {
|
|
7056
7675
|
const cssText = $(el).text();
|
|
7057
7676
|
try {
|
|
7058
|
-
const ast =
|
|
7677
|
+
const ast = csstree4.parse(cssText, { parseCustomProperty: true });
|
|
7059
7678
|
let modified = false;
|
|
7060
|
-
|
|
7679
|
+
csstree4.walk(ast, {
|
|
7061
7680
|
enter(node) {
|
|
7062
7681
|
if (node.type !== "Declaration") return;
|
|
7063
7682
|
const prop = node.property.toLowerCase();
|
|
7064
7683
|
if (!COLOR_PROPS.has(prop) && prop !== "background") return;
|
|
7065
|
-
const valueStr =
|
|
7684
|
+
const valueStr = csstree4.generate(node.value);
|
|
7066
7685
|
if (prop === "background") {
|
|
7067
7686
|
const bgColor = extractBackgroundColor(valueStr);
|
|
7068
7687
|
if (bgColor) {
|
|
7069
7688
|
const inverted = invertColor(bgColor, mode);
|
|
7070
7689
|
if (inverted) {
|
|
7071
7690
|
const newValue = valueStr.replace(bgColor, inverted);
|
|
7072
|
-
node.value =
|
|
7691
|
+
node.value = csstree4.parse(newValue, { context: "value" });
|
|
7073
7692
|
modified = true;
|
|
7074
7693
|
}
|
|
7075
7694
|
}
|
|
7076
7695
|
} else {
|
|
7077
7696
|
const inverted = invertColor(valueStr, mode);
|
|
7078
7697
|
if (inverted) {
|
|
7079
|
-
node.value =
|
|
7698
|
+
node.value = csstree4.parse(inverted, { context: "value" });
|
|
7080
7699
|
modified = true;
|
|
7081
7700
|
}
|
|
7082
7701
|
}
|
|
7083
7702
|
}
|
|
7084
7703
|
});
|
|
7085
7704
|
if (modified) {
|
|
7086
|
-
$(el).text(
|
|
7705
|
+
$(el).text(csstree4.generate(ast));
|
|
7087
7706
|
}
|
|
7088
7707
|
} catch (e) {
|
|
7089
7708
|
}
|
|
@@ -7395,7 +8014,7 @@ function extractCode(response) {
|
|
|
7395
8014
|
}
|
|
7396
8015
|
|
|
7397
8016
|
// src/spam-scorer.ts
|
|
7398
|
-
import * as
|
|
8017
|
+
import * as cheerio5 from "cheerio";
|
|
7399
8018
|
var SPAM_TRIGGER_PHRASES = [
|
|
7400
8019
|
"act now",
|
|
7401
8020
|
"limited time",
|
|
@@ -7826,12 +8445,12 @@ function analyzeSpam(html, options) {
|
|
|
7826
8445
|
if (html.length > MAX_HTML_SIZE) {
|
|
7827
8446
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
7828
8447
|
}
|
|
7829
|
-
const $ =
|
|
8448
|
+
const $ = cheerio5.load(html);
|
|
7830
8449
|
return analyzeSpamFromDom($, options);
|
|
7831
8450
|
}
|
|
7832
8451
|
|
|
7833
8452
|
// src/link-validator.ts
|
|
7834
|
-
import * as
|
|
8453
|
+
import * as cheerio6 from "cheerio";
|
|
7835
8454
|
function classifyHref(href) {
|
|
7836
8455
|
if (!href || !href.trim()) return "empty";
|
|
7837
8456
|
const h = href.trim().toLowerCase();
|
|
@@ -8027,12 +8646,12 @@ function validateLinks(html) {
|
|
|
8027
8646
|
if (html.length > MAX_HTML_SIZE) {
|
|
8028
8647
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
8029
8648
|
}
|
|
8030
|
-
const $ =
|
|
8649
|
+
const $ = cheerio6.load(html);
|
|
8031
8650
|
return validateLinksFromDom($);
|
|
8032
8651
|
}
|
|
8033
8652
|
|
|
8034
8653
|
// src/accessibility-checker.ts
|
|
8035
|
-
import * as
|
|
8654
|
+
import * as cheerio7 from "cheerio";
|
|
8036
8655
|
var RULE_PENALTY_CAPS = {
|
|
8037
8656
|
"img-missing-alt": 3,
|
|
8038
8657
|
"link-generic-text": 3,
|
|
@@ -8335,12 +8954,12 @@ function checkAccessibility(html) {
|
|
|
8335
8954
|
if (html.length > MAX_HTML_SIZE) {
|
|
8336
8955
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
8337
8956
|
}
|
|
8338
|
-
const $ =
|
|
8957
|
+
const $ = cheerio7.load(html);
|
|
8339
8958
|
return checkAccessibilityFromDom($);
|
|
8340
8959
|
}
|
|
8341
8960
|
|
|
8342
8961
|
// src/image-analyzer.ts
|
|
8343
|
-
import * as
|
|
8962
|
+
import * as cheerio8 from "cheerio";
|
|
8344
8963
|
var DATA_URI_WARN_BYTES = 100 * 1024;
|
|
8345
8964
|
var TOTAL_DATA_URI_WARN_BYTES = 500 * 1024;
|
|
8346
8965
|
var HIGH_IMAGE_COUNT = 10;
|
|
@@ -8504,12 +9123,12 @@ function analyzeImages(html) {
|
|
|
8504
9123
|
if (html.length > MAX_HTML_SIZE) {
|
|
8505
9124
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
8506
9125
|
}
|
|
8507
|
-
const $ =
|
|
9126
|
+
const $ = cheerio8.load(html);
|
|
8508
9127
|
return analyzeImagesFromDom($);
|
|
8509
9128
|
}
|
|
8510
9129
|
|
|
8511
9130
|
// src/inbox-preview.ts
|
|
8512
|
-
import * as
|
|
9131
|
+
import * as cheerio9 from "cheerio";
|
|
8513
9132
|
var MAX_SUBJECT_LENGTH = 60;
|
|
8514
9133
|
var MAX_PREHEADER_LENGTH = 100;
|
|
8515
9134
|
var MIN_PREHEADER_LENGTH = 30;
|
|
@@ -8529,7 +9148,7 @@ function extractInboxPreview(html) {
|
|
|
8529
9148
|
if (html.length > MAX_HTML_SIZE) {
|
|
8530
9149
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
8531
9150
|
}
|
|
8532
|
-
const $ =
|
|
9151
|
+
const $ = cheerio9.load(html);
|
|
8533
9152
|
return extractInboxPreviewFromDom($);
|
|
8534
9153
|
}
|
|
8535
9154
|
function extractInboxPreviewFromDom($) {
|
|
@@ -8697,7 +9316,7 @@ function getFirstVisibleText($, body) {
|
|
|
8697
9316
|
}
|
|
8698
9317
|
|
|
8699
9318
|
// src/size-checker.ts
|
|
8700
|
-
import * as
|
|
9319
|
+
import * as cheerio10 from "cheerio";
|
|
8701
9320
|
function humanizeBytes(bytes) {
|
|
8702
9321
|
if (bytes < 1024) return `${bytes} B`;
|
|
8703
9322
|
const kb = bytes / 1024;
|
|
@@ -8735,12 +9354,12 @@ function checkSize(html) {
|
|
|
8735
9354
|
if (html.length > MAX_HTML_SIZE) {
|
|
8736
9355
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
8737
9356
|
}
|
|
8738
|
-
const $ =
|
|
9357
|
+
const $ = cheerio10.load(html);
|
|
8739
9358
|
return checkSizeFromDom($, html);
|
|
8740
9359
|
}
|
|
8741
9360
|
|
|
8742
9361
|
// src/template-checker.ts
|
|
8743
|
-
import * as
|
|
9362
|
+
import * as cheerio11 from "cheerio";
|
|
8744
9363
|
function checkTemplateVariablesFromDom($) {
|
|
8745
9364
|
const issues = [];
|
|
8746
9365
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -8803,12 +9422,12 @@ function checkTemplateVariables(html) {
|
|
|
8803
9422
|
if (html.length > MAX_HTML_SIZE) {
|
|
8804
9423
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
8805
9424
|
}
|
|
8806
|
-
const $ =
|
|
9425
|
+
const $ = cheerio11.load(html);
|
|
8807
9426
|
return checkTemplateVariablesFromDom($);
|
|
8808
9427
|
}
|
|
8809
9428
|
|
|
8810
9429
|
// src/audit.ts
|
|
8811
|
-
import * as
|
|
9430
|
+
import * as cheerio12 from "cheerio";
|
|
8812
9431
|
function auditEmail(html, options) {
|
|
8813
9432
|
var _a;
|
|
8814
9433
|
if (!html || !html.trim()) {
|
|
@@ -8828,7 +9447,7 @@ function auditEmail(html, options) {
|
|
|
8828
9447
|
}
|
|
8829
9448
|
const framework = options == null ? void 0 : options.framework;
|
|
8830
9449
|
const skip = new Set((_a = options == null ? void 0 : options.skip) != null ? _a : []);
|
|
8831
|
-
const $ =
|
|
9450
|
+
const $ = cheerio12.load(html);
|
|
8832
9451
|
const warnings = skip.has("compatibility") ? [] : analyzeEmailFromDom($, framework);
|
|
8833
9452
|
const scores = skip.has("compatibility") ? {} : generateCompatibilityScore(warnings);
|
|
8834
9453
|
const spam = skip.has("spam") ? EMPTY_SPAM : analyzeSpamFromDom($, options == null ? void 0 : options.spam);
|
|
@@ -8841,8 +9460,53 @@ function auditEmail(html, options) {
|
|
|
8841
9460
|
return { compatibility: { warnings, scores }, spam, links, accessibility, images, inboxPreview, size, templateVariables };
|
|
8842
9461
|
}
|
|
8843
9462
|
|
|
9463
|
+
// src/plain-text.ts
|
|
9464
|
+
import * as cheerio13 from "cheerio";
|
|
9465
|
+
function toPlainText(html) {
|
|
9466
|
+
const $ = cheerio13.load(html);
|
|
9467
|
+
$("style, script").remove();
|
|
9468
|
+
$("[data-skip-in-text='true']").remove();
|
|
9469
|
+
$("hr").replaceWith("\n---\n");
|
|
9470
|
+
$("br").replaceWith("\n");
|
|
9471
|
+
$("img").each((_, el) => {
|
|
9472
|
+
const alt = $(el).attr("alt");
|
|
9473
|
+
$(el).replaceWith(alt ? alt : "");
|
|
9474
|
+
});
|
|
9475
|
+
$("a").each((_, el) => {
|
|
9476
|
+
const $el = $(el);
|
|
9477
|
+
const href = $el.attr("href") || "";
|
|
9478
|
+
const text2 = $el.text().trim();
|
|
9479
|
+
if (!href || href === text2) {
|
|
9480
|
+
$el.replaceWith(text2 || href);
|
|
9481
|
+
} else if (!text2) {
|
|
9482
|
+
$el.replaceWith(href);
|
|
9483
|
+
} else {
|
|
9484
|
+
$el.replaceWith(`${text2} (${href})`);
|
|
9485
|
+
}
|
|
9486
|
+
});
|
|
9487
|
+
$("li").each((_, el) => {
|
|
9488
|
+
const $el = $(el);
|
|
9489
|
+
const text2 = $el.text().trim();
|
|
9490
|
+
$el.replaceWith(`
|
|
9491
|
+
- ${text2}
|
|
9492
|
+
`);
|
|
9493
|
+
});
|
|
9494
|
+
const blockTags = "p, div, h1, h2, h3, h4, h5, h6, tr, blockquote, ul, ol";
|
|
9495
|
+
$(blockTags).each((_, el) => {
|
|
9496
|
+
const $el = $(el);
|
|
9497
|
+
$el.prepend("\n\n");
|
|
9498
|
+
$el.append("\n\n");
|
|
9499
|
+
});
|
|
9500
|
+
let text = $("body").text();
|
|
9501
|
+
if (!text.trim()) {
|
|
9502
|
+
text = $.root().text();
|
|
9503
|
+
}
|
|
9504
|
+
text = text.replace(/\n{3,}/g, "\n\n");
|
|
9505
|
+
return text.trim();
|
|
9506
|
+
}
|
|
9507
|
+
|
|
8844
9508
|
// src/session.ts
|
|
8845
|
-
import * as
|
|
9509
|
+
import * as cheerio14 from "cheerio";
|
|
8846
9510
|
function createSession(html, options) {
|
|
8847
9511
|
if (!html || !html.trim()) {
|
|
8848
9512
|
const fw = options == null ? void 0 : options.framework;
|
|
@@ -8876,7 +9540,7 @@ function createSession(html, options) {
|
|
|
8876
9540
|
if (html.length > MAX_HTML_SIZE) {
|
|
8877
9541
|
throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
|
|
8878
9542
|
}
|
|
8879
|
-
const $ =
|
|
9543
|
+
const $ = cheerio14.load(html);
|
|
8880
9544
|
const framework = options == null ? void 0 : options.framework;
|
|
8881
9545
|
return {
|
|
8882
9546
|
html,
|
|
@@ -8958,9 +9622,11 @@ export {
|
|
|
8958
9622
|
contrastRatio,
|
|
8959
9623
|
createSession,
|
|
8960
9624
|
diffResults,
|
|
9625
|
+
downlevelCSS,
|
|
8961
9626
|
errorWarnings,
|
|
8962
9627
|
estimateAiFixTokens,
|
|
8963
9628
|
extractInboxPreview,
|
|
9629
|
+
formatRgb,
|
|
8964
9630
|
generateAiFix,
|
|
8965
9631
|
generateCompatibilityScore,
|
|
8966
9632
|
generateFixPrompt,
|
|
@@ -8972,6 +9638,7 @@ export {
|
|
|
8972
9638
|
relativeLuminance,
|
|
8973
9639
|
simulateDarkMode,
|
|
8974
9640
|
structuralWarnings,
|
|
9641
|
+
toPlainText,
|
|
8975
9642
|
transformForAllClients,
|
|
8976
9643
|
transformForClient,
|
|
8977
9644
|
validateLinks,
|