@emailens/engine 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -2
- package/dist/index.d.ts +175 -15
- package/dist/index.js +588 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
2
4
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
6
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
@@ -14,6 +16,19 @@ var __spreadValues = (a, b) => {
|
|
|
14
16
|
}
|
|
15
17
|
return a;
|
|
16
18
|
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
var __objRest = (source, exclude) => {
|
|
21
|
+
var target = {};
|
|
22
|
+
for (var prop in source)
|
|
23
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
24
|
+
target[prop] = source[prop];
|
|
25
|
+
if (source != null && __getOwnPropSymbols)
|
|
26
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
27
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
28
|
+
target[prop] = source[prop];
|
|
29
|
+
}
|
|
30
|
+
return target;
|
|
31
|
+
};
|
|
17
32
|
|
|
18
33
|
// src/clients.ts
|
|
19
34
|
var EMAIL_CLIENTS = [
|
|
@@ -730,6 +745,189 @@ var CSS_SUPPORT = {
|
|
|
730
745
|
"superhuman": "unsupported"
|
|
731
746
|
// Superhuman strips forms for security
|
|
732
747
|
},
|
|
748
|
+
// --- Text Wrapping ---
|
|
749
|
+
"word-break": {
|
|
750
|
+
"gmail-web": "supported",
|
|
751
|
+
"gmail-android": "supported",
|
|
752
|
+
"gmail-ios": "supported",
|
|
753
|
+
"outlook-web": "supported",
|
|
754
|
+
"outlook-windows": "unsupported",
|
|
755
|
+
// Word engine ignores word-break entirely
|
|
756
|
+
"apple-mail-macos": "supported",
|
|
757
|
+
"apple-mail-ios": "supported",
|
|
758
|
+
"yahoo-mail": "partial",
|
|
759
|
+
// break-all partially works; break-word unreliable
|
|
760
|
+
"samsung-mail": "supported",
|
|
761
|
+
"thunderbird": "supported",
|
|
762
|
+
"hey-mail": "supported",
|
|
763
|
+
"superhuman": "supported"
|
|
764
|
+
},
|
|
765
|
+
"overflow-wrap": {
|
|
766
|
+
"gmail-web": "supported",
|
|
767
|
+
"gmail-android": "supported",
|
|
768
|
+
"gmail-ios": "supported",
|
|
769
|
+
"outlook-web": "supported",
|
|
770
|
+
"outlook-windows": "unsupported",
|
|
771
|
+
// Word engine ignores overflow-wrap
|
|
772
|
+
"apple-mail-macos": "supported",
|
|
773
|
+
"apple-mail-ios": "supported",
|
|
774
|
+
"yahoo-mail": "partial",
|
|
775
|
+
// inconsistent support
|
|
776
|
+
"samsung-mail": "supported",
|
|
777
|
+
"thunderbird": "supported",
|
|
778
|
+
"hey-mail": "supported",
|
|
779
|
+
"superhuman": "supported"
|
|
780
|
+
},
|
|
781
|
+
"white-space": {
|
|
782
|
+
"gmail-web": "supported",
|
|
783
|
+
"gmail-android": "supported",
|
|
784
|
+
"gmail-ios": "supported",
|
|
785
|
+
"outlook-web": "supported",
|
|
786
|
+
"outlook-windows": "partial",
|
|
787
|
+
// only normal and nowrap
|
|
788
|
+
"apple-mail-macos": "supported",
|
|
789
|
+
"apple-mail-ios": "supported",
|
|
790
|
+
"yahoo-mail": "supported",
|
|
791
|
+
"samsung-mail": "supported",
|
|
792
|
+
"thunderbird": "supported",
|
|
793
|
+
"hey-mail": "supported",
|
|
794
|
+
"superhuman": "supported"
|
|
795
|
+
},
|
|
796
|
+
"text-overflow": {
|
|
797
|
+
"gmail-web": "unsupported",
|
|
798
|
+
// Gmail strips overflow, so text-overflow is useless
|
|
799
|
+
"gmail-android": "unsupported",
|
|
800
|
+
"gmail-ios": "unsupported",
|
|
801
|
+
"outlook-web": "supported",
|
|
802
|
+
"outlook-windows": "unsupported",
|
|
803
|
+
"apple-mail-macos": "supported",
|
|
804
|
+
"apple-mail-ios": "supported",
|
|
805
|
+
"yahoo-mail": "partial",
|
|
806
|
+
"samsung-mail": "supported",
|
|
807
|
+
"thunderbird": "supported",
|
|
808
|
+
"hey-mail": "supported",
|
|
809
|
+
"superhuman": "supported"
|
|
810
|
+
},
|
|
811
|
+
// --- Table Layout ---
|
|
812
|
+
"vertical-align": {
|
|
813
|
+
"gmail-web": "supported",
|
|
814
|
+
"gmail-android": "supported",
|
|
815
|
+
"gmail-ios": "supported",
|
|
816
|
+
"outlook-web": "supported",
|
|
817
|
+
"outlook-windows": "partial",
|
|
818
|
+
// only on <td> elements via valign attribute
|
|
819
|
+
"apple-mail-macos": "supported",
|
|
820
|
+
"apple-mail-ios": "supported",
|
|
821
|
+
"yahoo-mail": "supported",
|
|
822
|
+
"samsung-mail": "supported",
|
|
823
|
+
"thunderbird": "supported",
|
|
824
|
+
"hey-mail": "supported",
|
|
825
|
+
"superhuman": "supported"
|
|
826
|
+
},
|
|
827
|
+
"border-spacing": {
|
|
828
|
+
"gmail-web": "supported",
|
|
829
|
+
"gmail-android": "supported",
|
|
830
|
+
"gmail-ios": "supported",
|
|
831
|
+
"outlook-web": "supported",
|
|
832
|
+
"outlook-windows": "unsupported",
|
|
833
|
+
// use cellspacing attribute instead
|
|
834
|
+
"apple-mail-macos": "supported",
|
|
835
|
+
"apple-mail-ios": "supported",
|
|
836
|
+
"yahoo-mail": "supported",
|
|
837
|
+
"samsung-mail": "supported",
|
|
838
|
+
"thunderbird": "supported",
|
|
839
|
+
"hey-mail": "supported",
|
|
840
|
+
"superhuman": "supported"
|
|
841
|
+
},
|
|
842
|
+
// --- Sizing ---
|
|
843
|
+
"min-width": {
|
|
844
|
+
"gmail-web": "supported",
|
|
845
|
+
"gmail-android": "supported",
|
|
846
|
+
"gmail-ios": "supported",
|
|
847
|
+
"outlook-web": "supported",
|
|
848
|
+
"outlook-windows": "unsupported",
|
|
849
|
+
// Word engine ignores min-width
|
|
850
|
+
"apple-mail-macos": "supported",
|
|
851
|
+
"apple-mail-ios": "supported",
|
|
852
|
+
"yahoo-mail": "supported",
|
|
853
|
+
"samsung-mail": "supported",
|
|
854
|
+
"thunderbird": "supported",
|
|
855
|
+
"hey-mail": "supported",
|
|
856
|
+
"superhuman": "supported"
|
|
857
|
+
},
|
|
858
|
+
"min-height": {
|
|
859
|
+
"gmail-web": "supported",
|
|
860
|
+
"gmail-android": "supported",
|
|
861
|
+
"gmail-ios": "supported",
|
|
862
|
+
"outlook-web": "supported",
|
|
863
|
+
"outlook-windows": "unsupported",
|
|
864
|
+
// Word engine ignores min-height
|
|
865
|
+
"apple-mail-macos": "supported",
|
|
866
|
+
"apple-mail-ios": "supported",
|
|
867
|
+
"yahoo-mail": "supported",
|
|
868
|
+
"samsung-mail": "supported",
|
|
869
|
+
"thunderbird": "supported",
|
|
870
|
+
"hey-mail": "supported",
|
|
871
|
+
"superhuman": "supported"
|
|
872
|
+
},
|
|
873
|
+
"max-height": {
|
|
874
|
+
"gmail-web": "supported",
|
|
875
|
+
"gmail-android": "supported",
|
|
876
|
+
"gmail-ios": "supported",
|
|
877
|
+
"outlook-web": "supported",
|
|
878
|
+
"outlook-windows": "unsupported",
|
|
879
|
+
"apple-mail-macos": "supported",
|
|
880
|
+
"apple-mail-ios": "supported",
|
|
881
|
+
"yahoo-mail": "supported",
|
|
882
|
+
"samsung-mail": "supported",
|
|
883
|
+
"thunderbird": "supported",
|
|
884
|
+
"hey-mail": "supported",
|
|
885
|
+
"superhuman": "supported"
|
|
886
|
+
},
|
|
887
|
+
// --- Shadows ---
|
|
888
|
+
"text-shadow": {
|
|
889
|
+
"gmail-web": "unsupported",
|
|
890
|
+
"gmail-android": "unsupported",
|
|
891
|
+
"gmail-ios": "unsupported",
|
|
892
|
+
"outlook-web": "supported",
|
|
893
|
+
"outlook-windows": "unsupported",
|
|
894
|
+
"apple-mail-macos": "supported",
|
|
895
|
+
"apple-mail-ios": "supported",
|
|
896
|
+
"yahoo-mail": "unsupported",
|
|
897
|
+
"samsung-mail": "supported",
|
|
898
|
+
"thunderbird": "supported",
|
|
899
|
+
"hey-mail": "supported",
|
|
900
|
+
"superhuman": "supported"
|
|
901
|
+
},
|
|
902
|
+
// --- Background Sub-properties ---
|
|
903
|
+
"background-size": {
|
|
904
|
+
"gmail-web": "supported",
|
|
905
|
+
"gmail-android": "supported",
|
|
906
|
+
"gmail-ios": "supported",
|
|
907
|
+
"outlook-web": "supported",
|
|
908
|
+
"outlook-windows": "unsupported",
|
|
909
|
+
"apple-mail-macos": "supported",
|
|
910
|
+
"apple-mail-ios": "supported",
|
|
911
|
+
"yahoo-mail": "partial",
|
|
912
|
+
"samsung-mail": "supported",
|
|
913
|
+
"thunderbird": "supported",
|
|
914
|
+
"hey-mail": "supported",
|
|
915
|
+
"superhuman": "supported"
|
|
916
|
+
},
|
|
917
|
+
"background-position": {
|
|
918
|
+
"gmail-web": "supported",
|
|
919
|
+
"gmail-android": "supported",
|
|
920
|
+
"gmail-ios": "supported",
|
|
921
|
+
"outlook-web": "supported",
|
|
922
|
+
"outlook-windows": "unsupported",
|
|
923
|
+
"apple-mail-macos": "supported",
|
|
924
|
+
"apple-mail-ios": "supported",
|
|
925
|
+
"yahoo-mail": "partial",
|
|
926
|
+
"samsung-mail": "supported",
|
|
927
|
+
"thunderbird": "supported",
|
|
928
|
+
"hey-mail": "supported",
|
|
929
|
+
"superhuman": "supported"
|
|
930
|
+
},
|
|
733
931
|
// --- Misc ---
|
|
734
932
|
"opacity": {
|
|
735
933
|
"gmail-web": "unsupported",
|
|
@@ -822,6 +1020,8 @@ var OUTLOOK_WORD_UNSUPPORTED = /* @__PURE__ */ new Set([
|
|
|
822
1020
|
"text-shadow",
|
|
823
1021
|
"max-width",
|
|
824
1022
|
"max-height",
|
|
1023
|
+
"min-width",
|
|
1024
|
+
"min-height",
|
|
825
1025
|
"float",
|
|
826
1026
|
"position",
|
|
827
1027
|
"display",
|
|
@@ -834,7 +1034,30 @@ var OUTLOOK_WORD_UNSUPPORTED = /* @__PURE__ */ new Set([
|
|
|
834
1034
|
"background-position",
|
|
835
1035
|
"box-sizing",
|
|
836
1036
|
"object-fit",
|
|
837
|
-
"gap"
|
|
1037
|
+
"gap",
|
|
1038
|
+
"word-break",
|
|
1039
|
+
"overflow-wrap",
|
|
1040
|
+
"text-overflow",
|
|
1041
|
+
"border-spacing"
|
|
1042
|
+
]);
|
|
1043
|
+
var STRUCTURAL_FIX_PROPERTIES = /* @__PURE__ */ new Set([
|
|
1044
|
+
"display:flex",
|
|
1045
|
+
"display:grid",
|
|
1046
|
+
"word-break",
|
|
1047
|
+
"overflow-wrap",
|
|
1048
|
+
"text-overflow",
|
|
1049
|
+
"position",
|
|
1050
|
+
"float",
|
|
1051
|
+
"gap",
|
|
1052
|
+
"max-width",
|
|
1053
|
+
"border-radius",
|
|
1054
|
+
"background-image",
|
|
1055
|
+
"background-size",
|
|
1056
|
+
"background-position",
|
|
1057
|
+
"<svg>",
|
|
1058
|
+
"<video>",
|
|
1059
|
+
"<form>",
|
|
1060
|
+
"object-fit"
|
|
838
1061
|
]);
|
|
839
1062
|
|
|
840
1063
|
// src/fix-snippets.ts
|
|
@@ -1340,6 +1563,146 @@ h1 {
|
|
|
1340
1563
|
<div style="font-size: 0; max-height: 0; overflow: hidden;
|
|
1341
1564
|
mso-hide: all;" aria-hidden="true">
|
|
1342
1565
|
Preheader text
|
|
1566
|
+
</div>`
|
|
1567
|
+
},
|
|
1568
|
+
// ── word-break → table cell wrapping ─────────────────────────────────────
|
|
1569
|
+
"word-break": {
|
|
1570
|
+
language: "html",
|
|
1571
|
+
description: "Wrap long text in a table cell to force line breaks without word-break",
|
|
1572
|
+
before: `<span style="word-break: break-all;">
|
|
1573
|
+
https://example.com/very/long/url?token=abc123def456
|
|
1574
|
+
</span>`,
|
|
1575
|
+
after: `<!-- Table cells force text wrapping in all clients including Outlook -->
|
|
1576
|
+
<table role="presentation" width="100%" cellpadding="0"
|
|
1577
|
+
cellspacing="0" border="0">
|
|
1578
|
+
<tr>
|
|
1579
|
+
<td style="word-break: break-all; overflow-wrap: break-word;
|
|
1580
|
+
word-wrap: break-word;">
|
|
1581
|
+
https://example.com/very/long/url?token=abc123def456
|
|
1582
|
+
</td>
|
|
1583
|
+
</tr>
|
|
1584
|
+
</table>`
|
|
1585
|
+
},
|
|
1586
|
+
"word-break::jsx": {
|
|
1587
|
+
language: "jsx",
|
|
1588
|
+
description: "Wrap long text in a table cell for Outlook-safe word breaking",
|
|
1589
|
+
before: `<span style={{ wordBreak: "break-all" }}>{url}</span>`,
|
|
1590
|
+
after: `{/* Table cells force text wrapping in Outlook and Yahoo */}
|
|
1591
|
+
<table width="100%" cellPadding={0} cellSpacing={0}
|
|
1592
|
+
role="presentation" style={{ borderCollapse: "collapse" }}>
|
|
1593
|
+
<tr>
|
|
1594
|
+
<td style={{
|
|
1595
|
+
wordBreak: "break-all" as const,
|
|
1596
|
+
overflowWrap: "break-word" as const,
|
|
1597
|
+
wordWrap: "break-word" as const,
|
|
1598
|
+
}}>
|
|
1599
|
+
{url}
|
|
1600
|
+
</td>
|
|
1601
|
+
</tr>
|
|
1602
|
+
</table>`
|
|
1603
|
+
},
|
|
1604
|
+
"word-break::mjml": {
|
|
1605
|
+
language: "mjml",
|
|
1606
|
+
description: "MJML renders text in table cells by default \u2014 word-break works via mj-text",
|
|
1607
|
+
before: `<mj-text>
|
|
1608
|
+
<span style="word-break: break-all;">Long URL here</span>
|
|
1609
|
+
</mj-text>`,
|
|
1610
|
+
after: `<!-- mj-text already renders inside a <td>, so add word-break
|
|
1611
|
+
to the mj-text css-class or inline style -->
|
|
1612
|
+
<mj-text css-class="break-words"
|
|
1613
|
+
padding="0">
|
|
1614
|
+
Long URL here
|
|
1615
|
+
</mj-text>
|
|
1616
|
+
<mj-style>
|
|
1617
|
+
.break-words td { word-break: break-all; word-wrap: break-word; }
|
|
1618
|
+
</mj-style>`
|
|
1619
|
+
},
|
|
1620
|
+
// ── overflow-wrap → table cell wrapping ────────────────────────────────
|
|
1621
|
+
"overflow-wrap": {
|
|
1622
|
+
language: "html",
|
|
1623
|
+
description: "Use a table cell to force word wrapping without overflow-wrap",
|
|
1624
|
+
before: `<p style="overflow-wrap: break-word;">
|
|
1625
|
+
https://example.com/very/long/url?token=abc123def456
|
|
1626
|
+
</p>`,
|
|
1627
|
+
after: `<table role="presentation" width="100%" cellpadding="0"
|
|
1628
|
+
cellspacing="0" border="0">
|
|
1629
|
+
<tr>
|
|
1630
|
+
<td style="overflow-wrap: break-word; word-wrap: break-word;
|
|
1631
|
+
word-break: break-all;">
|
|
1632
|
+
https://example.com/very/long/url?token=abc123def456
|
|
1633
|
+
</td>
|
|
1634
|
+
</tr>
|
|
1635
|
+
</table>`
|
|
1636
|
+
},
|
|
1637
|
+
"overflow-wrap::jsx": {
|
|
1638
|
+
language: "jsx",
|
|
1639
|
+
description: "Wrap text in a table cell for Outlook-safe overflow wrapping",
|
|
1640
|
+
before: `<p style={{ overflowWrap: "break-word" }}>{longText}</p>`,
|
|
1641
|
+
after: `<table width="100%" cellPadding={0} cellSpacing={0}
|
|
1642
|
+
role="presentation" style={{ borderCollapse: "collapse" }}>
|
|
1643
|
+
<tr>
|
|
1644
|
+
<td style={{
|
|
1645
|
+
overflowWrap: "break-word" as const,
|
|
1646
|
+
wordWrap: "break-word" as const,
|
|
1647
|
+
wordBreak: "break-all" as const,
|
|
1648
|
+
}}>
|
|
1649
|
+
{longText}
|
|
1650
|
+
</td>
|
|
1651
|
+
</tr>
|
|
1652
|
+
</table>`
|
|
1653
|
+
},
|
|
1654
|
+
// ── text-shadow → border/font-weight alternative ───────────────────────
|
|
1655
|
+
"text-shadow": {
|
|
1656
|
+
language: "css",
|
|
1657
|
+
description: "Use font-weight or border-bottom as alternatives to text-shadow",
|
|
1658
|
+
before: `.glow {
|
|
1659
|
+
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
|
|
1660
|
+
}`,
|
|
1661
|
+
after: `.glow {
|
|
1662
|
+
/* text-shadow is not supported in Gmail, Outlook, Yahoo.
|
|
1663
|
+
Use font-weight or letter-spacing for emphasis instead. */
|
|
1664
|
+
font-weight: bold;
|
|
1665
|
+
letter-spacing: 0.5px;
|
|
1666
|
+
}`
|
|
1667
|
+
},
|
|
1668
|
+
// ── border-spacing → cellspacing attribute ─────────────────────────────
|
|
1669
|
+
"border-spacing": {
|
|
1670
|
+
language: "html",
|
|
1671
|
+
description: "Use the cellspacing HTML attribute instead of border-spacing CSS",
|
|
1672
|
+
before: `<table style="border-spacing: 8px; border-collapse: separate;">
|
|
1673
|
+
<tr><td>Cell</td></tr>
|
|
1674
|
+
</table>`,
|
|
1675
|
+
after: `<table cellspacing="8" style="border-collapse: separate;">
|
|
1676
|
+
<tr><td>Cell</td></tr>
|
|
1677
|
+
</table>`
|
|
1678
|
+
},
|
|
1679
|
+
// ── min-width → fixed width ────────────────────────────────────────────
|
|
1680
|
+
"min-width": {
|
|
1681
|
+
language: "html",
|
|
1682
|
+
description: "Use a fixed width instead of min-width for Outlook compatibility",
|
|
1683
|
+
before: `<td style="min-width: 200px;">Content</td>`,
|
|
1684
|
+
after: `<!-- Outlook ignores min-width. Use a fixed width or a spacer. -->
|
|
1685
|
+
<td width="200" style="width: 200px;">Content</td>`
|
|
1686
|
+
},
|
|
1687
|
+
// ── min-height → fixed height ──────────────────────────────────────────
|
|
1688
|
+
"min-height": {
|
|
1689
|
+
language: "html",
|
|
1690
|
+
description: "Use a fixed height or spacer instead of min-height",
|
|
1691
|
+
before: `<td style="min-height: 100px;">Content</td>`,
|
|
1692
|
+
after: `<!-- Outlook ignores min-height. Use height or a spacer image. -->
|
|
1693
|
+
<td height="100" style="height: 100px;">Content</td>`
|
|
1694
|
+
},
|
|
1695
|
+
// ── max-height → fixed height ──────────────────────────────────────────
|
|
1696
|
+
"max-height": {
|
|
1697
|
+
language: "html",
|
|
1698
|
+
description: "Outlook ignores max-height \u2014 truncate content server-side",
|
|
1699
|
+
before: `<div style="max-height: 200px; overflow: hidden;">
|
|
1700
|
+
Long content...
|
|
1701
|
+
</div>`,
|
|
1702
|
+
after: `<!-- Outlook ignores max-height. Truncate content server-side. -->
|
|
1703
|
+
<div style="height: 200px;">
|
|
1704
|
+
Shortened content...
|
|
1705
|
+
<a href="https://example.com/full">Read more</a>
|
|
1343
1706
|
</div>`
|
|
1344
1707
|
},
|
|
1345
1708
|
// ── REACT EMAIL (jsx) framework-specific fixes ────────────────────────────
|
|
@@ -2074,6 +2437,8 @@ function getClientPrefix(clientId) {
|
|
|
2074
2437
|
if (clientId.startsWith("outlook")) return null;
|
|
2075
2438
|
if (clientId.startsWith("gmail")) return "gmail";
|
|
2076
2439
|
if (clientId.startsWith("apple-mail")) return "apple";
|
|
2440
|
+
if (clientId === "yahoo-mail") return "yahoo";
|
|
2441
|
+
if (clientId === "samsung-mail") return "samsung";
|
|
2077
2442
|
return null;
|
|
2078
2443
|
}
|
|
2079
2444
|
var SUGGESTION_DATABASE = {
|
|
@@ -2177,6 +2542,31 @@ var SUGGESTION_DATABASE = {
|
|
|
2177
2542
|
"opacity::jsx": "Use solid colors. Opacity is not supported in many email clients.",
|
|
2178
2543
|
"opacity::mjml": "Use solid colors. Most email clients don't support opacity.",
|
|
2179
2544
|
"opacity::maizzle": "Use solid Tailwind color classes instead of opacity.",
|
|
2545
|
+
// ── word-break ──────────────────────────────────────────────────────
|
|
2546
|
+
"word-break": "Wrap long text in a <table><td> to force wrapping in clients that don't support word-break.",
|
|
2547
|
+
"word-break::outlook": "Outlook's Word engine ignores word-break. Place text inside a <td> with a constrained width \u2014 tables always wrap.",
|
|
2548
|
+
"word-break::jsx": "Wrap long text in a <table><tr><td> element. Outlook ignores wordBreak but respects table cell widths.",
|
|
2549
|
+
"word-break::mjml": "mj-text renders inside a <td>, which helps. Add word-wrap: break-word to the td via mj-style.",
|
|
2550
|
+
// ── overflow-wrap ──────────────────────────────────────────────────
|
|
2551
|
+
"overflow-wrap": "Wrap text in a <table><td> to force wrapping. overflow-wrap is ignored by Outlook and unreliable in Yahoo.",
|
|
2552
|
+
"overflow-wrap::jsx": "Wrap text in a <table><tr><td> element. Outlook ignores overflowWrap but respects table cell widths.",
|
|
2553
|
+
// ── white-space ────────────────────────────────────────────────────
|
|
2554
|
+
"white-space": "Outlook only supports 'normal' and 'nowrap'. Use for non-breaking spaces.",
|
|
2555
|
+
// ── text-overflow ──────────────────────────────────────────────────
|
|
2556
|
+
"text-overflow": "text-overflow requires overflow:hidden which is stripped by Gmail. Truncate content server-side.",
|
|
2557
|
+
// ── vertical-align ─────────────────────────────────────────────────
|
|
2558
|
+
"vertical-align": 'Use the valign HTML attribute on <td> elements for Outlook (e.g., valign="top").',
|
|
2559
|
+
// ── border-spacing ─────────────────────────────────────────────────
|
|
2560
|
+
"border-spacing": 'Use the cellspacing HTML attribute instead (e.g., <table cellspacing="8">).',
|
|
2561
|
+
// ── min-width / min-height ─────────────────────────────────────────
|
|
2562
|
+
"min-width": "Outlook ignores min-width. Use a fixed width attribute on <td> or <table>.",
|
|
2563
|
+
"min-height": "Outlook ignores min-height. Use a fixed height or a spacer image.",
|
|
2564
|
+
"max-height": "Outlook ignores max-height. Truncate content server-side or use a fixed height.",
|
|
2565
|
+
// ── text-shadow ────────────────────────────────────────────────────
|
|
2566
|
+
"text-shadow": "text-shadow is stripped by Gmail, Outlook, and Yahoo. Use font-weight for emphasis.",
|
|
2567
|
+
// ── background-size / background-position ──────────────────────────
|
|
2568
|
+
"background-size": "Not supported in many clients. Set image dimensions directly.",
|
|
2569
|
+
"background-position": "Not supported in many clients. Use VML for positioning.",
|
|
2180
2570
|
// ── Additional properties covered by transform helpers ────────────────
|
|
2181
2571
|
"overflow": "Content will always be visible. Design accordingly.",
|
|
2182
2572
|
"visibility": "Remove the element or use display:none as an alternative.",
|
|
@@ -2185,9 +2575,6 @@ var SUGGESTION_DATABASE = {
|
|
|
2185
2575
|
"transition": "CSS transitions are not supported in email.",
|
|
2186
2576
|
"box-sizing": "Account for padding in your width calculations (use padding on a nested element).",
|
|
2187
2577
|
"object-fit": "Use width/height attributes on <img> directly.",
|
|
2188
|
-
"max-height": "Use fixed height instead.",
|
|
2189
|
-
"background-size": "Not supported in many clients. Set image dimensions directly.",
|
|
2190
|
-
"background-position": "Not supported in many clients. Use VML for positioning.",
|
|
2191
2578
|
"display": "Use tables for layout in email clients."
|
|
2192
2579
|
};
|
|
2193
2580
|
function getSuggestion(property, clientId, framework) {
|
|
@@ -2329,7 +2716,9 @@ function makeWarning(base, prop, clientId, framework) {
|
|
|
2329
2716
|
const sug = getSuggestion(prop, clientId, framework);
|
|
2330
2717
|
const fix = getCodeFix(prop, clientId, framework);
|
|
2331
2718
|
const isFallback = framework && ((sug == null ? void 0 : sug.isGenericFallback) || fix && isCodeFixGenericFallback(prop, clientId, framework));
|
|
2332
|
-
return __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, base), sug ? { suggestion: sug.text } : {}), fix ? { fix } : {}), isFallback ? { fixIsGenericFallback: true } : {})
|
|
2719
|
+
return __spreadProps(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, base), sug ? { suggestion: sug.text } : {}), fix ? { fix } : {}), isFallback ? { fixIsGenericFallback: true } : {}), {
|
|
2720
|
+
fixType: STRUCTURAL_FIX_PROPERTIES.has(prop) ? "structural" : "css"
|
|
2721
|
+
});
|
|
2333
2722
|
}
|
|
2334
2723
|
function transformGmail(html, clientId, framework) {
|
|
2335
2724
|
const $ = cheerio.load(html);
|
|
@@ -2870,7 +3259,8 @@ function analyzeEmail(html, framework) {
|
|
|
2870
3259
|
property: "<style>",
|
|
2871
3260
|
message: `${client.name} strips <style> blocks. Styles must be inlined.`,
|
|
2872
3261
|
suggestion: sug.text,
|
|
2873
|
-
fix
|
|
3262
|
+
fix,
|
|
3263
|
+
fixType: getFixType("<style>")
|
|
2874
3264
|
}, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<style>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
2875
3265
|
} else if (support === "partial") {
|
|
2876
3266
|
const sug = getSuggestion("<style>:partial", client.id, framework);
|
|
@@ -2881,7 +3271,8 @@ function analyzeEmail(html, framework) {
|
|
|
2881
3271
|
property: "<style>",
|
|
2882
3272
|
message: `${client.name} has partial <style> support (head only, with limitations). Inline styles recommended.`,
|
|
2883
3273
|
suggestion: sug.text,
|
|
2884
|
-
fix
|
|
3274
|
+
fix,
|
|
3275
|
+
fixType: getFixType("<style>")
|
|
2885
3276
|
}, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<style>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
2886
3277
|
}
|
|
2887
3278
|
}
|
|
@@ -2898,7 +3289,8 @@ function analyzeEmail(html, framework) {
|
|
|
2898
3289
|
property: "<link>",
|
|
2899
3290
|
message: `${client.name} does not support external stylesheets.`,
|
|
2900
3291
|
suggestion: sug.text,
|
|
2901
|
-
fix
|
|
3292
|
+
fix,
|
|
3293
|
+
fixType: getFixType("<link>")
|
|
2902
3294
|
}, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<link>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
2903
3295
|
}
|
|
2904
3296
|
}
|
|
@@ -2915,7 +3307,8 @@ function analyzeEmail(html, framework) {
|
|
|
2915
3307
|
property: "<svg>",
|
|
2916
3308
|
message: `${client.name} does not support inline SVG.`,
|
|
2917
3309
|
suggestion: sug.text,
|
|
2918
|
-
fix
|
|
3310
|
+
fix,
|
|
3311
|
+
fixType: getFixType("<svg>")
|
|
2919
3312
|
}, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<svg>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
2920
3313
|
}
|
|
2921
3314
|
}
|
|
@@ -2932,7 +3325,8 @@ function analyzeEmail(html, framework) {
|
|
|
2932
3325
|
property: "<video>",
|
|
2933
3326
|
message: `${client.name} does not support <video> elements.`,
|
|
2934
3327
|
suggestion: sug.text,
|
|
2935
|
-
fix
|
|
3328
|
+
fix,
|
|
3329
|
+
fixType: getFixType("<video>")
|
|
2936
3330
|
}, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<video>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
2937
3331
|
}
|
|
2938
3332
|
}
|
|
@@ -2949,7 +3343,8 @@ function analyzeEmail(html, framework) {
|
|
|
2949
3343
|
property: "<form>",
|
|
2950
3344
|
message: `${client.name} strips form elements.`,
|
|
2951
3345
|
suggestion: sug.text,
|
|
2952
|
-
fix
|
|
3346
|
+
fix,
|
|
3347
|
+
fixType: getFixType("<form>")
|
|
2953
3348
|
}, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<form>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
2954
3349
|
}
|
|
2955
3350
|
}
|
|
@@ -2994,7 +3389,8 @@ function analyzeEmail(html, framework) {
|
|
|
2994
3389
|
property: "@font-face",
|
|
2995
3390
|
message: `${client.name} does not support web fonts (@font-face).`,
|
|
2996
3391
|
suggestion: sug.text,
|
|
2997
|
-
fix
|
|
3392
|
+
fix,
|
|
3393
|
+
fixType: getFixType("@font-face")
|
|
2998
3394
|
}, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("@font-face", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
2999
3395
|
}
|
|
3000
3396
|
}
|
|
@@ -3011,7 +3407,8 @@ function analyzeEmail(html, framework) {
|
|
|
3011
3407
|
property: "@media",
|
|
3012
3408
|
message: `${client.name} does not support @media queries.`,
|
|
3013
3409
|
suggestion: sug.text,
|
|
3014
|
-
fix
|
|
3410
|
+
fix,
|
|
3411
|
+
fixType: getFixType("@media")
|
|
3015
3412
|
}, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("@media", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
3016
3413
|
}
|
|
3017
3414
|
}
|
|
@@ -3050,7 +3447,8 @@ function analyzeEmail(html, framework) {
|
|
|
3050
3447
|
severity: "warning",
|
|
3051
3448
|
client: client.id,
|
|
3052
3449
|
property: prop,
|
|
3053
|
-
message: `${client.name} does not support "${prop}" in <style> blocks
|
|
3450
|
+
message: `${client.name} does not support "${prop}" in <style> blocks.`,
|
|
3451
|
+
fixType: getFixType(prop)
|
|
3054
3452
|
});
|
|
3055
3453
|
}
|
|
3056
3454
|
}
|
|
@@ -3064,9 +3462,13 @@ function analyzeEmail(html, framework) {
|
|
|
3064
3462
|
warnings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
3065
3463
|
return warnings;
|
|
3066
3464
|
}
|
|
3465
|
+
function getFixType(prop) {
|
|
3466
|
+
return STRUCTURAL_FIX_PROPERTIES.has(prop) ? "structural" : "css";
|
|
3467
|
+
}
|
|
3067
3468
|
function checkPropertySupport(prop, addWarning, framework) {
|
|
3068
3469
|
const supportData = CSS_SUPPORT[prop];
|
|
3069
3470
|
if (!supportData) return;
|
|
3471
|
+
const fixType = getFixType(prop);
|
|
3070
3472
|
for (const client of EMAIL_CLIENTS) {
|
|
3071
3473
|
const support = supportData[client.id] || "unknown";
|
|
3072
3474
|
if (support === "unsupported") {
|
|
@@ -3078,7 +3480,8 @@ function checkPropertySupport(prop, addWarning, framework) {
|
|
|
3078
3480
|
property: prop,
|
|
3079
3481
|
message: `${client.name} does not support "${prop}".`,
|
|
3080
3482
|
suggestion: sug.text,
|
|
3081
|
-
fix
|
|
3483
|
+
fix,
|
|
3484
|
+
fixType
|
|
3082
3485
|
}, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(prop, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
3083
3486
|
} else if (support === "partial") {
|
|
3084
3487
|
const sug = getSuggestion(prop, client.id, framework);
|
|
@@ -3089,7 +3492,8 @@ function checkPropertySupport(prop, addWarning, framework) {
|
|
|
3089
3492
|
property: prop,
|
|
3090
3493
|
message: `${client.name} has partial support for "${prop}".`,
|
|
3091
3494
|
suggestion: sug.text,
|
|
3092
|
-
fix
|
|
3495
|
+
fix,
|
|
3496
|
+
fixType
|
|
3093
3497
|
}, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(prop, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
|
|
3094
3498
|
}
|
|
3095
3499
|
}
|
|
@@ -3311,8 +3715,13 @@ ${originalHtml}
|
|
|
3311
3715
|
`;
|
|
3312
3716
|
for (const w of group) {
|
|
3313
3717
|
const clientName = (_f = (_e = getClient(w.client)) == null ? void 0 : _e.name) != null ? _f : w.client;
|
|
3718
|
+
const fixTypeLabel = w.fixType === "structural" ? " [STRUCTURAL]" : "";
|
|
3314
3719
|
issueSection += `
|
|
3315
|
-
- **${w.property}** (${clientName}): ${w.message}`;
|
|
3720
|
+
- **${w.property}** (${clientName})${fixTypeLabel}: ${w.message}`;
|
|
3721
|
+
if (w.fixType === "structural") {
|
|
3722
|
+
issueSection += `
|
|
3723
|
+
- **Fix type: structural** \u2014 CSS-only changes will NOT work. HTML restructuring required.`;
|
|
3724
|
+
}
|
|
3316
3725
|
if (w.suggestion) {
|
|
3317
3726
|
issueSection += `
|
|
3318
3727
|
- Suggestion: ${w.suggestion}`;
|
|
@@ -3343,15 +3752,177 @@ ${originalHtml}
|
|
|
3343
3752
|
);
|
|
3344
3753
|
return sections.join("\n\n");
|
|
3345
3754
|
}
|
|
3755
|
+
|
|
3756
|
+
// src/token-utils.ts
|
|
3757
|
+
var CHARS_PER_TOKEN = 3.5;
|
|
3758
|
+
var OUTPUT_RATIO = 1.3;
|
|
3759
|
+
var DEFAULT_SYSTEM_PROMPT_TOKENS = 250;
|
|
3760
|
+
async function estimateAiFixTokens(options) {
|
|
3761
|
+
const _a = options, {
|
|
3762
|
+
maxInputTokens = 16e3,
|
|
3763
|
+
tokenCounter,
|
|
3764
|
+
systemPromptTokens = DEFAULT_SYSTEM_PROMPT_TOKENS
|
|
3765
|
+
} = _a, rest = __objRest(_a, [
|
|
3766
|
+
"maxInputTokens",
|
|
3767
|
+
"tokenCounter",
|
|
3768
|
+
"systemPromptTokens"
|
|
3769
|
+
]);
|
|
3770
|
+
const effectiveMaxTokens = maxInputTokens - systemPromptTokens;
|
|
3771
|
+
const { warnings: finalWarnings, truncated, removed } = truncateWarnings(
|
|
3772
|
+
rest.warnings,
|
|
3773
|
+
rest.originalHtml,
|
|
3774
|
+
effectiveMaxTokens,
|
|
3775
|
+
rest.scope,
|
|
3776
|
+
rest.selectedClientId
|
|
3777
|
+
);
|
|
3778
|
+
const prompt = generateFixPrompt(__spreadProps(__spreadValues({}, rest), { warnings: finalWarnings }));
|
|
3779
|
+
const promptChars = prompt.length;
|
|
3780
|
+
let promptTokens;
|
|
3781
|
+
if (tokenCounter) {
|
|
3782
|
+
const count = tokenCounter(prompt);
|
|
3783
|
+
promptTokens = count instanceof Promise ? await count : count;
|
|
3784
|
+
} else {
|
|
3785
|
+
promptTokens = heuristicTokenCount(prompt);
|
|
3786
|
+
}
|
|
3787
|
+
const inputTokens = promptTokens + systemPromptTokens;
|
|
3788
|
+
const estimatedOutputTokens = Math.ceil(
|
|
3789
|
+
heuristicTokenCount(rest.originalHtml) * OUTPUT_RATIO
|
|
3790
|
+
);
|
|
3791
|
+
const structuralCount = finalWarnings.filter(
|
|
3792
|
+
(w) => w.fixType === "structural"
|
|
3793
|
+
).length;
|
|
3794
|
+
return {
|
|
3795
|
+
inputTokens,
|
|
3796
|
+
estimatedOutputTokens,
|
|
3797
|
+
promptCharacters: promptChars,
|
|
3798
|
+
htmlCharacters: rest.originalHtml.length,
|
|
3799
|
+
warningCount: finalWarnings.length,
|
|
3800
|
+
structuralCount,
|
|
3801
|
+
truncated,
|
|
3802
|
+
warningsRemoved: removed,
|
|
3803
|
+
warnings: finalWarnings
|
|
3804
|
+
};
|
|
3805
|
+
}
|
|
3806
|
+
function heuristicTokenCount(text) {
|
|
3807
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
3808
|
+
}
|
|
3809
|
+
function truncateWarnings(warnings, html, maxTokens, scope, selectedClientId) {
|
|
3810
|
+
const originalCount = warnings.length;
|
|
3811
|
+
const fullPromptEstimate = heuristicTokenCount(html) + heuristicTokenCount(
|
|
3812
|
+
JSON.stringify(warnings)
|
|
3813
|
+
) + 500;
|
|
3814
|
+
if (fullPromptEstimate <= maxTokens) {
|
|
3815
|
+
return { warnings, truncated: false, removed: 0 };
|
|
3816
|
+
}
|
|
3817
|
+
let result = [...warnings];
|
|
3818
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3819
|
+
result = result.filter((w) => {
|
|
3820
|
+
const key = `${w.property}:${w.severity}`;
|
|
3821
|
+
if (seen.has(key)) return false;
|
|
3822
|
+
seen.add(key);
|
|
3823
|
+
return true;
|
|
3824
|
+
});
|
|
3825
|
+
if (estimateFits(result, html, maxTokens)) {
|
|
3826
|
+
return { warnings: result, truncated: true, removed: originalCount - result.length };
|
|
3827
|
+
}
|
|
3828
|
+
result = result.filter((w) => w.severity !== "info");
|
|
3829
|
+
if (estimateFits(result, html, maxTokens)) {
|
|
3830
|
+
return { warnings: result, truncated: true, removed: originalCount - result.length };
|
|
3831
|
+
}
|
|
3832
|
+
result = result.filter((w) => w.fixType === "structural" || w.severity === "error");
|
|
3833
|
+
if (estimateFits(result, html, maxTokens)) {
|
|
3834
|
+
return { warnings: result, truncated: true, removed: originalCount - result.length };
|
|
3835
|
+
}
|
|
3836
|
+
result = result.map((w) => __spreadProps(__spreadValues({}, w), {
|
|
3837
|
+
fix: w.fix ? __spreadProps(__spreadValues({}, w.fix), {
|
|
3838
|
+
before: w.fix.before.length > 200 ? w.fix.before.slice(0, 200) + "\n/* ... truncated ... */" : w.fix.before,
|
|
3839
|
+
after: w.fix.after.length > 200 ? w.fix.after.slice(0, 200) + "\n/* ... truncated ... */" : w.fix.after
|
|
3840
|
+
}) : void 0
|
|
3841
|
+
}));
|
|
3842
|
+
return { warnings: result, truncated: true, removed: originalCount - result.length };
|
|
3843
|
+
}
|
|
3844
|
+
function estimateFits(warnings, html, maxTokens) {
|
|
3845
|
+
const estimate = heuristicTokenCount(html) + heuristicTokenCount(JSON.stringify(warnings)) + 500;
|
|
3846
|
+
return estimate <= maxTokens;
|
|
3847
|
+
}
|
|
3848
|
+
|
|
3849
|
+
// src/ai-fix.ts
|
|
3850
|
+
async function generateAiFix(options) {
|
|
3851
|
+
const _a = options, { provider, maxInputTokens = 16e3 } = _a, promptOptions = __objRest(_a, ["provider", "maxInputTokens"]);
|
|
3852
|
+
const estimate = await estimateAiFixTokens(__spreadProps(__spreadValues({}, promptOptions), {
|
|
3853
|
+
maxInputTokens
|
|
3854
|
+
}));
|
|
3855
|
+
const truncatedWarnings = estimate.warnings;
|
|
3856
|
+
const prompt = generateFixPrompt(__spreadProps(__spreadValues({}, promptOptions), { warnings: truncatedWarnings }));
|
|
3857
|
+
const structuralCount = countStructuralWarnings(
|
|
3858
|
+
truncatedWarnings,
|
|
3859
|
+
promptOptions.scope,
|
|
3860
|
+
promptOptions.selectedClientId
|
|
3861
|
+
);
|
|
3862
|
+
const _b = estimate, { warnings: _discarded } = _b, tokenEstimate = __objRest(_b, ["warnings"]);
|
|
3863
|
+
const response = await provider(prompt);
|
|
3864
|
+
const code = extractCode(response);
|
|
3865
|
+
return {
|
|
3866
|
+
code,
|
|
3867
|
+
prompt,
|
|
3868
|
+
targetedWarnings: tokenEstimate.warningCount,
|
|
3869
|
+
structuralCount,
|
|
3870
|
+
tokenEstimate
|
|
3871
|
+
};
|
|
3872
|
+
}
|
|
3873
|
+
var AI_FIX_SYSTEM_PROMPT = `You are an expert email developer specializing in cross-client HTML email compatibility. You fix emails to render correctly across all email clients.
|
|
3874
|
+
|
|
3875
|
+
Rules:
|
|
3876
|
+
- Return ONLY the fixed code inside a single code fence. No explanations before or after.
|
|
3877
|
+
- Preserve all existing content, text, links, and visual design.
|
|
3878
|
+
- For structural issues (fixType: "structural"), you MUST restructure the HTML \u2014 CSS-only changes will not work.
|
|
3879
|
+
- Common structural patterns:
|
|
3880
|
+
- word-break/overflow-wrap unsupported \u2192 wrap text in <table><tr><td> with constrained width
|
|
3881
|
+
- display:flex/grid \u2192 convert to <table> layout (match the original column count and proportions)
|
|
3882
|
+
- border-radius in Outlook \u2192 use VML <v:roundrect> with <!--[if mso]> conditionals
|
|
3883
|
+
- background-image in Outlook \u2192 use VML <v:rect> with <v:fill>
|
|
3884
|
+
- max-width in Outlook \u2192 wrap in <!--[if mso]><table width="N"> conditional
|
|
3885
|
+
- position:absolute \u2192 use <table> cells for layout
|
|
3886
|
+
- <svg> \u2192 replace with <img> pointing to a hosted PNG
|
|
3887
|
+
- For CSS-only issues (fixType: "css"), swap properties or add fallbacks.
|
|
3888
|
+
- Apply ALL fixes from the issues list \u2014 do not skip any.
|
|
3889
|
+
- Use the framework syntax specified (JSX/MJML/Maizzle/HTML).
|
|
3890
|
+
- For JSX: use camelCase style props, React Email components, and proper TypeScript types.
|
|
3891
|
+
- For MJML: use mj-* elements and attributes.
|
|
3892
|
+
- For Maizzle: use Tailwind CSS classes.`;
|
|
3893
|
+
function countStructuralWarnings(warnings, scope, selectedClientId) {
|
|
3894
|
+
const filtered = scope === "current" && selectedClientId ? warnings.filter((w) => w.client === selectedClientId) : warnings;
|
|
3895
|
+
return filtered.filter((w) => w.fixType === "structural").length;
|
|
3896
|
+
}
|
|
3897
|
+
function extractCode(response) {
|
|
3898
|
+
const fencePattern = /```(?:[\w]*)\n([\s\S]*?)```/g;
|
|
3899
|
+
let largest = null;
|
|
3900
|
+
let match;
|
|
3901
|
+
while ((match = fencePattern.exec(response)) !== null) {
|
|
3902
|
+
const content = match[1].trim();
|
|
3903
|
+
if (largest === null || content.length > largest.length) {
|
|
3904
|
+
largest = content;
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
if (largest !== null) {
|
|
3908
|
+
return largest;
|
|
3909
|
+
}
|
|
3910
|
+
return response.trim();
|
|
3911
|
+
}
|
|
3346
3912
|
export {
|
|
3913
|
+
AI_FIX_SYSTEM_PROMPT,
|
|
3347
3914
|
EMAIL_CLIENTS,
|
|
3915
|
+
STRUCTURAL_FIX_PROPERTIES,
|
|
3348
3916
|
analyzeEmail,
|
|
3349
3917
|
diffResults,
|
|
3918
|
+
estimateAiFixTokens,
|
|
3919
|
+
generateAiFix,
|
|
3350
3920
|
generateCompatibilityScore,
|
|
3351
3921
|
generateFixPrompt,
|
|
3352
3922
|
getClient,
|
|
3353
3923
|
getCodeFix,
|
|
3354
3924
|
getSuggestion,
|
|
3925
|
+
heuristicTokenCount,
|
|
3355
3926
|
simulateDarkMode,
|
|
3356
3927
|
transformForAllClients,
|
|
3357
3928
|
transformForClient
|