@hyper-elements/react 0.3.0 → 0.3.1
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/GUIDE.md +47 -28
- package/README.md +24 -5
- package/REFERENCE.md +487 -485
- package/dist/builder.cjs +795 -206
- package/dist/builder.cjs.map +1 -1
- package/dist/builder.js +795 -206
- package/dist/builder.js.map +1 -1
- package/dist/image.cjs +1 -1
- package/dist/image.cjs.map +1 -1
- package/dist/image.js +1 -1
- package/dist/image.js.map +1 -1
- package/dist/inspect.cjs +3601 -0
- package/dist/inspect.cjs.map +1 -0
- package/dist/inspect.d.cts +472 -0
- package/dist/inspect.d.ts +472 -0
- package/dist/inspect.js +3572 -0
- package/dist/inspect.js.map +1 -0
- package/dist/pdf.cjs +54 -9
- package/dist/pdf.cjs.map +1 -1
- package/dist/pdf.js +54 -9
- package/dist/pdf.js.map +1 -1
- package/dist/primitives.cjs +738 -142
- package/dist/primitives.cjs.map +1 -1
- package/dist/primitives.d.cts +558 -158
- package/dist/primitives.d.ts +558 -158
- package/dist/primitives.js +736 -143
- package/dist/primitives.js.map +1 -1
- package/package.json +10 -1
package/dist/builder.js
CHANGED
|
@@ -59,21 +59,26 @@ var Diagnostics = class {
|
|
|
59
59
|
constructor() {
|
|
60
60
|
this.list = [];
|
|
61
61
|
}
|
|
62
|
-
add(code, severity, message, path, nodeId) {
|
|
63
|
-
|
|
62
|
+
add(code, severity, message, path, nodeId, extra = {}) {
|
|
63
|
+
const d = { code, severity, message, path };
|
|
64
|
+
if (nodeId !== void 0) d.nodeId = nodeId;
|
|
65
|
+
if (extra.clients) d.clients = [...extra.clients];
|
|
66
|
+
if (extra.fix) d.fix = extra.fix;
|
|
67
|
+
if (extra.see) d.see = extra.see;
|
|
68
|
+
this.list.push(d);
|
|
64
69
|
}
|
|
65
70
|
error(code, message, path, nodeId) {
|
|
66
71
|
this.add(code, "error", message, path, nodeId);
|
|
67
72
|
}
|
|
68
|
-
warning(code, message, path, nodeId) {
|
|
69
|
-
this.add(code, "warning", message, path, nodeId);
|
|
73
|
+
warning(code, message, path, nodeId, extra) {
|
|
74
|
+
this.add(code, "warning", message, path, nodeId, extra);
|
|
70
75
|
}
|
|
71
76
|
get hasErrors() {
|
|
72
77
|
return this.list.some((d) => d.severity === "error");
|
|
73
78
|
}
|
|
74
79
|
};
|
|
75
80
|
function formatDiagnostics(diagnostics) {
|
|
76
|
-
return diagnostics.map((d) => `${d.severity} ${d.code} at ${d.path}: ${d.message}`).join("\n");
|
|
81
|
+
return diagnostics.map((d) => `${d.severity} ${d.code} at ${d.path}: ${d.message}${d.clients ? ` [${d.clients.join(", ")}]` : ""}${d.fix ? ` Fix: ${d.fix}` : ""}${d.see ? ` See: ${d.see}` : ""}`).join("\n");
|
|
77
82
|
}
|
|
78
83
|
var ElementsError = class extends Error {
|
|
79
84
|
constructor(diagnostics) {
|
|
@@ -84,6 +89,64 @@ ${formatDiagnostics(errors)}`);
|
|
|
84
89
|
this.diagnostics = diagnostics;
|
|
85
90
|
}
|
|
86
91
|
};
|
|
92
|
+
var captured = (against, wordsWithin4px, lineBreaks) => ({ elements: "0.3.0", against, captured: "2026-11-08", wordsWithin4px, lineBreaks });
|
|
93
|
+
var tables = (name) => ({ name, widths: {}, dark: false, approximate: true, measured: null });
|
|
94
|
+
var EMAIL_CLIENTS = {
|
|
95
|
+
outlook: { name: "Classic Outlook", widths: { desktop: 820 }, dark: false, approximate: true, measured: captured("Outlook 16.0.20527 for Windows, 96 DPI", 0.85, 0.983) },
|
|
96
|
+
"outlook-com": { name: "Outlook.com and new Outlook", widths: { desktop: 820 }, dark: false, approximate: false, measured: captured("Outlook on the web", 1, 1) },
|
|
97
|
+
"outlook-mac": tables("Outlook for Mac"),
|
|
98
|
+
"outlook-app": tables("Outlook app (iOS and Android)"),
|
|
99
|
+
gmail: { name: "Gmail", widths: { desktop: 820 }, dark: false, approximate: false, measured: captured("Gmail on the web", 1, 1) },
|
|
100
|
+
"gmail-app": { name: "Gmail app", widths: { phone: 375 }, dark: false, approximate: true, measured: null },
|
|
101
|
+
"gmail-app-other": { name: "Gmail app, another account", widths: { phone: 375 }, dark: false, approximate: true, measured: null },
|
|
102
|
+
"apple-mail": { name: "Apple Mail", widths: { desktop: 820 }, dark: true, approximate: false, measured: captured("Mail 16 on macOS 26", 0.999, 0.999) },
|
|
103
|
+
yahoo: tables("Yahoo Mail"),
|
|
104
|
+
aol: tables("AOL Mail"),
|
|
105
|
+
"samsung-email": tables("Samsung Email"),
|
|
106
|
+
thunderbird: tables("Thunderbird"),
|
|
107
|
+
protonmail: tables("Proton Mail"),
|
|
108
|
+
fastmail: tables("Fastmail"),
|
|
109
|
+
hey: tables("HEY"),
|
|
110
|
+
gmx: tables("GMX"),
|
|
111
|
+
"web-de": tables("WEB.DE"),
|
|
112
|
+
orange: tables("Orange"),
|
|
113
|
+
sfr: tables("SFR"),
|
|
114
|
+
laposte: tables("La Poste"),
|
|
115
|
+
"free-fr": tables("Free"),
|
|
116
|
+
"t-online-de": tables("T-Online"),
|
|
117
|
+
"mail-ru": tables("Mail.ru"),
|
|
118
|
+
ionos: tables("IONOS (1&1)"),
|
|
119
|
+
"wp-pl": tables("WP Poczta"),
|
|
120
|
+
rainloop: tables("RainLoop")
|
|
121
|
+
};
|
|
122
|
+
var DEFAULT_EMAIL_CLIENTS = ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail", "gmail-app", "gmail-app-other", "apple-mail", "yahoo", "aol", "samsung-email"];
|
|
123
|
+
var caniemail = (feature) => `https://www.caniemail.com/features/${feature}/`;
|
|
124
|
+
function checkEmailClients(clients) {
|
|
125
|
+
if (clients === void 0) return;
|
|
126
|
+
const unknown = Array.isArray(clients) ? clients.filter((c) => !Object.prototype.hasOwnProperty.call(EMAIL_CLIENTS, c)) : [clients];
|
|
127
|
+
if (unknown.length) throw new TypeError(`[Elements] emailClients takes a list of ${Object.keys(EMAIL_CLIENTS).join(", ")}, not ${unknown.map((c) => JSON.stringify(c)).join(", ")}`);
|
|
128
|
+
}
|
|
129
|
+
function forEmailClients(diagnostics, selection = DEFAULT_EMAIL_CLIENTS) {
|
|
130
|
+
const out = [];
|
|
131
|
+
const seen = /* @__PURE__ */ new Map();
|
|
132
|
+
for (const d of diagnostics) {
|
|
133
|
+
if (!d.clients) {
|
|
134
|
+
out.push(d);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const clients = d.clients.filter((c) => selection.includes(c));
|
|
138
|
+
if (!clients.length) continue;
|
|
139
|
+
const key = JSON.stringify([d.code, d.path, d.severity, d.message]);
|
|
140
|
+
const same42 = seen.get(key);
|
|
141
|
+
if (same42) same42.clients = [.../* @__PURE__ */ new Set([...same42.clients, ...clients])];
|
|
142
|
+
else {
|
|
143
|
+
const kept = { ...d, clients };
|
|
144
|
+
seen.set(key, kept);
|
|
145
|
+
out.push(kept);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
87
150
|
var ok = (value) => ({ ok: true, value });
|
|
88
151
|
var fail = (message) => ({ ok: false, message });
|
|
89
152
|
var show = (input) => {
|
|
@@ -669,6 +732,26 @@ function attributes() {
|
|
|
669
732
|
jsonSchema: () => ({ type: "object", additionalProperties: { type: "string" } })
|
|
670
733
|
});
|
|
671
734
|
}
|
|
735
|
+
var MIN_PHONE_WIDTH = 240;
|
|
736
|
+
function breakpoints() {
|
|
737
|
+
const accepts = `{ mobile }: the phone width in px (from ${MIN_PHONE_WIDTH})`;
|
|
738
|
+
return kind({
|
|
739
|
+
name: "breakpoints",
|
|
740
|
+
editor: "number",
|
|
741
|
+
accepts,
|
|
742
|
+
parse(input) {
|
|
743
|
+
if (!isObject(input)) return fail(`expected ${accepts}, not ${show(input)}`);
|
|
744
|
+
const unknown = Object.keys(input).find((key) => key !== "mobile");
|
|
745
|
+
if (unknown !== void 0) return fail(`${show(unknown)} isn't a breakpoint a Page has: it has mobile`);
|
|
746
|
+
const { mobile } = input;
|
|
747
|
+
if (mobile === void 0) return ok({});
|
|
748
|
+
if (typeof mobile !== "number" || !Number.isInteger(mobile) || mobile < MIN_PHONE_WIDTH) return fail(`mobile is the widest window its phone styles apply to, an integer from ${MIN_PHONE_WIDTH}, not ${show(mobile)}`);
|
|
749
|
+
return ok({ mobile });
|
|
750
|
+
},
|
|
751
|
+
empty: (value) => value.mobile === void 0,
|
|
752
|
+
jsonSchema: () => ({ type: "object", properties: { mobile: { type: "integer", minimum: MIN_PHONE_WIDTH } }, additionalProperties: false })
|
|
753
|
+
});
|
|
754
|
+
}
|
|
672
755
|
function linkParts(value) {
|
|
673
756
|
return typeof value === "string" ? { href: value } : value;
|
|
674
757
|
}
|
|
@@ -681,6 +764,54 @@ function defineComponent(definition) {
|
|
|
681
764
|
function prop(kind2, options) {
|
|
682
765
|
return { kind: kind2, ...options };
|
|
683
766
|
}
|
|
767
|
+
var MOBILE = {
|
|
768
|
+
name: "mobile",
|
|
769
|
+
on: "on phones",
|
|
770
|
+
for: "for phones",
|
|
771
|
+
roots: (root) => !!root?.responsive,
|
|
772
|
+
notAllowed: { code: "mobile/not-supported", message: "Phone styles are for an Email or a Page, which have a phone width" },
|
|
773
|
+
sheet: { media: "phone", selector: "&" },
|
|
774
|
+
allows: (spec) => !!spec.mobile,
|
|
775
|
+
support: { web: { level: "full", source: "writer" }, email: { level: "full", source: "writer" } },
|
|
776
|
+
styles: {
|
|
777
|
+
// The email's phone text styles are rules that override a size; "normal" isn't one.
|
|
778
|
+
lineHeight: { email: { level: "none", values: ["normal"], shows: "the line height it has on desktop", source: "writer" } }
|
|
779
|
+
},
|
|
780
|
+
outlookApplies: false,
|
|
781
|
+
hiddenKey: "mobile"
|
|
782
|
+
};
|
|
783
|
+
var HOVER = {
|
|
784
|
+
name: "hover",
|
|
785
|
+
on: "on hover",
|
|
786
|
+
for: "for hover",
|
|
787
|
+
// Keyboard focus gets the same styles as the pointer: a link or button reached by Tab shows its hover colors.
|
|
788
|
+
// A box around the link (an email Button's cell, its background) takes them while the link in it has focus.
|
|
789
|
+
sheet: { media: "all", selector: "&:hover,&:focus-visible", within: "&:hover,&:focus-within", level: "hover" },
|
|
790
|
+
allows: (_, key, definition) => !!definition.hover?.includes(key),
|
|
791
|
+
support: {
|
|
792
|
+
web: { level: "full", source: "css" },
|
|
793
|
+
// On type selectors only in Outlook.com and Outlook for Mac (Elements' are classes); the phone apps' results don't
|
|
794
|
+
// matter, as nothing hovers there.
|
|
795
|
+
email: {
|
|
796
|
+
level: "partial",
|
|
797
|
+
shows: "no change (where it shows, keyboard focus gets it too)",
|
|
798
|
+
source: "caniemail",
|
|
799
|
+
feature: "css-pseudo-class-hover",
|
|
800
|
+
clients: ["outlook", "outlook-com", "outlook-mac", "gmx", "web-de", "sfr", "laposte", "mail-ru"]
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
paged: (node, where) => `A hover isn't possible in ${where}: nothing points at it. The ${node.type} shows its own colors`,
|
|
804
|
+
outlookApplies: false
|
|
805
|
+
};
|
|
806
|
+
var CONDITIONS = [MOBILE, HOVER];
|
|
807
|
+
var CONDITION_NAMES = new Set(CONDITIONS.map((condition) => condition.name));
|
|
808
|
+
function blockOf(node, condition) {
|
|
809
|
+
const block2 = node[condition.name];
|
|
810
|
+
return block2 && Object.keys(block2).length ? block2 : void 0;
|
|
811
|
+
}
|
|
812
|
+
function shownAnywhere(node) {
|
|
813
|
+
return !(node.hidden?.mobile && node.hidden?.desktop);
|
|
814
|
+
}
|
|
684
815
|
var writer = { level: "full", source: "writer" };
|
|
685
816
|
var css = { level: "full", source: "css" };
|
|
686
817
|
var textStyle = {
|
|
@@ -689,7 +820,12 @@ var textStyle = {
|
|
|
689
820
|
fontFamily: { kind: fontFamily(), label: "Font", inherited: true, support: { email: { level: "full", source: "outlook-captures" }, web: css } },
|
|
690
821
|
// Word rounds sizes to half points (13px is 10pt): a fraction of a px.
|
|
691
822
|
fontSize: { kind: length(["px"], { min: 1 }), label: "Font size", inherited: true, mobile: true, support: { email: { level: "full", source: "outlook-captures" }, web: css } },
|
|
692
|
-
fontWeight: {
|
|
823
|
+
fontWeight: {
|
|
824
|
+
kind: fontWeight(),
|
|
825
|
+
label: "Font weight",
|
|
826
|
+
inherited: true,
|
|
827
|
+
support: { email: { level: "outlook", values: [100, 200, 300, 500], shows: "normal weight: Word draws only normal and bold (600 and over)", source: "caniemail", feature: "css-font-weight" }, web: css }
|
|
828
|
+
},
|
|
693
829
|
fontStyle: { kind: enumOf(["normal", "italic"]), label: "Font style", inherited: true, support: { email: { level: "full", source: "caniemail" }, web: css } },
|
|
694
830
|
// Written in px with mso-line-height-rule: exactly, which Word keeps (it drops unitless ones).
|
|
695
831
|
lineHeight: { kind: lineHeight(), label: "Line height", inherited: true, mobile: true, support: { email: { level: "full", source: "outlook-captures" }, web: css } },
|
|
@@ -701,8 +837,21 @@ var textStyle = {
|
|
|
701
837
|
support: { email: { level: "outlook", shows: "normal spacing: Word keeps letter spacing but doesn't draw it", source: "outlook-captures" }, web: css }
|
|
702
838
|
},
|
|
703
839
|
textAlign: { kind: enumOf(["left", "center", "right", "justify"]), label: "Alignment", inherited: true, mobile: true, support: { email: { level: "full", source: "caniemail" }, web: css } },
|
|
704
|
-
textTransform: {
|
|
705
|
-
|
|
840
|
+
textTransform: {
|
|
841
|
+
kind: enumOf(["none", "uppercase", "lowercase", "capitalize"]),
|
|
842
|
+
label: "Letter case",
|
|
843
|
+
inherited: true,
|
|
844
|
+
support: { email: { level: "outlook", values: ["lowercase"], shows: "the text as written: Word doesn't lowercase it", source: "caniemail", feature: "css-text-transform" }, web: css }
|
|
845
|
+
},
|
|
846
|
+
textDecoration: {
|
|
847
|
+
kind: enumOf(["none", "underline", "line-through"]),
|
|
848
|
+
label: "Decoration",
|
|
849
|
+
support: {
|
|
850
|
+
// Removed, so "none" leaves a link the client's own underline: said only where a line is asked for.
|
|
851
|
+
email: { level: "partial", values: ["underline", "line-through"], shows: "no line: the client removes it", source: "caniemail", feature: "css-text-decoration", clients: ["gmail-app-other", "protonmail", "orange", "sfr"] },
|
|
852
|
+
web: css
|
|
853
|
+
}
|
|
854
|
+
}
|
|
706
855
|
};
|
|
707
856
|
var inheritedTextStyle = (({ textDecoration: _, ...rest }) => rest)(textStyle);
|
|
708
857
|
var boxStyle = {
|
|
@@ -715,7 +864,17 @@ var boxStyle = {
|
|
|
715
864
|
padding: { kind: insets(), label: "Padding", mobile: true, support: { email: { level: "full", source: "outlook-captures" }, web: css } },
|
|
716
865
|
backgroundColor: { kind: color(), label: "Background color", support: { email: writer, web: css } },
|
|
717
866
|
border: { kind: border(), label: "Border", mobile: true, support: { email: { level: "full", source: "caniemail" }, web: css } },
|
|
718
|
-
borderRadius: {
|
|
867
|
+
borderRadius: {
|
|
868
|
+
kind: radius(),
|
|
869
|
+
label: "Corner radius",
|
|
870
|
+
support: {
|
|
871
|
+
email: [
|
|
872
|
+
{ level: "outlook", shows: "square corners", source: "caniemail", feature: "css-border-radius" },
|
|
873
|
+
{ level: "partial", shows: "square corners", source: "caniemail", feature: "css-border-radius", clients: ["gmx", "web-de", "orange"] }
|
|
874
|
+
],
|
|
875
|
+
web: css
|
|
876
|
+
}
|
|
877
|
+
},
|
|
719
878
|
overflow: {
|
|
720
879
|
kind: enumOf(["visible", "hidden"]),
|
|
721
880
|
label: "Overflow",
|
|
@@ -923,6 +1082,15 @@ var Grid = defineComponent({
|
|
|
923
1082
|
},
|
|
924
1083
|
children: { categories: ["layout", "block"] },
|
|
925
1084
|
sizing: "stretch",
|
|
1085
|
+
// Stacked on phones, every two of its children are its phone gap apart: there are no rows for a row gap.
|
|
1086
|
+
conditionSupport: {
|
|
1087
|
+
mobile: {
|
|
1088
|
+
rowGap: {
|
|
1089
|
+
web: { level: "none", when: (_, node) => node.props.stackOnMobile !== false, shows: "its phone gap between every two children", source: "writer" },
|
|
1090
|
+
email: { level: "none", when: (_, node) => node.props.stackOnMobile !== false, shows: "its phone gap between every two children", source: "writer" }
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
},
|
|
926
1094
|
targets: { email: {}, web: {} },
|
|
927
1095
|
editor: {
|
|
928
1096
|
groups: [
|
|
@@ -935,7 +1103,7 @@ var Grid = defineComponent({
|
|
|
935
1103
|
var Layer = defineComponent({
|
|
936
1104
|
type: "Layer",
|
|
937
1105
|
label: "Layer",
|
|
938
|
-
description: "Its children over each other, in one box as tall as the tallest: an image
|
|
1106
|
+
description: "Its children over each other, in one box as tall as the tallest: words on an image (a background image, its first child). In an email, an Image with one child over it.",
|
|
939
1107
|
categories: ["layout", "block"],
|
|
940
1108
|
props: {},
|
|
941
1109
|
style: {
|
|
@@ -967,13 +1135,27 @@ var Layer = defineComponent({
|
|
|
967
1135
|
}
|
|
968
1136
|
});
|
|
969
1137
|
var WRITTEN3 = { email: { level: "full", source: "writer" }, web: { level: "full", source: "writer" } };
|
|
1138
|
+
var NO_WEB_FONTS = ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail", "gmail-app", "gmail-app-other", "yahoo", "aol", "protonmail", "fastmail", "gmx", "web-de", "orange", "sfr", "laposte", "mail-ru", "ionos"];
|
|
970
1139
|
var ROOT_PROPS = {
|
|
971
1140
|
title: prop(text(), { support: WRITTEN3, label: "Title", description: "The document's title (shown in a browser tab, and by some email clients)." }),
|
|
972
|
-
lang: prop(text({ maxLength: 35 }), {
|
|
973
|
-
|
|
1141
|
+
lang: prop(text({ maxLength: 35 }), {
|
|
1142
|
+
support: {
|
|
1143
|
+
...WRITTEN3,
|
|
1144
|
+
email: { level: "partial", shows: "no language: the client removes it, and a screen reader reads the email in its own", source: "caniemail", feature: "html-lang", clients: ["hey", "gmx", "web-de", "orange", "ionos"] }
|
|
1145
|
+
},
|
|
1146
|
+
label: "Language",
|
|
1147
|
+
description: 'Its language, as a BCP 47 tag ("en", "ar").'
|
|
1148
|
+
}),
|
|
1149
|
+
dir: prop(enumOf(["ltr", "rtl", "auto"]), {
|
|
1150
|
+
support: {
|
|
1151
|
+
...WRITTEN3,
|
|
1152
|
+
email: { level: "partial", values: ["rtl", "auto"], shows: "left to right: the client removes the direction", source: "caniemail", feature: "html-dir", clients: ["gmx", "web-de"] }
|
|
1153
|
+
},
|
|
1154
|
+
label: "Text direction"
|
|
1155
|
+
}),
|
|
974
1156
|
fonts: prop(fontFaces(), {
|
|
975
1157
|
support: {
|
|
976
|
-
email: { level: "partial", shows: "
|
|
1158
|
+
email: { level: "partial", shows: "no web font loads: the next font in each stack shows", source: "caniemail", feature: "css-at-font-face", clients: NO_WEB_FONTS },
|
|
977
1159
|
web: { level: "full", source: "writer" }
|
|
978
1160
|
},
|
|
979
1161
|
label: "Web fonts",
|
|
@@ -1113,7 +1295,10 @@ var PageNumber = defineComponent({
|
|
|
1113
1295
|
style: {},
|
|
1114
1296
|
children: {},
|
|
1115
1297
|
sizing: "none",
|
|
1116
|
-
targets: { web: {} }
|
|
1298
|
+
targets: { web: {} },
|
|
1299
|
+
// A Page's or a Canvas's text has no page to number.
|
|
1300
|
+
roots: ["Document"],
|
|
1301
|
+
notAllowed: { code: "document/page-number", message: (root) => `A PageNumber is a page's number in a Document; this ${root} has no pages` }
|
|
1117
1302
|
});
|
|
1118
1303
|
function documentPages(root) {
|
|
1119
1304
|
const pages = [];
|
|
@@ -1249,6 +1434,11 @@ var Page = defineComponent({
|
|
|
1249
1434
|
props: {
|
|
1250
1435
|
width: prop(length(["px"], { min: 200 }), { support: WRITTEN6, label: "Content width", description: "The widest the content gets.", default: 1200 }),
|
|
1251
1436
|
description: prop(text(), { support: WRITTEN6, label: "Description", description: "The page's meta description." }),
|
|
1437
|
+
breakpoints: prop(breakpoints(), {
|
|
1438
|
+
support: { web: { level: "full", source: "writer" } },
|
|
1439
|
+
label: "Breakpoints",
|
|
1440
|
+
description: "Where its phone styles start: mobile, the widest window they apply to, in px (480 where it's not set)."
|
|
1441
|
+
}),
|
|
1252
1442
|
...ROOT_PROPS
|
|
1253
1443
|
},
|
|
1254
1444
|
style: ROOT_STYLE,
|
|
@@ -1256,6 +1446,12 @@ var Page = defineComponent({
|
|
|
1256
1446
|
children: { categories: ["section", "layout", "block"] },
|
|
1257
1447
|
sizing: "none",
|
|
1258
1448
|
responsive: true,
|
|
1449
|
+
// The page writer writes the root's phone gap, not its phone text styles.
|
|
1450
|
+
conditionSupport: {
|
|
1451
|
+
mobile: Object.fromEntries(
|
|
1452
|
+
["fontSize", "lineHeight", "letterSpacing", "textAlign"].map((key) => [key, { web: { level: "none", shows: "its desktop value: set phone text styles on its Sections or Columns", source: "writer" } }])
|
|
1453
|
+
)
|
|
1454
|
+
},
|
|
1259
1455
|
targets: { web: {} },
|
|
1260
1456
|
editor: {
|
|
1261
1457
|
groups: [
|
|
@@ -1499,6 +1695,7 @@ var Image = defineComponent({
|
|
|
1499
1695
|
},
|
|
1500
1696
|
children: {},
|
|
1501
1697
|
sizing: "shrink",
|
|
1698
|
+
conditionSupport: { mobile: { border: { email: { level: "none", shows: "its desktop border", source: "writer" } } } },
|
|
1502
1699
|
targets: { email: {}, web: {} },
|
|
1503
1700
|
editor: { groups: [{ title: "Image", paths: ["props.src", "props.alt", "style.width"] }, { title: "Box", paths: ["style.border", "style.borderRadius"] }] }
|
|
1504
1701
|
});
|
|
@@ -1629,6 +1826,7 @@ var TableCell = defineComponent({
|
|
|
1629
1826
|
},
|
|
1630
1827
|
children: { text: true, categories: ["inline"], types: ["Image"] },
|
|
1631
1828
|
sizing: "none",
|
|
1829
|
+
conditionSupport: { mobile: { border: { email: { level: "none", shows: "its desktop border", source: "writer" } } } },
|
|
1632
1830
|
targets: { email: {}, web: {} },
|
|
1633
1831
|
editor: { groups: [{ title: "Cell", paths: ["props.colSpan", "style.width", "style.verticalAlign"] }, TEXT_EDITOR, { title: "Box", paths: ["style.padding", "style.backgroundColor", "style.border"] }] }
|
|
1634
1832
|
});
|
|
@@ -1727,17 +1925,27 @@ function checkDocument(input, options) {
|
|
|
1727
1925
|
const extra = Object.keys(input).filter((key) => key !== "root");
|
|
1728
1926
|
for (const key of extra) d.error("document/unknown-key", `"${key}" isn't part of a document (root)`, key);
|
|
1729
1927
|
const rootDefinition = isObject2(input.root) && typeof input.root.type === "string" ? registry.get(input.root.type) : void 0;
|
|
1730
|
-
const context = { d, registry, used: /* @__PURE__ */ new Set(),
|
|
1928
|
+
const context = { d, registry, used: /* @__PURE__ */ new Set(), root: rootDefinition };
|
|
1731
1929
|
const root = input.root === void 0 ? null : readNode(input.root, "root", null, context);
|
|
1732
1930
|
if (input.root === void 0) d.error("document/root", 'A document has a root: { type: "Email" }, "Page", \u2026', "root");
|
|
1733
1931
|
return { document: root ? { root } : null, diagnostics: d.list };
|
|
1734
1932
|
}
|
|
1735
1933
|
function checkFragment(input, options) {
|
|
1736
1934
|
const d = new Diagnostics();
|
|
1737
|
-
const context = { d, registry: options.registry, idPrefix: options.idPrefix, used: new Set(options.taken),
|
|
1935
|
+
const context = { d, registry: options.registry, idPrefix: options.idPrefix, used: new Set(options.taken), root: options.root };
|
|
1738
1936
|
const node = readNode(input, options.path, options.parent, context);
|
|
1739
1937
|
return { node, diagnostics: d.list };
|
|
1740
1938
|
}
|
|
1939
|
+
function rootList(roots) {
|
|
1940
|
+
const named = roots.map((root) => a(root).replace(/^A/, "a"));
|
|
1941
|
+
return named.length > 1 ? `${named.slice(0, -1).join(", ")} or ${named[named.length - 1]}` : named[0] ?? "no root";
|
|
1942
|
+
}
|
|
1943
|
+
function rootTakes(roots, root) {
|
|
1944
|
+
return !roots || !root || roots.includes(root.type);
|
|
1945
|
+
}
|
|
1946
|
+
function sharesOutput(definition, root) {
|
|
1947
|
+
return Object.keys(definition.targets).some((target) => target in root.targets);
|
|
1948
|
+
}
|
|
1741
1949
|
function allowedIn(parent, child) {
|
|
1742
1950
|
const rule2 = parent.children;
|
|
1743
1951
|
return !!rule2.types?.includes(child.type) || child.categories.some((category) => rule2.categories?.includes(category));
|
|
@@ -1763,7 +1971,29 @@ function editDistance(from, to) {
|
|
|
1763
1971
|
}
|
|
1764
1972
|
return row[to.length];
|
|
1765
1973
|
}
|
|
1974
|
+
var OTHER_BREAKPOINTS = /* @__PURE__ */ new Set(["base", "initial", "xs", "sm", "md", "lg", "xl", "2xl", "tablet", "desktop", "wide", "small", "medium", "large"]);
|
|
1975
|
+
function conditionHint(definition, key) {
|
|
1976
|
+
const name = key.trim().replace(/^(&?:+|_+|\$|@)/, "").replace(/Style$/, "").toLowerCase();
|
|
1977
|
+
const own3 = CUSTOM_TYPE.test(definition.type);
|
|
1978
|
+
const hoverStyles = definition.hover ?? [];
|
|
1979
|
+
const toHover = own3 ? `a template's own component takes only its place in a Row in its style: set hover styles in what it renders` : hoverStyles.length ? `hover styles go in its style's hover block, style={{ hover: { \u2026 } }} (it takes ${hoverStyles.join(", ")})` : `${a(definition.type).replace(/^A/, "a")} takes no hover styles (a Button's and a Link's colors do)`;
|
|
1980
|
+
const toPhone = own3 ? `a template's own component takes only its place in a Row in its style: set phone styles in what it renders` : `phone styles go in its style's mobile block, style={{ mobile: { \u2026 } }}`;
|
|
1981
|
+
if (name === "hover") return toHover;
|
|
1982
|
+
if (name === "mobile" || name === "phone" || name === "phones" || key.trim().startsWith("@media")) return toPhone;
|
|
1983
|
+
if (name === "focus" || name === "focus-visible" || name === "focusvisible" || name === "focus-within") {
|
|
1984
|
+
return hoverStyles.length && !own3 ? `keyboard focus gets its hover styles: set them in style={{ hover: { \u2026 } }}` : `there are no focus styles; keyboard focus gets a Button's and a Link's hover styles`;
|
|
1985
|
+
}
|
|
1986
|
+
if (OTHER_BREAKPOINTS.has(name)) {
|
|
1987
|
+
return `Elements has one breakpoint, phones: desktop styles go in style itself, phone styles in its mobile block, style={{ mobile: { \u2026 } }} (a Page sets where phones start with breakpoints={{ mobile }})`;
|
|
1988
|
+
}
|
|
1989
|
+
if (name === "active" || name === "pressed" || name === "press" || name === "visited" || name === "dark" || name === "light" || name === "print") {
|
|
1990
|
+
return `there are no ${name} styles: Elements has phone styles (mobile) and hover styles (hover) in style`;
|
|
1991
|
+
}
|
|
1992
|
+
return void 0;
|
|
1993
|
+
}
|
|
1766
1994
|
function unknownProp(definition, key) {
|
|
1995
|
+
const condition = conditionHint(definition, key);
|
|
1996
|
+
if (condition) return `${a(definition.type)} has no prop "${key}": ${condition}`;
|
|
1767
1997
|
if (Object.prototype.hasOwnProperty.call(definition.style, key)) return `${a(definition.type)} has no prop "${key}": it's a style, style={{ ${key}: \u2026 }}`;
|
|
1768
1998
|
const known = Object.keys(definition.props);
|
|
1769
1999
|
const meant = nearest(key, known);
|
|
@@ -1803,6 +2033,15 @@ function readNode(input, path, parent, context) {
|
|
|
1803
2033
|
d.error("children/not-allowed", placementMessage(parent, definition), path);
|
|
1804
2034
|
return null;
|
|
1805
2035
|
}
|
|
2036
|
+
const root = context.root;
|
|
2037
|
+
if (root && !rootTakes(definition.roots, root) && sharesOutput(definition, root)) {
|
|
2038
|
+
const { code, message } = definition.notAllowed ?? {
|
|
2039
|
+
code: "component/not-supported",
|
|
2040
|
+
message: (under) => `${a(definition.type)} goes in ${rootList(definition.roots ?? [])}, not in ${a(under).replace(/^A/, "a")}`
|
|
2041
|
+
};
|
|
2042
|
+
d.error(code, message(root.type), path);
|
|
2043
|
+
return null;
|
|
2044
|
+
}
|
|
1806
2045
|
let id;
|
|
1807
2046
|
if (input.id !== void 0) {
|
|
1808
2047
|
if (typeof input.id !== "string" || !ID.test(input.id)) {
|
|
@@ -1822,21 +2061,49 @@ function readNode(input, path, parent, context) {
|
|
|
1822
2061
|
if (!NODE_KEYS.includes(key)) d.error("node/unknown-key", `"${key}" isn't part of a component (${NODE_KEYS.join(", ")})`, `${path}.${key}`, id);
|
|
1823
2062
|
}
|
|
1824
2063
|
const node = id === void 0 ? { type: definition.type } : { id, type: definition.type };
|
|
1825
|
-
const props = readProps(input.props, definition, `${path}.props`, id, d);
|
|
2064
|
+
const props = readProps(input.props, definition, `${path}.props`, id, d, context.root);
|
|
1826
2065
|
if (props) node.props = props;
|
|
1827
|
-
const
|
|
1828
|
-
|
|
1829
|
-
if (
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
2066
|
+
const lifted = {};
|
|
2067
|
+
let styleInput = input.style;
|
|
2068
|
+
if (isObject2(styleInput) && !CUSTOM_TYPE.test(definition.type)) {
|
|
2069
|
+
const rest = {};
|
|
2070
|
+
for (const [key, value] of Object.entries(styleInput)) {
|
|
2071
|
+
if (CONDITION_NAMES.has(key)) lifted[key] = value;
|
|
2072
|
+
else rest[key] = value;
|
|
1834
2073
|
}
|
|
2074
|
+
styleInput = rest;
|
|
1835
2075
|
}
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2076
|
+
const style = readStyles(styleInput, definition, definition.style, `${path}.style`, id, d, "style", () => true, void 0, context.root);
|
|
2077
|
+
if (style) node.style = style;
|
|
2078
|
+
for (const condition of CONDITIONS) {
|
|
2079
|
+
const inStyle = lifted[condition.name];
|
|
2080
|
+
const given = input[condition.name];
|
|
2081
|
+
if (inStyle !== void 0 && given !== void 0) {
|
|
2082
|
+
d.error(
|
|
2083
|
+
"style/twice",
|
|
2084
|
+
`${condition.name} is given twice: as ${condition.name}={\u2026} and in style. Give it once, in style: style={{ ${condition.name}: { \u2026 } }}`,
|
|
2085
|
+
`${path}.style.${condition.name}`,
|
|
2086
|
+
id
|
|
2087
|
+
);
|
|
2088
|
+
continue;
|
|
2089
|
+
}
|
|
2090
|
+
let value = inStyle ?? given;
|
|
2091
|
+
if (value === void 0) continue;
|
|
2092
|
+
const at = inStyle !== void 0 ? `${path}.style.${condition.name}` : `${path}.${condition.name}`;
|
|
2093
|
+
if (condition.roots && !condition.roots(context.root)) {
|
|
2094
|
+
if (condition.notAllowed) d.error(condition.notAllowed.code, condition.notAllowed.message, at, id);
|
|
2095
|
+
continue;
|
|
2096
|
+
}
|
|
2097
|
+
if (isObject2(value) && Object.keys(value).some((key) => CONDITION_NAMES.has(key))) {
|
|
2098
|
+
const own3 = {};
|
|
2099
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
2100
|
+
if (CONDITION_NAMES.has(key)) d.error("style/nested", `${key} can't be set inside ${condition.name}: a block holds styles, not another block`, `${at}.${key}`, id);
|
|
2101
|
+
else own3[key] = inner;
|
|
2102
|
+
}
|
|
2103
|
+
value = own3;
|
|
2104
|
+
}
|
|
2105
|
+
const read2 = readStyles(value, definition, definition.style, at, id, d, condition.name, (spec, key) => condition.allows(spec, key, definition), condition.for, context.root);
|
|
2106
|
+
if (read2) node[condition.name] = read2;
|
|
1840
2107
|
}
|
|
1841
2108
|
if (input.hidden !== void 0) {
|
|
1842
2109
|
const hidden2 = readHidden(input.hidden, `${path}.hidden`, id, context);
|
|
@@ -1846,7 +2113,7 @@ function readNode(input, path, parent, context) {
|
|
|
1846
2113
|
if (children.length) node.children = children;
|
|
1847
2114
|
return node;
|
|
1848
2115
|
}
|
|
1849
|
-
function readProps(input, definition, path, id, d) {
|
|
2116
|
+
function readProps(input, definition, path, id, d, root) {
|
|
1850
2117
|
const out = {};
|
|
1851
2118
|
if (input !== void 0 && !isObject2(input)) {
|
|
1852
2119
|
d.error("props/invalid", "props is an object", path, id);
|
|
@@ -1859,9 +2126,14 @@ function readProps(input, definition, path, id, d) {
|
|
|
1859
2126
|
d.error("prop/unknown", unknownProp(definition, key), `${path}.${key}`, id);
|
|
1860
2127
|
continue;
|
|
1861
2128
|
}
|
|
2129
|
+
if (!rootTakes(spec.roots, root)) {
|
|
2130
|
+
d.error("prop/not-supported", `${a(definition.type)}'s "${key}" is for ${rootList(spec.roots)}, not ${a(root.type).replace(/^A/, "a")}`, `${path}.${key}`, id);
|
|
2131
|
+
continue;
|
|
2132
|
+
}
|
|
1862
2133
|
const parsed = spec.kind.parse(value);
|
|
1863
|
-
if (parsed.ok)
|
|
1864
|
-
|
|
2134
|
+
if (parsed.ok) {
|
|
2135
|
+
if (!spec.kind.empty?.(parsed.value)) out[key] = parsed.value;
|
|
2136
|
+
} else d.error("prop/invalid", `${key}: ${parsed.message}`, `${path}.${key}`, id);
|
|
1865
2137
|
}
|
|
1866
2138
|
for (const [key, spec] of Object.entries(definition.props)) {
|
|
1867
2139
|
if (spec.required && !(key in out) && !(isObject2(input) && input[key] !== void 0)) {
|
|
@@ -1878,6 +2150,8 @@ var UNSUPPORTED_CSS = {
|
|
|
1878
2150
|
marginRight: "use padding",
|
|
1879
2151
|
display: "the component sets it",
|
|
1880
2152
|
position: "use a Layer for content placed over other content",
|
|
2153
|
+
backgroundImage: "put a Layer around it, its first child an Image (the Layer's height crops it to cover), the rest over it",
|
|
2154
|
+
background: "backgroundColor for a color; for an image, a Layer: its first child an Image, the rest over it",
|
|
1881
2155
|
float: "use a Row to put things side by side",
|
|
1882
2156
|
// Names Elements used before it took CSS's.
|
|
1883
2157
|
align: "it's alignItems now",
|
|
@@ -1891,10 +2165,12 @@ function styleHint(definition, specs, key) {
|
|
|
1891
2165
|
if (Object.prototype.hasOwnProperty.call(definition.props, key)) return `: it's a prop, ${key}={\u2026}`;
|
|
1892
2166
|
if (key === "padding" && definition.categories.includes("root")) return ": a root has no padding; put it on a Column (a card) or a Section (a band) inside it";
|
|
1893
2167
|
if (UNSUPPORTED_CSS[key]) return `: ${UNSUPPORTED_CSS[key]}`;
|
|
2168
|
+
const condition = conditionHint(definition, key);
|
|
2169
|
+
if (condition) return `: ${condition}`;
|
|
1894
2170
|
const meant = nearest(key, Object.keys(specs));
|
|
1895
2171
|
return meant ? `: did you mean "${meant}"?` : "";
|
|
1896
2172
|
}
|
|
1897
|
-
function readStyles(input, definition, specs, path, id, d, what, allowed) {
|
|
2173
|
+
function readStyles(input, definition, specs, path, id, d, what, allowed, forLabel, root) {
|
|
1898
2174
|
if (input === void 0) return void 0;
|
|
1899
2175
|
if (!isObject2(input)) {
|
|
1900
2176
|
d.error(`${what}/invalid`, `${what} is an object of style properties`, path, id);
|
|
@@ -1908,8 +2184,12 @@ function readStyles(input, definition, specs, path, id, d, what, allowed) {
|
|
|
1908
2184
|
d.error("style/unknown", seeing(`"${key}" isn't a style ${a(definition.type).replace(/^A/, "a")} takes${styleHint(definition, specs, key)}`, definition.type), `${path}.${key}`, id);
|
|
1909
2185
|
continue;
|
|
1910
2186
|
}
|
|
2187
|
+
if (!rootTakes(spec.roots, root)) {
|
|
2188
|
+
d.error("style/not-supported", `${a(definition.type)}'s "${key}" is for ${rootList(spec.roots)}, not ${a(root.type).replace(/^A/, "a")}`, `${path}.${key}`, id);
|
|
2189
|
+
continue;
|
|
2190
|
+
}
|
|
1911
2191
|
if (!allowed(spec, key)) {
|
|
1912
|
-
d.error(`${what}/not-allowed`,
|
|
2192
|
+
d.error(`${what}/not-allowed`, `"${key}" can't be set ${forLabel ?? "here"}`, `${path}.${key}`, id);
|
|
1913
2193
|
continue;
|
|
1914
2194
|
}
|
|
1915
2195
|
const parsed = spec.kind.parse(value);
|
|
@@ -1920,7 +2200,7 @@ function readStyles(input, definition, specs, path, id, d, what, allowed) {
|
|
|
1920
2200
|
}
|
|
1921
2201
|
function readHidden(input, path, id, context) {
|
|
1922
2202
|
const { d } = context;
|
|
1923
|
-
if (!context.responsive) {
|
|
2203
|
+
if (!context.root?.responsive) {
|
|
1924
2204
|
d.error("hidden/not-supported", "Hiding on phones or desktops is for an Email or a Page", path, id);
|
|
1925
2205
|
return void 0;
|
|
1926
2206
|
}
|
|
@@ -1931,7 +2211,7 @@ function readHidden(input, path, id, context) {
|
|
|
1931
2211
|
const out = {};
|
|
1932
2212
|
if (input.mobile === true) out.mobile = true;
|
|
1933
2213
|
if (input.desktop === true) out.desktop = true;
|
|
1934
|
-
if (
|
|
2214
|
+
if (!shownAnywhere({ hidden: out })) d.warning("hidden/everywhere", "Hidden on phones and on desktops, it never shows", path, id);
|
|
1935
2215
|
return Object.keys(out).length ? out : void 0;
|
|
1936
2216
|
}
|
|
1937
2217
|
function readChildren(input, definition, path, id, context) {
|
|
@@ -1964,6 +2244,22 @@ function readChildren(input, definition, path, id, context) {
|
|
|
1964
2244
|
});
|
|
1965
2245
|
return out;
|
|
1966
2246
|
}
|
|
2247
|
+
var same = (a2, b) => JSON.stringify(a2) === JSON.stringify(b);
|
|
2248
|
+
function reportDeclared(d, node, at, key, value, declared, target, outlook = true) {
|
|
2249
|
+
if (!declared) return;
|
|
2250
|
+
for (const rule2 of [].concat(declared)) {
|
|
2251
|
+
if (rule2.level === "full") continue;
|
|
2252
|
+
if (rule2.level === "outlook" && !outlook) continue;
|
|
2253
|
+
if (rule2.values && !rule2.values.some((v) => same(v, value))) continue;
|
|
2254
|
+
if (rule2.when && !rule2.when(value, node)) continue;
|
|
2255
|
+
const asked = typeof value === "string" ? `${key}: "${value}"` : key;
|
|
2256
|
+
const clients = target === "email" ? rule2.clients ?? (rule2.level === "outlook" ? ["outlook"] : void 0) : void 0;
|
|
2257
|
+
const output = clients ? "some email clients" : target === "web" ? "a page" : target;
|
|
2258
|
+
const [code, severity, message] = rule2.level === "none" ? [`${target}/unsupported`, "warning", `${asked} isn't possible in ${output}: ${rule2.shows}`] : rule2.level === "partial" ? [`${target}/degraded`, "info", `${asked} on this ${node.type} in ${output}: ${rule2.shows}`] : ["email/outlook-degraded", "info", `${asked} on this ${node.type} in classic Outlook: ${rule2.shows}`];
|
|
2259
|
+
const [said, as] = rule2.source === "assumed" ? ["info", `${message} (not checked)`] : [severity, message];
|
|
2260
|
+
d.add(code, said, as, at, node.id, { clients, fix: rule2.fix, see: rule2.feature && caniemail(rule2.feature) });
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
1967
2263
|
var MAX_DEPTH = 32;
|
|
1968
2264
|
function templatePathsOf(document, paths) {
|
|
1969
2265
|
const places = /* @__PURE__ */ new Map();
|
|
@@ -1980,7 +2276,7 @@ function templatePathsOf(document, paths) {
|
|
|
1980
2276
|
return path;
|
|
1981
2277
|
};
|
|
1982
2278
|
}
|
|
1983
|
-
function expandDocument(document, options) {
|
|
2279
|
+
function expandDocument(document, options, target) {
|
|
1984
2280
|
const registry = registryOf(options);
|
|
1985
2281
|
const d = new Diagnostics();
|
|
1986
2282
|
const paths = /* @__PURE__ */ new WeakMap();
|
|
@@ -1992,7 +2288,20 @@ function expandDocument(document, options) {
|
|
|
1992
2288
|
collect(document.root);
|
|
1993
2289
|
const rootDefinition = registry.get(document.root.type);
|
|
1994
2290
|
const responsive = !!rootDefinition?.responsive;
|
|
2291
|
+
const unshown = (node, path) => {
|
|
2292
|
+
const declared = target && registry.get(node.type)?.conditionSupport;
|
|
2293
|
+
if (!declared) return;
|
|
2294
|
+
for (const condition of CONDITIONS) {
|
|
2295
|
+
const block2 = blockOf(node, condition);
|
|
2296
|
+
const byStyle = declared[condition.name];
|
|
2297
|
+
if (!block2 || !byStyle || condition.hiddenKey && node.hidden?.[condition.hiddenKey]) continue;
|
|
2298
|
+
for (const [key, value] of Object.entries(block2)) {
|
|
2299
|
+
reportDeclared(d, { type: node.type, id: node.id, props: node.props ?? {} }, `${path}.${condition.name}.${key}`, key, value, byStyle[key]?.[target], target);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
1995
2303
|
const visit = (given, path, depth, self = path) => {
|
|
2304
|
+
unshown(given, path);
|
|
1996
2305
|
const arrange = registry.get(given.type)?.arrange;
|
|
1997
2306
|
if (!arrange) return walk(given, path, self, depth);
|
|
1998
2307
|
const origins = /* @__PURE__ */ new Map();
|
|
@@ -2019,27 +2328,34 @@ function expandDocument(document, options) {
|
|
|
2019
2328
|
return void 0;
|
|
2020
2329
|
}
|
|
2021
2330
|
let raw2;
|
|
2331
|
+
const reported = [];
|
|
2022
2332
|
try {
|
|
2023
2333
|
const style = { ...definition.defaultStyle, ...node.style };
|
|
2024
|
-
raw2 = definition.expand(propsWithDefaults(definition, node.props), { register: (custom) => registry.add(custom), style, responsive });
|
|
2334
|
+
raw2 = definition.expand(propsWithDefaults(definition, node.props), { register: (custom) => registry.add(custom), style, responsive, report: (diagnostic) => reported.push(diagnostic) });
|
|
2025
2335
|
} catch (error) {
|
|
2026
2336
|
d.error("custom/render", `${node.type} failed to render: ${error instanceof Error ? error.message : String(error)}`, path, node.id);
|
|
2027
2337
|
return void 0;
|
|
2028
2338
|
}
|
|
2339
|
+
const at = `${path}(${node.type})`;
|
|
2340
|
+
for (const diagnostic of reported) d.list.push({ ...diagnostic, path: diagnostic.path === "root" || diagnostic.path.startsWith("root.") ? at + diagnostic.path.slice(4) : at });
|
|
2029
2341
|
if (Array.isArray(raw2) || typeof raw2 !== "object" || raw2 === null) {
|
|
2030
2342
|
d.error("custom/render", `${node.type} renders one component (a Column, a Section, \u2026), not ${Array.isArray(raw2) ? "several" : JSON.stringify(raw2)}`, path, node.id);
|
|
2031
2343
|
return void 0;
|
|
2032
2344
|
}
|
|
2033
|
-
const { node: expanded, diagnostics } = checkFragment(raw2, { parent,
|
|
2345
|
+
const { node: expanded, diagnostics } = checkFragment(raw2, { parent, root: rootDefinition, idPrefix: node.id ?? null, taken, registry, path: `${path}(${node.type})` });
|
|
2034
2346
|
for (const diagnostic of diagnostics) d.list.push(diagnostic);
|
|
2035
2347
|
if (!expanded) return void 0;
|
|
2036
2348
|
collectIds(expanded);
|
|
2037
2349
|
if (node.style?.flex !== void 0) expanded.style = { ...expanded.style, flex: node.style.flex };
|
|
2038
2350
|
if (node.hidden) expanded.hidden = { ...expanded.hidden, ...node.hidden };
|
|
2039
2351
|
if (node.id && !expanded.id && !CUSTOM_TYPE.test(node.type)) expanded.id = node.id;
|
|
2040
|
-
for (const
|
|
2041
|
-
|
|
2042
|
-
|
|
2352
|
+
for (const condition of CONDITIONS) {
|
|
2353
|
+
const rendered = registry.get(expanded.type);
|
|
2354
|
+
for (const [key, value] of Object.entries(node[condition.name] ?? {})) {
|
|
2355
|
+
const spec = rendered?.style[key];
|
|
2356
|
+
if (rendered && spec && condition.allows(spec, key, rendered)) expanded[condition.name] = { [key]: value, ...expanded[condition.name] };
|
|
2357
|
+
else d.warning(`${condition.name}/not-shown`, `${node.type}'s ${key} ${condition.on} isn't shown: what it renders (a ${expanded.type}) has no ${key} ${condition.for}`, `${path}.${condition.name}.${key}`, node.id);
|
|
2358
|
+
}
|
|
2043
2359
|
}
|
|
2044
2360
|
const inner = registry.get(expanded.type);
|
|
2045
2361
|
if (isCustom(inner)) return expandOne(expanded, inner, parent, `${path}(${node.type})`, depth + 1, self);
|
|
@@ -2491,11 +2807,11 @@ var TEXT_CSS = [
|
|
|
2491
2807
|
["textAlign", "text-align", (v) => v],
|
|
2492
2808
|
["textTransform", "text-transform", (v) => v]
|
|
2493
2809
|
];
|
|
2494
|
-
var
|
|
2810
|
+
var same2 = (a2, b) => a2 === b || JSON.stringify(a2) === JSON.stringify(b);
|
|
2495
2811
|
function textDeclarations(text22, parent, always = []) {
|
|
2496
2812
|
const out = [];
|
|
2497
2813
|
for (const [key, property, write3] of TEXT_CSS) {
|
|
2498
|
-
if (!parent || always.includes(key) || !
|
|
2814
|
+
if (!parent || always.includes(key) || !same2(text22[key], parent[key])) out.push([property, write3(text22[key])]);
|
|
2499
2815
|
}
|
|
2500
2816
|
return out;
|
|
2501
2817
|
}
|
|
@@ -2571,7 +2887,9 @@ function classesOf(name, value) {
|
|
|
2571
2887
|
if (name === "class") return (Array.isArray(value) ? value : [value]).filter((v) => typeof v === "string" && v !== "");
|
|
2572
2888
|
const text22 = styleText(value, true);
|
|
2573
2889
|
if (!text22) return [];
|
|
2574
|
-
|
|
2890
|
+
if (name === "phone") return [rule(MOBILE.sheet.media, `${MOBILE.sheet.selector}{${text22}}`)];
|
|
2891
|
+
const selector = name === "hoverWithin" ? HOVER.sheet.within ?? HOVER.sheet.selector : HOVER.sheet.selector;
|
|
2892
|
+
return [rule(HOVER.sheet.media, `${selector}{${text22}}`, HOVER.sheet.level)];
|
|
2575
2893
|
}
|
|
2576
2894
|
function attributes2(props) {
|
|
2577
2895
|
const out = [];
|
|
@@ -2579,7 +2897,7 @@ function attributes2(props) {
|
|
|
2579
2897
|
let slot = -1;
|
|
2580
2898
|
for (const [name, value] of Object.entries(props)) {
|
|
2581
2899
|
if (name === "children" || name === "style" || value === void 0 || value === null || value === false) continue;
|
|
2582
|
-
if (name === "class" || name === "phone" || name === "hover") {
|
|
2900
|
+
if (name === "class" || name === "phone" || name === "hover" || name === "hoverWithin") {
|
|
2583
2901
|
const found = classesOf(name, value);
|
|
2584
2902
|
if (found.length && slot < 0) slot = out.push("") - 1;
|
|
2585
2903
|
classes.push(...found);
|
|
@@ -2595,8 +2913,8 @@ function withId(element2, id) {
|
|
|
2595
2913
|
if (!id) return element2;
|
|
2596
2914
|
const tag = /^<[a-zA-Z0-9]+/.exec(element2.markup)?.[0];
|
|
2597
2915
|
if (!tag) throw new Error("[Elements] An id goes on an element");
|
|
2598
|
-
const
|
|
2599
|
-
return new Html2(
|
|
2916
|
+
const insert2 = (markup2) => `${tag} id="${escapeAttr(id)}"${markup2.slice(tag.length)}`;
|
|
2917
|
+
return new Html2(insert2(element2.markup), { ...element2.options, start: element2.options.start && insert2(element2.options.start) });
|
|
2600
2918
|
}
|
|
2601
2919
|
function h(type, props, ...children) {
|
|
2602
2920
|
const all = props ?? {};
|
|
@@ -2623,6 +2941,98 @@ function Merged({ children }) {
|
|
|
2623
2941
|
}
|
|
2624
2942
|
var open = (element2) => new Html2(element2.options.start ?? element2.markup);
|
|
2625
2943
|
var close = (tag) => new Html2(`</${tag}>`);
|
|
2944
|
+
var BLOCK_MAP_VERSION = 1;
|
|
2945
|
+
var MARK_PAIRS = 16;
|
|
2946
|
+
var BlockMarks = class {
|
|
2947
|
+
constructor(pair) {
|
|
2948
|
+
this.nodes = [];
|
|
2949
|
+
this.open = String.fromCharCode(64976 + pair * 2);
|
|
2950
|
+
this.close = String.fromCharCode(64977 + pair * 2);
|
|
2951
|
+
}
|
|
2952
|
+
};
|
|
2953
|
+
var current = null;
|
|
2954
|
+
function withBlockMarks(marks, write3) {
|
|
2955
|
+
const before = current;
|
|
2956
|
+
current = marks;
|
|
2957
|
+
try {
|
|
2958
|
+
return write3();
|
|
2959
|
+
} finally {
|
|
2960
|
+
current = before;
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
function markBlock(html2, node) {
|
|
2964
|
+
if (!current) return html2;
|
|
2965
|
+
const mark = `${current.open}${current.nodes.length}${current.close}`;
|
|
2966
|
+
const marked = insert(html2, mark, false) ?? insert(html2, mark, true);
|
|
2967
|
+
if (!marked) return html2;
|
|
2968
|
+
current.nodes.push(node);
|
|
2969
|
+
return marked;
|
|
2970
|
+
}
|
|
2971
|
+
var OUTLOOK_OPEN = "<!--[if mso]>";
|
|
2972
|
+
var OUTLOOK_CLOSE = "<![endif]-->";
|
|
2973
|
+
var TAG2 = /<[a-zA-Z][a-zA-Z0-9]*/y;
|
|
2974
|
+
function tagEnd(markup2, outlook) {
|
|
2975
|
+
for (let i = markup2.indexOf("<"); i >= 0; i = markup2.indexOf("<", i + 1)) {
|
|
2976
|
+
if (markup2.startsWith(OUTLOOK_OPEN, i)) {
|
|
2977
|
+
if (outlook) {
|
|
2978
|
+
i += OUTLOOK_OPEN.length - 1;
|
|
2979
|
+
continue;
|
|
2980
|
+
}
|
|
2981
|
+
const end = markup2.indexOf(OUTLOOK_CLOSE, i);
|
|
2982
|
+
if (end < 0) return -1;
|
|
2983
|
+
i = end + OUTLOOK_CLOSE.length - 1;
|
|
2984
|
+
continue;
|
|
2985
|
+
}
|
|
2986
|
+
if (markup2.startsWith("<!--", i)) {
|
|
2987
|
+
const end = markup2.indexOf("-->", i + 4);
|
|
2988
|
+
if (end < 0) return -1;
|
|
2989
|
+
i = end + 2;
|
|
2990
|
+
continue;
|
|
2991
|
+
}
|
|
2992
|
+
TAG2.lastIndex = i;
|
|
2993
|
+
const tag = TAG2.exec(markup2);
|
|
2994
|
+
if (tag) return i + tag[0].length;
|
|
2995
|
+
}
|
|
2996
|
+
return -1;
|
|
2997
|
+
}
|
|
2998
|
+
function insert(html2, mark, outlook) {
|
|
2999
|
+
const { list, kind: kind2, start } = html2.options;
|
|
3000
|
+
if (list) {
|
|
3001
|
+
for (let i = 0; i < list.length; i++) {
|
|
3002
|
+
const marked = insert(list[i], mark, outlook);
|
|
3003
|
+
if (marked) return new Html2(html2.markup, { ...html2.options, list: list.map((piece, j) => j === i ? marked : piece) });
|
|
3004
|
+
}
|
|
3005
|
+
return null;
|
|
3006
|
+
}
|
|
3007
|
+
if (kind2 === "outlook" && !outlook) return null;
|
|
3008
|
+
const at = tagEnd(html2.markup, outlook);
|
|
3009
|
+
if (at < 0) return null;
|
|
3010
|
+
const put = (text22) => text22.slice(0, at) + mark + text22.slice(at);
|
|
3011
|
+
return new Html2(put(html2.markup), { ...html2.options, start: start !== void 0 && at <= start.length ? put(start) : start });
|
|
3012
|
+
}
|
|
3013
|
+
function readMarks(marks, written) {
|
|
3014
|
+
const found = [];
|
|
3015
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3016
|
+
const pieces2 = [];
|
|
3017
|
+
let length32 = 0;
|
|
3018
|
+
let last = 0;
|
|
3019
|
+
for (const match of written.matchAll(new RegExp(`${marks.open}(\\d+)${marks.close}`, "g"))) {
|
|
3020
|
+
const index = Number(match[1]);
|
|
3021
|
+
const node = marks.nodes[index];
|
|
3022
|
+
if (!node || seen.has(index)) return null;
|
|
3023
|
+
seen.add(index);
|
|
3024
|
+
const before = written.slice(last, match.index);
|
|
3025
|
+
pieces2.push(before);
|
|
3026
|
+
length32 += before.length;
|
|
3027
|
+
last = match.index + match[0].length;
|
|
3028
|
+
if (!/<[a-zA-Z][a-zA-Z0-9]*$/.test(written.slice(Math.max(0, match.index - 32), match.index).replace(new RegExp(`${marks.open}\\d+${marks.close}`, "g"), ""))) return null;
|
|
3029
|
+
found.push({ node, at: length32 });
|
|
3030
|
+
}
|
|
3031
|
+
pieces2.push(written.slice(last));
|
|
3032
|
+
const text22 = pieces2.join("");
|
|
3033
|
+
if (text22.includes(marks.open) || text22.includes(marks.close)) return null;
|
|
3034
|
+
return { text: text22, found };
|
|
3035
|
+
}
|
|
2626
3036
|
function utf8Bytes(text22) {
|
|
2627
3037
|
let bytes = 0;
|
|
2628
3038
|
for (let i = 0; i < text22.length; i++) {
|
|
@@ -2709,7 +3119,7 @@ function fontFaces2(root, { swap }) {
|
|
|
2709
3119
|
return [...faces.values()];
|
|
2710
3120
|
}
|
|
2711
3121
|
var isNode2 = (child) => typeof child !== "string";
|
|
2712
|
-
var shown = (node) =>
|
|
3122
|
+
var shown = (node) => shownAnywhere(node);
|
|
2713
3123
|
function link2(props) {
|
|
2714
3124
|
const { href, target } = linkParts(props.href);
|
|
2715
3125
|
return {
|
|
@@ -2944,7 +3354,7 @@ var Button2 = ({ node, style, mobile, fill, phoneText: phoneText22, text: text22
|
|
|
2944
3354
|
{
|
|
2945
3355
|
align: TEXT_ALIGN[node.text.textAlign],
|
|
2946
3356
|
bgcolor: bgcolor(style.backgroundColor),
|
|
2947
|
-
|
|
3357
|
+
hoverWithin: { backgroundColor: node.hover?.backgroundColor },
|
|
2948
3358
|
phone: { border: mobile.border, backgroundColor: mobile.backgroundColor },
|
|
2949
3359
|
style: { backgroundColor: style.backgroundColor, border: style.border, borderRadius: style.borderRadius, msoPaddingAlt: insets2(style.padding) }
|
|
2950
3360
|
},
|
|
@@ -2955,8 +3365,11 @@ var Button2 = ({ node, style, mobile, fill, phoneText: phoneText22, text: text22
|
|
|
2955
3365
|
phone: [phoneText22, { padding: mobile.padding }],
|
|
2956
3366
|
hover: { color: node.hover?.color },
|
|
2957
3367
|
style: [
|
|
2958
|
-
// A button with a width fills it.
|
|
2959
|
-
|
|
3368
|
+
// A button with a width fills it. Its label's words stay whole (the email's word-break: break-word
|
|
3369
|
+
// is for long links in text): a button narrower than its padding otherwise sized itself to one letter
|
|
3370
|
+
// and drew its label a letter a line, in Gmail and browsers; it grows to its longest word instead, as
|
|
3371
|
+
// classic Outlook's and Apple Mail's do (captures of Elements 0.3.0, e3-fresh/g-0054).
|
|
3372
|
+
{ display: width.width !== void 0 ? "block" : "inline-block", padding: style.padding, wordBreak: "normal" },
|
|
2960
3373
|
fullText(node),
|
|
2961
3374
|
{ textDecoration: style.textDecoration ?? "none", borderRadius: style.borderRadius }
|
|
2962
3375
|
]
|
|
@@ -3420,27 +3833,42 @@ function propsOf(node, place, options) {
|
|
|
3420
3833
|
options
|
|
3421
3834
|
};
|
|
3422
3835
|
}
|
|
3836
|
+
var UNMAPPED = /* @__PURE__ */ new Set(["LineBreak", "MergeTag"]);
|
|
3423
3837
|
function write(node, place, options) {
|
|
3424
3838
|
if (place.inline) {
|
|
3425
3839
|
if (!shown(node)) return raw("");
|
|
3426
3840
|
const component2 = INLINES[node.type];
|
|
3427
3841
|
if (!component2) throw new Error(`[Elements] The email exporter can't render a ${node.type} inside text`);
|
|
3428
|
-
|
|
3842
|
+
const html2 = component2(propsOf(node, place, options));
|
|
3843
|
+
return UNMAPPED.has(node.type) ? html2 : markBlock(html2, node);
|
|
3429
3844
|
}
|
|
3430
3845
|
const component = BLOCKS[node.type];
|
|
3431
3846
|
if (!component) throw new Error(`[Elements] The email exporter can't render a ${node.type} here (in a ${place.parent.type})`);
|
|
3432
|
-
return component(propsOf(node, place, options));
|
|
3847
|
+
return markBlock(component(propsOf(node, place, options)), node);
|
|
3433
3848
|
}
|
|
3434
3849
|
var EMAIL_PHONE_MARGIN = 20;
|
|
3435
3850
|
var GMAIL_CLIP_BYTES = 102400;
|
|
3436
3851
|
var GMAIL_STYLE_BYTES = 16384;
|
|
3437
3852
|
function renderEmail(resolved, options = {}) {
|
|
3438
3853
|
const root = resolved.root;
|
|
3439
|
-
const sheet = new Stylesheet(options.classPrefix);
|
|
3440
3854
|
const d = new Diagnostics();
|
|
3441
3855
|
const fonts = fontFaces2(root, { swap: false });
|
|
3442
3856
|
const width = root.width ?? 600;
|
|
3443
|
-
const
|
|
3857
|
+
const write3 = (marks) => {
|
|
3858
|
+
const sheet2 = new Stylesheet(options.classPrefix);
|
|
3859
|
+
const markup2 = withStylesheet(sheet2, () => withBlockMarks(marks, () => String(markBlock(Email2(propsOf(root, { parent: root, fill: false, mobile: {}, inline: false, rtl: false }, options)), root))));
|
|
3860
|
+
return { sheet: sheet2, markup: markup2 };
|
|
3861
|
+
};
|
|
3862
|
+
let written = options.blockMap ? null : write3(null);
|
|
3863
|
+
let marked = null;
|
|
3864
|
+
for (let pair = 0; !written; pair++) {
|
|
3865
|
+
if (pair === MARK_PAIRS) throw new Error("[Elements] blockMap can't mark this email: its text holds every pair of the noncharacters U+FDD0 to U+FDEF");
|
|
3866
|
+
const marks = new BlockMarks(pair);
|
|
3867
|
+
const attempt = write3(marks);
|
|
3868
|
+
marked = readMarks(marks, attempt.markup);
|
|
3869
|
+
if (marked) written = { sheet: attempt.sheet, markup: marked.text };
|
|
3870
|
+
}
|
|
3871
|
+
const { sheet, markup: email } = written;
|
|
3444
3872
|
const background = root.style.backgroundColor;
|
|
3445
3873
|
const lang = root.props.lang;
|
|
3446
3874
|
const dir = root.props.dir;
|
|
@@ -3464,7 +3892,8 @@ function renderEmail(resolved, options = {}) {
|
|
|
3464
3892
|
`<style>${stylesheet}</style>`
|
|
3465
3893
|
].join("");
|
|
3466
3894
|
const preheader = preview ? `<div style="display:none;max-height:0;overflow:hidden;mso-hide:all" aria-hidden="true" data-skip-in-text="true">${escapeText(preview)}${"͏‌ ".repeat(30)}</div>` : "";
|
|
3467
|
-
const
|
|
3895
|
+
const opening = `<div${attrs({ role: "article", "aria-roledescription": "email", "aria-label": title, lang, dir })}${styleAttr([["background-color", background], ["overflow-wrap", "break-word"], ["word-break", "break-word"]])}>`;
|
|
3896
|
+
const body = `${preheader}${opening}${email}</div>`;
|
|
3468
3897
|
const bodyDeclarations = [
|
|
3469
3898
|
["margin", "0"],
|
|
3470
3899
|
["padding", "0"],
|
|
@@ -3472,20 +3901,38 @@ function renderEmail(resolved, options = {}) {
|
|
|
3472
3901
|
["background-color", background]
|
|
3473
3902
|
];
|
|
3474
3903
|
const bodyStyle = css2(bodyDeclarations);
|
|
3475
|
-
const
|
|
3904
|
+
const shell = `<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html${attrs({ lang, dir, xmlns: "http://www.w3.org/1999/xhtml", "xmlns:v": "urn:schemas-microsoft-com:vml", "xmlns:o": "urn:schemas-microsoft-com:office:office" })}><head>${head}</head><body${styleAttr(bodyDeclarations)}>`;
|
|
3905
|
+
const html2 = `${shell}${body}</body></html>`;
|
|
3476
3906
|
const bytes = utf8Bytes(html2);
|
|
3907
|
+
const gmails = ["gmail", "gmail-app", "gmail-app-other"];
|
|
3477
3908
|
if (bytes > GMAIL_CLIP_BYTES) {
|
|
3478
|
-
d.warning("email/size", `The email is ${Math.round(bytes / 1024)} KB; Gmail clips a message after ${GMAIL_CLIP_BYTES / 1024} KB ("[Message clipped]")`, "root"
|
|
3909
|
+
d.warning("email/size", `The email is ${Math.round(bytes / 1024)} KB; Gmail clips a message after ${GMAIL_CLIP_BYTES / 1024} KB ("[Message clipped]")`, "root", void 0, {
|
|
3910
|
+
clients: gmails,
|
|
3911
|
+
fix: "shorten the email, or move long sections to a page: what follows the clip is behind a link"
|
|
3912
|
+
});
|
|
3479
3913
|
}
|
|
3480
3914
|
const styleBytes = utf8Bytes(stylesheet);
|
|
3481
3915
|
if (styleBytes > GMAIL_STYLE_BYTES) {
|
|
3482
|
-
d.warning("email/style-size", `The stylesheet is ${Math.round(styleBytes / 1024)} KB; Gmail reads ${GMAIL_STYLE_BYTES / 1024} KB of a <style>`, "root"
|
|
3916
|
+
d.warning("email/style-size", `The stylesheet is ${Math.round(styleBytes / 1024)} KB; Gmail reads ${GMAIL_STYLE_BYTES / 1024} KB of a <style>`, "root", void 0, {
|
|
3917
|
+
clients: gmails,
|
|
3918
|
+
fix: "use fewer phone and hover styles: each one adds a rule"
|
|
3919
|
+
});
|
|
3483
3920
|
}
|
|
3484
|
-
return { html: html2, head, body, bodyStyle, diagnostics: d.list };
|
|
3921
|
+
return { html: html2, head, body, bodyStyle, diagnostics: d.list, ...marked ? { blocks: blockMap(resolved, marked.found, email, utf8Bytes(shell + preheader + opening)) } : {} };
|
|
3922
|
+
}
|
|
3923
|
+
function blockMap(resolved, found, email, start) {
|
|
3924
|
+
const templatePath = resolved.templatePath ?? ((path) => path);
|
|
3925
|
+
let offset = start;
|
|
3926
|
+
let from = 0;
|
|
3927
|
+
const entries = found.map(({ node, at }) => {
|
|
3928
|
+
offset += utf8Bytes(email.slice(from, at));
|
|
3929
|
+
from = at;
|
|
3930
|
+
return { path: templatePath(node.path), ...node.id ? { nodeId: node.id } : {}, offset };
|
|
3931
|
+
});
|
|
3932
|
+
return { version: BLOCK_MAP_VERSION, unit: "utf8", entries };
|
|
3485
3933
|
}
|
|
3486
3934
|
var isNode3 = (child) => typeof child !== "string";
|
|
3487
|
-
var
|
|
3488
|
-
var same2 = (a2, b) => JSON.stringify(a2) === JSON.stringify(b);
|
|
3935
|
+
var same3 = (a2, b) => JSON.stringify(a2) === JSON.stringify(b);
|
|
3489
3936
|
function support(root, target) {
|
|
3490
3937
|
const d = new Diagnostics();
|
|
3491
3938
|
const visit = (node) => {
|
|
@@ -3493,41 +3940,81 @@ function support(root, target) {
|
|
|
3493
3940
|
const byProp = definition.defaultStyleBy ? definition.defaultStyleBy.values[String(node.props[definition.defaultStyleBy.prop])] : void 0;
|
|
3494
3941
|
for (const [key, value] of Object.entries(node.style)) {
|
|
3495
3942
|
const fallback2 = byProp?.[key] ?? definition.defaultStyle?.[key];
|
|
3496
|
-
|
|
3943
|
+
if (same3(value, fallback2)) continue;
|
|
3944
|
+
reportDeclared(d, node, `${node.path}.style.${key}`, key, value, definition.style[key]?.support?.[target], target);
|
|
3497
3945
|
}
|
|
3498
|
-
for (const [key, value] of Object.entries(node.props))
|
|
3499
|
-
|
|
3946
|
+
for (const [key, value] of Object.entries(node.props)) {
|
|
3947
|
+
if (same3(value, definition.props[key]?.default)) continue;
|
|
3948
|
+
reportDeclared(d, node, `${node.path}.props.${key}`, key, value, definition.props[key]?.support?.[target], target);
|
|
3949
|
+
}
|
|
3950
|
+
for (const condition of CONDITIONS) {
|
|
3951
|
+
const block2 = blockOf(node, condition);
|
|
3952
|
+
if (!block2 || condition.hiddenKey && node.hidden?.[condition.hiddenKey]) continue;
|
|
3953
|
+
const own3 = condition.support[target];
|
|
3954
|
+
reportDeclared(d, node, `${node.path}.${condition.name}`, condition.name, block2, own3, target);
|
|
3955
|
+
if (!own3 || own3.level === "none") continue;
|
|
3956
|
+
for (const [key, value] of Object.entries(block2)) {
|
|
3957
|
+
const rules = condition.styles?.[key]?.[target] ?? definition.style[key]?.support?.[target];
|
|
3958
|
+
reportDeclared(d, node, `${node.path}.${condition.name}.${key}`, key, value, rules, target, condition.outlookApplies);
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
for (const child of node.children) if (isNode3(child) && shownAnywhere(child)) visit(child);
|
|
3500
3962
|
};
|
|
3501
3963
|
visit(root);
|
|
3502
3964
|
return d.list;
|
|
3503
3965
|
}
|
|
3504
|
-
function
|
|
3505
|
-
const rules = declared?.[target];
|
|
3506
|
-
if (!rules || same2(value, fallback2)) return;
|
|
3507
|
-
for (const rule2 of [].concat(rules)) {
|
|
3508
|
-
if (rule2.level === "full") continue;
|
|
3509
|
-
if (rule2.values && !rule2.values.some((v) => same2(v, value))) continue;
|
|
3510
|
-
if (rule2.when && !rule2.when(value, node)) continue;
|
|
3511
|
-
const at = `${node.path}.${where}.${key}`;
|
|
3512
|
-
const asked = typeof value === "string" ? `${key}: "${value}"` : key;
|
|
3513
|
-
const output = target === "web" ? "a page" : target;
|
|
3514
|
-
if (rule2.level === "none") d.warning(`${target}/unsupported`, `${asked} isn't possible in ${output}: ${rule2.shows}`, at, node.id);
|
|
3515
|
-
else if (rule2.level === "partial") d.add(`${target}/degraded`, "info", `${asked} on this ${node.type} in ${output}: ${rule2.shows}`, at, node.id);
|
|
3516
|
-
else d.add("email/outlook-degraded", "info", `${asked} on this ${node.type} in classic Outlook: ${rule2.shows}`, at, node.id);
|
|
3517
|
-
}
|
|
3518
|
-
}
|
|
3519
|
-
function emailSupport(root) {
|
|
3966
|
+
function emailSupport(root, templatePath = (path) => path) {
|
|
3520
3967
|
const d = new Diagnostics();
|
|
3521
3968
|
d.list.push(...support(root, "email"));
|
|
3522
3969
|
const webFonts = new Set((root.props.fonts ?? []).map((face) => face.family.toLowerCase()));
|
|
3970
|
+
const phone = { styled: false, phoneHidden: false, desktopHidden: false };
|
|
3523
3971
|
const visit = (node) => {
|
|
3524
3972
|
structure(node, d);
|
|
3525
3973
|
fallback(node, webFonts, d);
|
|
3974
|
+
if (node.type === "Html") htmlBlock(node, d);
|
|
3975
|
+
if (node.type === "Image") imageFormat(node, "src", d);
|
|
3976
|
+
if (node.type === "Video" && node.props.thumbnail !== void 0) imageFormat(node, "thumbnail", d);
|
|
3977
|
+
if (node.mobile && Object.keys(node.mobile).length) phone.styled = true;
|
|
3978
|
+
if (node.hidden?.mobile && !builtIn(templatePath(node.path))) phone.phoneHidden = true;
|
|
3979
|
+
if (node.hidden?.desktop && !builtIn(templatePath(node.path))) phone.desktopHidden = true;
|
|
3526
3980
|
for (const child of node.children) if (isNode2(child) && shown(child)) visit(child);
|
|
3527
3981
|
};
|
|
3528
3982
|
visit(root);
|
|
3983
|
+
phoneStyles(root, phone, d);
|
|
3529
3984
|
return d.list;
|
|
3530
3985
|
}
|
|
3986
|
+
function builtIn(place) {
|
|
3987
|
+
const made = [...place.matchAll(/\((\w+)\)/g)].pop()?.[1];
|
|
3988
|
+
return !!made && Object.prototype.hasOwnProperty.call(COMPONENTS, made);
|
|
3989
|
+
}
|
|
3990
|
+
var NO_PHONE_STYLES = ["gmail-app-other", "sfr", "laposte", "gmx", "web-de", "rainloop"];
|
|
3991
|
+
function phoneStyles(root, phone, d) {
|
|
3992
|
+
const parts = [phone.desktopHidden && "what's hidden on desktop shows nowhere", phone.phoneHidden && "what's hidden on phones shows", phone.styled && "it keeps its desktop styles"].filter(Boolean);
|
|
3993
|
+
if (!parts.length) return;
|
|
3994
|
+
d.add("email/phone-styles", phone.desktopHidden ? "warning" : "info", `On phones, the email's phone styles don't apply: ${parts.join(", ")} (Rows still stack)`, root.path, root.id, {
|
|
3995
|
+
clients: NO_PHONE_STYLES,
|
|
3996
|
+
fix: phone.desktopHidden ? "show what a reader needs on desktop too, and hide on phones only what they can do without" : void 0,
|
|
3997
|
+
see: caniemail("css-at-media")
|
|
3998
|
+
});
|
|
3999
|
+
}
|
|
4000
|
+
var IMAGE_FORMATS = {
|
|
4001
|
+
webp: { clients: ["outlook"], feature: "image-webp", name: "a WebP" },
|
|
4002
|
+
avif: { clients: ["outlook", "fastmail", "gmx", "web-de", "orange", "sfr", "mail-ru", "ionos"], feature: "image-avif", name: "an AVIF" },
|
|
4003
|
+
svg: { clients: ["mail-ru"], feature: "image-svg", name: "an SVG" }
|
|
4004
|
+
};
|
|
4005
|
+
var FORMAT_IN = [/^data:image\/([a-z]+)/i, /[?&](?:fm|format|f)=(webp|avif|svg|png|jpe?g|gif)\b/i, /\/f_(webp|avif|svg|png|jpe?g|gif)[,/]/i, /\.([a-z]+)(?:[?#]|$)/i];
|
|
4006
|
+
function imageFormat(node, key, d) {
|
|
4007
|
+
const src = assetParts(node.props[key]).src;
|
|
4008
|
+
const format = FORMAT_IN.map((pattern) => pattern.exec(src)?.[1]).find(Boolean)?.toLowerCase();
|
|
4009
|
+
const known = format && Object.prototype.hasOwnProperty.call(IMAGE_FORMATS, format) ? IMAGE_FORMATS[format] : void 0;
|
|
4010
|
+
if (!known) return;
|
|
4011
|
+
const what = key === "src" ? "This Image is" : "This Video's thumbnail is";
|
|
4012
|
+
d.warning("email/image-format", `${what} ${known.name} file, which these email clients don't show`, `${node.path}.props.${key}`, node.id, {
|
|
4013
|
+
clients: known.clients,
|
|
4014
|
+
fix: "use a PNG, JPEG or GIF file",
|
|
4015
|
+
see: caniemail(known.feature)
|
|
4016
|
+
});
|
|
4017
|
+
}
|
|
3531
4018
|
function fallback(node, webFonts, d) {
|
|
3532
4019
|
const stack = node.style.fontFamily;
|
|
3533
4020
|
if (typeof stack !== "string" || !webFonts.size) return;
|
|
@@ -3536,9 +4023,10 @@ function fallback(node, webFonts, d) {
|
|
|
3536
4023
|
if (!last || !webFonts.has(last.toLowerCase())) return;
|
|
3537
4024
|
d.warning(
|
|
3538
4025
|
"email/font-fallback",
|
|
3539
|
-
`This font stack ends with the web font "${last}": where it doesn't load
|
|
4026
|
+
`This font stack ends with the web font "${last}": where it doesn't load, the email client picks its own font`,
|
|
3540
4027
|
`${node.path}.style.fontFamily`,
|
|
3541
|
-
node.id
|
|
4028
|
+
node.id,
|
|
4029
|
+
{ clients: NO_WEB_FONTS, fix: `end it with a font the client has: "${stack}, Arial, sans-serif"`, see: caniemail("css-at-font-face") }
|
|
3542
4030
|
);
|
|
3543
4031
|
}
|
|
3544
4032
|
function structure(node, d) {
|
|
@@ -3551,7 +4039,8 @@ function structure(node, d) {
|
|
|
3551
4039
|
"info",
|
|
3552
4040
|
`What it holds looks about ${past}px wide on one line, wider than its ${node.contentWidth}px (an estimate: text isn't measured yet): classic Outlook can't wrap it onto another line as other clients do, and draws it wider`,
|
|
3553
4041
|
path,
|
|
3554
|
-
id
|
|
4042
|
+
id,
|
|
4043
|
+
{ clients: ["outlook"], fix: "put less in it, or let it stack on phones (stackOnMobile)" }
|
|
3555
4044
|
);
|
|
3556
4045
|
}
|
|
3557
4046
|
const lines = wrapsWithGap(node);
|
|
@@ -3587,6 +4076,86 @@ function structure(node, d) {
|
|
|
3587
4076
|
d.warning("email/image-width", "This Image has no width: Outlook shows it at the size of its file, however wide that is. Give it a width, or the file's width in src", `${path}.props.src`, id);
|
|
3588
4077
|
}
|
|
3589
4078
|
}
|
|
4079
|
+
var HTML_CSS = [
|
|
4080
|
+
{ pattern: /!\s*(?!important\b)[iI][mM][pP][oO][rR][tT][aA][nN][tT]\b/, clients: ["gmail"], removes: "!important not written in lowercase: Gmail removes the whole style attribute, or the whole <style>, that holds it", fix: "write !important in lowercase" },
|
|
4081
|
+
{ pattern: /margin[a-z-]*\s*:([^;"'}]*\s)?-\d/i, clients: ["gmail", "outlook-com"], removes: "a negative margin: the client removes the whole margin declaration", fix: "move it with padding on what holds it instead" },
|
|
4082
|
+
{
|
|
4083
|
+
pattern: /(^|[\s;{"'])position\s*:\s*absolute\b/i,
|
|
4084
|
+
clients: ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail", "gmail-app", "gmail-app-other", "yahoo", "aol", "hey", "sfr", "laposte", "mail-ru", "gmx", "web-de"],
|
|
4085
|
+
removes: "position: absolute: the client removes it",
|
|
4086
|
+
fix: "lay it out without position (a Layer puts content over an image)",
|
|
4087
|
+
feature: "css-position"
|
|
4088
|
+
},
|
|
4089
|
+
{
|
|
4090
|
+
// Outlook.com removes every position (captures); the others not named keep relative.
|
|
4091
|
+
pattern: /(^|[\s;{"'])position\s*:\s*relative\b/i,
|
|
4092
|
+
clients: ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail", "gmail-app", "gmail-app-other", "mail-ru", "gmx", "web-de"],
|
|
4093
|
+
removes: "position: relative: the client removes it, and what it moves stays where it is",
|
|
4094
|
+
fix: "move it with padding instead",
|
|
4095
|
+
feature: "css-position",
|
|
4096
|
+
use: "relative"
|
|
4097
|
+
},
|
|
4098
|
+
{
|
|
4099
|
+
pattern: /var\(\s*--|(^|[\s;{"'])--[\w-]+\s*:/i,
|
|
4100
|
+
clients: ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail", "gmail-app", "gmail-app-other", "yahoo", "aol", "orange", "mail-ru", "fastmail", "gmx", "web-de"],
|
|
4101
|
+
removes: "CSS variables: the client removes var() and their declarations",
|
|
4102
|
+
fix: "write the values themselves",
|
|
4103
|
+
feature: "css-variables"
|
|
4104
|
+
},
|
|
4105
|
+
{
|
|
4106
|
+
pattern: /gradient\(/i,
|
|
4107
|
+
clients: ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail-app", "gmail-app-other", "yahoo", "aol", "orange", "gmx", "web-de"],
|
|
4108
|
+
removes: "a gradient: the client removes it",
|
|
4109
|
+
fix: "give it a solid background color too, or use an image",
|
|
4110
|
+
feature: "css-linear-gradient"
|
|
4111
|
+
},
|
|
4112
|
+
{ pattern: /\b(oklch|oklab|lch|lab|hwb|color-mix|color)\(/i, clients: ["gmail", "outlook-com"], removes: "a newer color syntax: Outlook.com removes the declaration, and Gmail every inline style on its element", fix: "write the color as hex or rgb()" },
|
|
4113
|
+
{ pattern: /rgba?\(\s*[\d.]+%?\s+[\d.]/i, clients: ["gmail"], removes: "rgb() with spaces between its numbers: Gmail removes the whole style attribute, or the whole <style>", fix: "separate them with commas: rgb(8, 121, 161)" },
|
|
4114
|
+
{
|
|
4115
|
+
pattern: /<style[\s>]/i,
|
|
4116
|
+
clients: ["gmail", "gmail-app", "gmail-app-other", "protonmail", "gmx", "web-de", "sfr", "laposte", "rainloop"],
|
|
4117
|
+
removes: "a <style> in the email's body: the client removes it, and what it styles shows without it",
|
|
4118
|
+
fix: "write the styles inline, on the elements they're for",
|
|
4119
|
+
feature: "html-style"
|
|
4120
|
+
},
|
|
4121
|
+
{ pattern: /box-shadow\s*:/i, clients: ["outlook", "gmail", "gmail-app", "yahoo", "aol", "orange", "gmx", "web-de"], removes: "box-shadow: the client removes it", fix: "draw it with a border, or leave it out", feature: "css-box-shadow" },
|
|
4122
|
+
{ pattern: /text-shadow\s*:/i, clients: ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail", "gmail-app", "gmail-app-other"], removes: "text-shadow: the client removes it", fix: "leave it out, and keep the text readable without it", feature: "css-text-shadow" },
|
|
4123
|
+
{ pattern: /display\s*:\s*flex\b/i, clients: ["outlook", "gmail-app-other", "orange", "gmx", "web-de"], removes: "display: flex: the client lays it out as blocks", fix: "lay it out with a Row", feature: "css-display-flex" },
|
|
4124
|
+
{
|
|
4125
|
+
pattern: /display\s*:\s*inline-flex\b/i,
|
|
4126
|
+
clients: ["outlook", "gmail-app-other", "yahoo", "aol", "orange", "gmx", "web-de"],
|
|
4127
|
+
removes: "display: inline-flex: the client lays it out without flex",
|
|
4128
|
+
fix: "lay it out with a Row",
|
|
4129
|
+
feature: "css-display-flex",
|
|
4130
|
+
use: "inline"
|
|
4131
|
+
},
|
|
4132
|
+
{ pattern: /display\s*:\s*(inline-)?grid\b/i, clients: ["outlook", "gmail-app-other", "orange", "gmx", "web-de"], removes: "display: grid: the client lays it out as blocks", fix: "lay it out with a Row or a Grid", feature: "css-display-grid" },
|
|
4133
|
+
{ pattern: /(^|[\s;{"'])opacity\s*:/i, clients: ["outlook", "aol", "orange", "mail-ru", "gmx", "web-de"], removes: "opacity: the client shows it opaque", fix: "use a lighter color instead", feature: "css-opacity" },
|
|
4134
|
+
{
|
|
4135
|
+
pattern: /object-fit\s*:/i,
|
|
4136
|
+
clients: ["outlook", "gmail-app-other", "yahoo", "aol", "orange", "gmx", "web-de"],
|
|
4137
|
+
removes: "object-fit: the client stretches the image to its box",
|
|
4138
|
+
fix: "crop the image file to the size it's shown at",
|
|
4139
|
+
feature: "css-object-fit"
|
|
4140
|
+
},
|
|
4141
|
+
{
|
|
4142
|
+
pattern: /(^|[\s;{"'])aspect-ratio\s*:/i,
|
|
4143
|
+
clients: ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail", "gmail-app", "gmail-app-other", "yahoo", "aol", "samsung-email", "thunderbird", "fastmail", "gmx", "web-de", "orange"],
|
|
4144
|
+
removes: "aspect-ratio: the client removes it",
|
|
4145
|
+
fix: "give it a width and a height",
|
|
4146
|
+
feature: "css-aspect-ratio"
|
|
4147
|
+
}
|
|
4148
|
+
];
|
|
4149
|
+
var OUTLOOK_ONLY2 = /<!--\[if\s+(?:(?:gte?|lte?)\s+)?mso[^\]]*\]>[\s\S]*?<!\[endif\]-->/gi;
|
|
4150
|
+
function htmlBlock(node, d) {
|
|
4151
|
+
if (typeof node.props.html !== "string") return;
|
|
4152
|
+
const markup2 = node.props.html.replace(OUTLOOK_ONLY2, "");
|
|
4153
|
+
for (const check of HTML_CSS) {
|
|
4154
|
+
if (check.pattern.test(markup2)) {
|
|
4155
|
+
d.add("email/html-css", "info", `This Html block's markup may have ${check.removes}`, `${node.path}.props.html`, node.id, { clients: check.clients, fix: check.fix, see: check.feature && caniemail(check.feature) });
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
3590
4159
|
var isNode4 = (child) => typeof child !== "string";
|
|
3591
4160
|
var CLEARLY = 1.25;
|
|
3592
4161
|
var sides = (value) => {
|
|
@@ -3718,15 +4287,15 @@ function classesFor(node, parent) {
|
|
|
3718
4287
|
const classes = [];
|
|
3719
4288
|
if (node.mobile) {
|
|
3720
4289
|
const { width, ...rest } = node.mobile;
|
|
3721
|
-
if (Object.keys(rest).length) classes.push(rule(
|
|
4290
|
+
if (Object.keys(rest).length) classes.push(rule(MOBILE.sheet.media, `${MOBILE.sheet.selector}{${css2(declarationsOf(rest), true)}}`));
|
|
3722
4291
|
const line22 = node.type === "Divider" && node.style.padding !== void 0 ? ">hr" : "";
|
|
3723
|
-
if (width !== void 0) classes.push(rule(
|
|
4292
|
+
if (width !== void 0) classes.push(rule(MOBILE.sheet.media, `${MOBILE.sheet.selector}${line22}{${css2(declarationsOf({ width }), true)}}`));
|
|
3724
4293
|
const own3 = node.style.width === void 0 && (node.style.flex === "none" || node.style.flex === 0);
|
|
3725
4294
|
const basis = own3 ? void 0 : length2(phoneWidthInRow(node, parent));
|
|
3726
4295
|
if (basis !== void 0) classes.push(rule("phone", `&{flex:0 1 ${basis}!important}`));
|
|
3727
4296
|
}
|
|
3728
4297
|
classes.push(...hidingClasses(node.hidden));
|
|
3729
|
-
if (node.hover) classes.push(rule(
|
|
4298
|
+
if (node.hover) classes.push(rule(HOVER.sheet.media, `${HOVER.sheet.selector}{${css2(declarationsOf(node.hover), true)}}`, HOVER.sheet.level));
|
|
3730
4299
|
if (node.type === "Row" && node.props.stackOnMobile !== false) {
|
|
3731
4300
|
classes.push(
|
|
3732
4301
|
rule("phone", "&{flex-direction:column!important;align-items:stretch!important}&>*{flex:0 0 auto!important;width:auto!important;max-width:100%!important;align-self:stretch!important}", "layout")
|
|
@@ -3934,17 +4503,17 @@ var Layer3 = ({ node, style, parent, placement, classes, children }) => /* @__PU
|
|
|
3934
4503
|
},
|
|
3935
4504
|
children()
|
|
3936
4505
|
);
|
|
3937
|
-
var
|
|
4506
|
+
var current2 = { page: 1, total: 1 };
|
|
3938
4507
|
function numbering(numbers, write3) {
|
|
3939
|
-
const before =
|
|
3940
|
-
|
|
4508
|
+
const before = current2;
|
|
4509
|
+
current2 = numbers;
|
|
3941
4510
|
try {
|
|
3942
4511
|
return write3();
|
|
3943
4512
|
} finally {
|
|
3944
|
-
|
|
4513
|
+
current2 = before;
|
|
3945
4514
|
}
|
|
3946
4515
|
}
|
|
3947
|
-
var PageNumber2 = ({ node }) => /* @__PURE__ */ h(Fragment, null, String(node.props.total ?
|
|
4516
|
+
var PageNumber2 = ({ node }) => /* @__PURE__ */ h(Fragment, null, String(node.props.total ? current2.total : current2.page));
|
|
3948
4517
|
function documentCss(root) {
|
|
3949
4518
|
const box2 = pageBox(root.props);
|
|
3950
4519
|
return `@page{size:${box2.css};margin:0}body{-webkit-print-color-adjust:exact;print-color-adjust:exact}@media screen{body{background:#e5e7eb;padding:24px 0}.elements-page{box-shadow:0 1px 4px rgba(0,0,0,.2)}.elements-page+.elements-page{margin-top:24px!important}}`;
|
|
@@ -4118,8 +4687,23 @@ var Video3 = ({ node, style, placement, classes }) => /* @__PURE__ */ h(
|
|
|
4118
4687
|
))
|
|
4119
4688
|
);
|
|
4120
4689
|
var PAGE_PHONE_WIDTH = 480;
|
|
4121
|
-
|
|
4122
|
-
|
|
4690
|
+
function pagePhoneWidth(root) {
|
|
4691
|
+
const mobile = root.props.breakpoints?.mobile;
|
|
4692
|
+
return typeof mobile === "number" ? mobile : PAGE_PHONE_WIDTH;
|
|
4693
|
+
}
|
|
4694
|
+
function pageBreakpoints(root) {
|
|
4695
|
+
const mobile = root.props.breakpoints?.mobile;
|
|
4696
|
+
const width = typeof root.props.width === "number" ? root.props.width : void 0;
|
|
4697
|
+
if (root.type !== "Page" || typeof mobile !== "number" || width === void 0 || mobile < width) return [];
|
|
4698
|
+
return [
|
|
4699
|
+
{
|
|
4700
|
+
code: "page/phone-width",
|
|
4701
|
+
severity: "warning",
|
|
4702
|
+
message: `Its phone styles apply to every window up to ${mobile}px, wider than its ${width}px content: on those, the content is at its full width with its phone styles. Set breakpoints.mobile below ${width}`,
|
|
4703
|
+
path: "root.props.breakpoints"
|
|
4704
|
+
}
|
|
4705
|
+
];
|
|
4706
|
+
}
|
|
4123
4707
|
var COMPONENTS2 = {
|
|
4124
4708
|
Section: Section3,
|
|
4125
4709
|
Row: Row3,
|
|
@@ -4180,7 +4764,8 @@ function renderPage(resolved, options = {}) {
|
|
|
4180
4764
|
const writer2 = document ? Document2 : root.type === "Canvas" ? Canvas2 : Page2;
|
|
4181
4765
|
const body = withStylesheet(sheet, () => String(writer2(propsOf2(root, root, [], options))));
|
|
4182
4766
|
const fixed = !root.definition.responsive;
|
|
4183
|
-
const
|
|
4767
|
+
const phoneWidth2 = pagePhoneWidth(root);
|
|
4768
|
+
const stylesheet = [...fonts, sheet.css(fixed ? null : `@media (max-width: ${phoneWidth2}px)`, fixed ? null : `@media (min-width: ${phoneWidth2 + 1}px)`), document ? documentCss(root) : ""].join("");
|
|
4184
4769
|
const head = [
|
|
4185
4770
|
'<meta charset="utf-8">',
|
|
4186
4771
|
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
@@ -4200,7 +4785,7 @@ function renderText(resolved, options = {}) {
|
|
|
4200
4785
|
const tag = options.mergeTag ?? ((name) => `{{${name}}}`);
|
|
4201
4786
|
const inline2 = (node, upper = false) => node.children.map((child) => {
|
|
4202
4787
|
if (typeof child === "string") return upper ? child.toUpperCase() : child;
|
|
4203
|
-
if (child
|
|
4788
|
+
if (!shownAnywhere(child)) return "";
|
|
4204
4789
|
switch (child.type) {
|
|
4205
4790
|
case "LineBreak":
|
|
4206
4791
|
return "\n";
|
|
@@ -4224,7 +4809,7 @@ function renderText(resolved, options = {}) {
|
|
|
4224
4809
|
return alt ? `[${upper ? alt.toUpperCase() : alt}]` : "";
|
|
4225
4810
|
};
|
|
4226
4811
|
const blocks = (node) => node.children.flatMap((child) => {
|
|
4227
|
-
if (typeof child === "string" || child
|
|
4812
|
+
if (typeof child === "string" || !shownAnywhere(child)) return [];
|
|
4228
4813
|
switch (child.type) {
|
|
4229
4814
|
case "Heading":
|
|
4230
4815
|
return [tidy(inline2(child, Number(child.props.level) <= 2))];
|
|
@@ -4262,8 +4847,8 @@ function renderText(resolved, options = {}) {
|
|
|
4262
4847
|
return blocks(child);
|
|
4263
4848
|
}
|
|
4264
4849
|
});
|
|
4265
|
-
const
|
|
4266
|
-
const list = (node, indent) => node.children.filter((child) => typeof child !== "string" &&
|
|
4850
|
+
const shown2 = (node) => shownAnywhere(node);
|
|
4851
|
+
const list = (node, indent) => node.children.filter((child) => typeof child !== "string" && shown2(child)).map((item, index) => {
|
|
4267
4852
|
const marker2 = node.props.type === "number" ? `${Number(node.props.start) + index}. ` : "- ";
|
|
4268
4853
|
const lines = [];
|
|
4269
4854
|
let run = [];
|
|
@@ -4279,13 +4864,13 @@ function renderText(resolved, options = {}) {
|
|
|
4279
4864
|
}
|
|
4280
4865
|
flush();
|
|
4281
4866
|
if (!lines.length) lines.push(`${indent}${marker2}`.trimEnd());
|
|
4282
|
-
if (
|
|
4867
|
+
if (shown2(child)) lines.push(list(child, `${indent} `));
|
|
4283
4868
|
}
|
|
4284
4869
|
flush();
|
|
4285
4870
|
return (lines.length ? lines : [`${indent}${marker2}`.trimEnd()]).join("\n");
|
|
4286
4871
|
}).join("\n");
|
|
4287
|
-
const table = (node) => node.children.filter((row) => typeof row !== "string" &&
|
|
4288
|
-
(row) => row.children.filter((cell) => typeof cell !== "string" &&
|
|
4872
|
+
const table = (node) => node.children.filter((row) => typeof row !== "string" && shown2(row)).map(
|
|
4873
|
+
(row) => row.children.filter((cell) => typeof cell !== "string" && shown2(cell)).map((cell) => tidy(inline2(cell))).join(" | ")
|
|
4289
4874
|
).join("\n");
|
|
4290
4875
|
return blocks(resolved.root).filter((block2) => block2.length > 0).join("\n\n");
|
|
4291
4876
|
}
|
|
@@ -4328,52 +4913,56 @@ var OUTPUTS = {
|
|
|
4328
4913
|
Email: { target: "email", ready: true }
|
|
4329
4914
|
};
|
|
4330
4915
|
function prepareTemplate(input, options = {}) {
|
|
4916
|
+
checkEmailClients(options.emailClients);
|
|
4331
4917
|
const registry = registryOf(options);
|
|
4332
4918
|
const checked = checkDocument(input, registry);
|
|
4333
4919
|
const diagnostics = [...checked.diagnostics];
|
|
4334
4920
|
const failed = () => diagnostics.some((d) => d.severity === "error");
|
|
4335
4921
|
if (!checked.document || failed()) return { resolved: null, target: null, diagnostics };
|
|
4336
4922
|
const root = checked.document.root.type;
|
|
4923
|
+
const done = (resolved2, target) => {
|
|
4924
|
+
if (root !== "Email" && options.emailClients) diagnostics.push({ code: "email/clients-ignored", severity: "info", message: `emailClients is for an Email; a ${root} isn't checked for email clients`, path: "root" });
|
|
4925
|
+
return { resolved: resolved2, target, diagnostics: root === "Email" ? forEmailClients(diagnostics, options.emailClients) : diagnostics };
|
|
4926
|
+
};
|
|
4337
4927
|
const output = OUTPUTS[root];
|
|
4338
4928
|
if (!output?.ready) {
|
|
4339
4929
|
diagnostics.push({ code: "output/unavailable", severity: "error", message: output?.later ?? `A ${root} can't be rendered yet`, path: "root" });
|
|
4340
|
-
return
|
|
4930
|
+
return done(null, null);
|
|
4341
4931
|
}
|
|
4342
|
-
const expanded = expandDocument(checked.document, registry);
|
|
4932
|
+
const expanded = expandDocument(checked.document, registry, output.target);
|
|
4343
4933
|
diagnostics.push(...expanded.diagnostics);
|
|
4344
|
-
if (failed()) return
|
|
4934
|
+
if (failed()) return done(null, output.target);
|
|
4345
4935
|
diagnostics.push(...checkTargetExpanded(expanded.document, output.target, registry, expanded.paths));
|
|
4346
|
-
if (failed()) return
|
|
4936
|
+
if (failed()) return done(null, output.target);
|
|
4347
4937
|
const resolved = resolveExpanded(expanded.document, registry, expanded.paths);
|
|
4348
4938
|
diagnostics.push(...resolved.diagnostics);
|
|
4349
|
-
const found = output.target === "email" ? emailSupport(resolved.root) : support(resolved.root, output.target);
|
|
4939
|
+
const found = output.target === "email" ? emailSupport(resolved.root, resolved.templatePath) : support(resolved.root, output.target);
|
|
4350
4940
|
if (root === "Document" || root === "Canvas") found.push(...overflow(resolved.root));
|
|
4351
|
-
|
|
4941
|
+
found.push(...pageBreakpoints(resolved.root));
|
|
4352
4942
|
diagnostics.push(...inTemplate(resolved, found));
|
|
4353
|
-
return
|
|
4943
|
+
return done(failed() ? null : resolved, output.target);
|
|
4354
4944
|
}
|
|
4355
4945
|
function inTemplate(resolved, diagnostics) {
|
|
4356
4946
|
const { templatePath } = resolved;
|
|
4357
4947
|
return templatePath ? diagnostics.map((diagnostic) => ({ ...diagnostic, path: templatePath(diagnostic.path) })) : diagnostics;
|
|
4358
4948
|
}
|
|
4359
|
-
function pageNumbersOutside(root) {
|
|
4360
|
-
const out = [];
|
|
4361
|
-
const visit = (node) => {
|
|
4362
|
-
if (node.type === "PageNumber") {
|
|
4363
|
-
out.push({ code: "document/page-number", severity: "error", message: `A PageNumber is a page's number in a Document; this ${root.type} has no pages`, path: node.path, ...node.id ? { nodeId: node.id } : {} });
|
|
4364
|
-
}
|
|
4365
|
-
for (const child of node.children) if (typeof child !== "string") visit(child);
|
|
4366
|
-
};
|
|
4367
|
-
visit(root);
|
|
4368
|
-
return out;
|
|
4369
|
-
}
|
|
4370
4949
|
function renderTemplate(input, options = {}) {
|
|
4950
|
+
if (options.blockMap && options.pretty) throw new ElementsError([{ code: "render/block-map-pretty", severity: "error", message: "blockMap gives offsets in the compact HTML; leave pretty off", path: "root" }]);
|
|
4371
4951
|
const { resolved, target, diagnostics } = prepareTemplate(input, options);
|
|
4372
4952
|
if (!resolved) throw new ElementsError(diagnostics);
|
|
4373
4953
|
const text22 = renderText(resolved, options);
|
|
4374
4954
|
const written = target === "email" ? renderEmail(resolved, options) : { ...renderPage(resolved, options), diagnostics: [] };
|
|
4375
|
-
const
|
|
4376
|
-
|
|
4955
|
+
const shown2 = (html2) => options.pretty ? prettyHtml(html2) : html2;
|
|
4956
|
+
const blocks = "blocks" in written ? written.blocks : void 0;
|
|
4957
|
+
return {
|
|
4958
|
+
html: shown2(written.html),
|
|
4959
|
+
head: shown2(written.head),
|
|
4960
|
+
body: shown2(written.body),
|
|
4961
|
+
bodyStyle: written.bodyStyle,
|
|
4962
|
+
text: text22,
|
|
4963
|
+
diagnostics: [...diagnostics, ...inTemplate(resolved, forEmailClients(written.diagnostics, options.emailClients))],
|
|
4964
|
+
...blocks ? { blocks } : {}
|
|
4965
|
+
};
|
|
4377
4966
|
}
|
|
4378
4967
|
|
|
4379
4968
|
// src/utils/collect.ts
|
|
@@ -4404,7 +4993,7 @@ function isAsyncComponent(type) {
|
|
|
4404
4993
|
}
|
|
4405
4994
|
var SHARED2 = Symbol.for("@hyper-elements/react/render-builds");
|
|
4406
4995
|
var using = globalThis[SHARED2] ?? (globalThis[SHARED2] = []);
|
|
4407
|
-
var
|
|
4996
|
+
var same4 = (a2, b) => a2.renderToString === b.renderToString;
|
|
4408
4997
|
function dispatcher() {
|
|
4409
4998
|
const internals = React2;
|
|
4410
4999
|
return internals.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE?.H ?? internals.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?.ReactCurrentDispatcher?.current ?? null;
|
|
@@ -4450,7 +5039,7 @@ function secondBuild(outer) {
|
|
|
4450
5039
|
for (const requirer of requirers) {
|
|
4451
5040
|
try {
|
|
4452
5041
|
const build = requirer()?.(id);
|
|
4453
|
-
if (build?.renderToStaticMarkup && !
|
|
5042
|
+
if (build?.renderToStaticMarkup && !same4(build, defaultBuild) && ![null, outer].includes(dispatcherOf(build))) {
|
|
4454
5043
|
second = build;
|
|
4455
5044
|
break search;
|
|
4456
5045
|
}
|
|
@@ -4464,11 +5053,11 @@ function secondBuild(outer) {
|
|
|
4464
5053
|
var buildOf = (outer) => outer === null ? void 0 : [defaultBuild, second].find((b) => !!b && dispatcherOf(b) === outer);
|
|
4465
5054
|
function withBuild(render) {
|
|
4466
5055
|
const outer = dispatcher();
|
|
4467
|
-
let build =
|
|
5056
|
+
let build = current3();
|
|
4468
5057
|
const theirs = using.length === 0 || collecting() !== void 0 ? buildOf(outer) : void 0;
|
|
4469
5058
|
const busy = [];
|
|
4470
5059
|
if (theirs) {
|
|
4471
|
-
const free = (b) => !
|
|
5060
|
+
const free = (b) => !same4(b, theirs) && !using.some((u) => same4(u, b));
|
|
4472
5061
|
const other = free(defaultBuild) ? defaultBuild : secondBuild(outer);
|
|
4473
5062
|
if (other && free(other)) {
|
|
4474
5063
|
busy.push(theirs);
|
|
@@ -4493,10 +5082,10 @@ function rendersHere(build) {
|
|
|
4493
5082
|
return false;
|
|
4494
5083
|
}
|
|
4495
5084
|
}
|
|
4496
|
-
function
|
|
5085
|
+
function current3() {
|
|
4497
5086
|
return using[using.length - 1] ?? defaultBuild;
|
|
4498
5087
|
}
|
|
4499
|
-
var renderToString = (element2, options) =>
|
|
5088
|
+
var renderToString = (element2, options) => current3().renderToString(element2, options);
|
|
4500
5089
|
|
|
4501
5090
|
// src/utils/render-scope.ts
|
|
4502
5091
|
function withRenderScope(render) {
|
|
@@ -4545,7 +5134,7 @@ function withoutAsync(children, collection) {
|
|
|
4545
5134
|
}
|
|
4546
5135
|
var ENTITIES2 = { "&": "&", "<": "<", ">": ">", """: '"', "'": "'" };
|
|
4547
5136
|
var unescape = (text4) => text4.replace(/&|<|>|"|'/g, (entity) => ENTITIES2[entity]);
|
|
4548
|
-
var NODE_KEYS2 = /* @__PURE__ */ new Set(["id", "style", "
|
|
5137
|
+
var NODE_KEYS2 = /* @__PURE__ */ new Set(["id", "style", "hidden"]);
|
|
4549
5138
|
function toJsonNode({ type, props }, diagnostics) {
|
|
4550
5139
|
const node = { type, children: [] };
|
|
4551
5140
|
const semantic = {};
|
|
@@ -4670,10 +5259,10 @@ function collectTemplate(element2) {
|
|
|
4670
5259
|
path: "jsx"
|
|
4671
5260
|
});
|
|
4672
5261
|
}
|
|
4673
|
-
return { document: null, diagnostics, components };
|
|
5262
|
+
return { document: null, diagnostics, components, collected: null };
|
|
4674
5263
|
}
|
|
4675
5264
|
const result = checkDocument({ root: top[0] }, new Registry(components));
|
|
4676
|
-
return { document: result.document, diagnostics: [...diagnostics, ...result.diagnostics], components };
|
|
5265
|
+
return { document: result.document, diagnostics: [...diagnostics, ...result.diagnostics], components, collected: top[0] };
|
|
4677
5266
|
}
|
|
4678
5267
|
|
|
4679
5268
|
// src/primitives/components.tsx
|
|
@@ -4744,9 +5333,8 @@ function propsOf3(node) {
|
|
|
4744
5333
|
return {
|
|
4745
5334
|
...node.id !== void 0 ? { id: node.id } : {},
|
|
4746
5335
|
...node.props,
|
|
4747
|
-
|
|
4748
|
-
...node.mobile ? { mobile: node.mobile } : {},
|
|
4749
|
-
...node.hover ? { hover: node.hover } : {},
|
|
5336
|
+
// Phone and hover styles inside the style, as a template writes them now.
|
|
5337
|
+
...node.style && Object.keys(node.style).length || node.mobile || node.hover ? { style: { ...node.style, ...node.mobile ? { mobile: node.mobile } : {}, ...node.hover ? { hover: node.hover } : {} } } : {},
|
|
4750
5338
|
...node.hidden ? { hidden: node.hidden } : {}
|
|
4751
5339
|
};
|
|
4752
5340
|
}
|
|
@@ -4800,15 +5388,15 @@ function text2(value, flat) {
|
|
|
4800
5388
|
return kept ? value : `{${JSON.stringify(value)}}`;
|
|
4801
5389
|
}
|
|
4802
5390
|
var inline = (node) => (node.children ?? []).some((child) => typeof child === "string" || ["Span", "Link", "LineBreak", "MergeTag", "PageNumber"].includes(child.type));
|
|
4803
|
-
function element(node, indent, flat, path,
|
|
5391
|
+
function element(node, indent, flat, path, report) {
|
|
4804
5392
|
if (!TYPE_NAME.test(node.type)) {
|
|
4805
|
-
|
|
5393
|
+
report.push(`${path}: ${JSON.stringify(node.type)} isn't a component's name: it's left out of the code`);
|
|
4806
5394
|
return flat ? "" : "{/* left out: not a component's name */}";
|
|
4807
5395
|
}
|
|
4808
5396
|
const name = tagName(node.type);
|
|
4809
|
-
const attributes5 = Object.entries(propsOf3(node)).filter(([key]) => PROP_NAME.test(key) || (
|
|
5397
|
+
const attributes5 = Object.entries(propsOf3(node)).filter(([key]) => PROP_NAME.test(key) || (report.push(`${path}: a prop named ${JSON.stringify(key)} isn't one JSX can write: it's left out of the code`), false)).map(([key, value]) => ` ${attribute(key, value)}`).join("");
|
|
4810
5398
|
const children = node.children ?? [];
|
|
4811
|
-
const child = (c, i, inner2, oneLine) => typeof c === "string" ? text2(c, oneLine) : element(c, inner2, oneLine, `${path}.children[${i}]`,
|
|
5399
|
+
const child = (c, i, inner2, oneLine) => typeof c === "string" ? text2(c, oneLine) : element(c, inner2, oneLine, `${path}.children[${i}]`, report);
|
|
4812
5400
|
if (!children.length) return `<${name}${attributes5} />`;
|
|
4813
5401
|
if (flat || inline(node)) return `<${name}${attributes5}>${children.map((c, i) => child(c, i, indent, true)).join("")}</${name}>`;
|
|
4814
5402
|
const inner = `${indent} `;
|
|
@@ -4816,9 +5404,9 @@ function element(node, indent, flat, path, report2) {
|
|
|
4816
5404
|
${children.map((c, i) => `${inner}${child(c, i, inner, false)}`).join("\n")}
|
|
4817
5405
|
${indent}</${name}>`;
|
|
4818
5406
|
}
|
|
4819
|
-
function printTemplate(root, name = "Template",
|
|
5407
|
+
function printTemplate(root, name = "Template", report = []) {
|
|
4820
5408
|
if (!IDENTIFIER.test(name)) {
|
|
4821
|
-
|
|
5409
|
+
report.push(`the component's name ${JSON.stringify(name)} isn't one JavaScript can write: it's named Template`);
|
|
4822
5410
|
name = "Template";
|
|
4823
5411
|
}
|
|
4824
5412
|
const used = /* @__PURE__ */ new Set();
|
|
@@ -4835,7 +5423,7 @@ function printTemplate(root, name = "Template", report2 = []) {
|
|
|
4835
5423
|
|
|
4836
5424
|
export default function ${name}() {
|
|
4837
5425
|
return (
|
|
4838
|
-
${element(root, " ", false, "root",
|
|
5426
|
+
${element(root, " ", false, "root", report)}
|
|
4839
5427
|
);
|
|
4840
5428
|
}
|
|
4841
5429
|
`;
|
|
@@ -5356,7 +5944,7 @@ function readHidden2(tool, type, values, say) {
|
|
|
5356
5944
|
// src/builder/malformed.ts
|
|
5357
5945
|
var DEPTH = 32;
|
|
5358
5946
|
var coerces = (value) => ["toString", "valueOf"].some((key) => Object.hasOwn(value, key) && typeof value[key] !== "function");
|
|
5359
|
-
function withoutMalformed(design,
|
|
5947
|
+
function withoutMalformed(design, report) {
|
|
5360
5948
|
const found = [];
|
|
5361
5949
|
const walk = (value, path) => {
|
|
5362
5950
|
if (!value || typeof value !== "object" || path.length > DEPTH) return;
|
|
@@ -5374,7 +5962,7 @@ function withoutMalformed(design, report2) {
|
|
|
5374
5962
|
const last = path[path.length - 1];
|
|
5375
5963
|
if (Array.isArray(holder)) holder[Number(last)] = null;
|
|
5376
5964
|
else if (holder) delete holder[last];
|
|
5377
|
-
|
|
5965
|
+
report.push(`${path.map((key, i) => /^\d+$/.test(key) ? `[${key}]` : i ? `.${key}` : key).join("")}: a value whose own toString or valueOf isn't a function can't be read: left out`);
|
|
5378
5966
|
}
|
|
5379
5967
|
return copy2;
|
|
5380
5968
|
}
|
|
@@ -5383,8 +5971,8 @@ function withoutMalformed(design, report2) {
|
|
|
5383
5971
|
var ZERO = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
5384
5972
|
var obj = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
5385
5973
|
var str = (value) => typeof value === "string" ? value : void 0;
|
|
5386
|
-
var
|
|
5387
|
-
var unless = (value, taken) => value === void 0 ||
|
|
5974
|
+
var same5 = (a2, b) => JSON.stringify(a2)?.toLowerCase() === JSON.stringify(b)?.toLowerCase();
|
|
5975
|
+
var unless = (value, taken) => value === void 0 || same5(value, taken) ? void 0 : value;
|
|
5388
5976
|
var lineHeight4 = (value) => {
|
|
5389
5977
|
const read2 = lineHeight3(value);
|
|
5390
5978
|
return typeof read2 === "string" && read2.endsWith("%") ? Math.round(parseFloat(read2) * 100) / 1e4 : read2;
|
|
@@ -5433,7 +6021,7 @@ function phoneText2(values) {
|
|
|
5433
6021
|
}
|
|
5434
6022
|
|
|
5435
6023
|
// src/builder/read-runs.ts
|
|
5436
|
-
var
|
|
6024
|
+
var TAG3 = /<(\/?)([a-zA-Z][a-zA-Z0-9]*)([^>]*?)\/?\s*>/g;
|
|
5437
6025
|
var ATTRIBUTE = /([a-zA-Z-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
5438
6026
|
var ENTITIES3 = {
|
|
5439
6027
|
amp: "&",
|
|
@@ -5574,16 +6162,16 @@ function parseRuns(html2, { weight = 400, links = true, builder = false } = {})
|
|
|
5574
6162
|
const left = [];
|
|
5575
6163
|
if (/<[!?]|<\/(?![a-zA-Z])/.test(html2)) left.push("a comment or a processing instruction");
|
|
5576
6164
|
const lines = [];
|
|
5577
|
-
let
|
|
5578
|
-
const stack = [
|
|
6165
|
+
let current4 = { tag: "", node: null, children: [], weight };
|
|
6166
|
+
const stack = [current4];
|
|
5579
6167
|
const top = () => stack[stack.length - 1];
|
|
5580
6168
|
const endParagraph = (last2 = false) => {
|
|
5581
6169
|
if (stack.length > 1) left.push("a paragraph inside text that's styled");
|
|
5582
|
-
const runs = tidy2(
|
|
6170
|
+
const runs = tidy2(current4.children);
|
|
5583
6171
|
if (runs.length > 1 && isBreak(runs[runs.length - 1]) && !(builder && last2)) runs.pop();
|
|
5584
6172
|
if (runs.length) lines.push(runs.length === 1 && isBreak(runs[0]) ? [] : runs);
|
|
5585
|
-
|
|
5586
|
-
stack.splice(0, stack.length,
|
|
6173
|
+
current4 = { tag: "", node: null, children: [], weight };
|
|
6174
|
+
stack.splice(0, stack.length, current4);
|
|
5587
6175
|
};
|
|
5588
6176
|
let last = 0;
|
|
5589
6177
|
let afterParagraph = false;
|
|
@@ -5593,7 +6181,7 @@ function parseRuns(html2, { weight = 400, links = true, builder = false } = {})
|
|
|
5593
6181
|
const value = decode3(raw2, left);
|
|
5594
6182
|
if (value) top().children.push(value);
|
|
5595
6183
|
};
|
|
5596
|
-
for (const match of html2.matchAll(
|
|
6184
|
+
for (const match of html2.matchAll(TAG3)) {
|
|
5597
6185
|
const tag = match[2].toLowerCase();
|
|
5598
6186
|
text4(html2.slice(last, match.index), tag === "p" || tag === "div");
|
|
5599
6187
|
last = match.index + match[0].length;
|
|
@@ -5936,7 +6524,7 @@ function textJsonHtml(textJson) {
|
|
|
5936
6524
|
}
|
|
5937
6525
|
|
|
5938
6526
|
// src/builder/text.ts
|
|
5939
|
-
var
|
|
6527
|
+
var TAG4 = /<\s*(\/?)\s*([a-zA-Z][a-zA-Z0-9]*)([^>]*?)(\/?)\s*>/g;
|
|
5940
6528
|
var styleOf2 = (attrs2) => /\bstyle\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(attrs2)?.slice(1).find((v) => v !== void 0) ?? "";
|
|
5941
6529
|
var declarations2 = (css3) => Object.fromEntries(css3.split(";").map((d) => d.split(":")).filter((p) => p.length >= 2 && p[0].trim()).map(([name, ...rest]) => [name.trim().toLowerCase(), rest.join(":").trim()]));
|
|
5942
6530
|
var ATTRIBUTES = `(?:[^>"']|"[^"]*"|'[^']*')*`;
|
|
@@ -6025,7 +6613,7 @@ var DEEPEST = 64;
|
|
|
6025
6613
|
var VOID3 = /* @__PURE__ */ new Set(["br", "img", "hr", "input", "meta", "link", "wbr", "source", "area", "col", "embed", "param", "track"]);
|
|
6026
6614
|
function tooDeep(html2) {
|
|
6027
6615
|
let depth = 0;
|
|
6028
|
-
for (const match of html2.matchAll(
|
|
6616
|
+
for (const match of html2.matchAll(TAG4)) {
|
|
6029
6617
|
if (match[1]) depth = Math.max(0, depth - 1);
|
|
6030
6618
|
else if (!match[4] && !VOID3.has(match[2].toLowerCase()) && ++depth > DEEPEST) return true;
|
|
6031
6619
|
}
|
|
@@ -6050,7 +6638,7 @@ function readText(html2, { weight = 400, pages = false, links = true, fontSize =
|
|
|
6050
6638
|
inline2 = "";
|
|
6051
6639
|
};
|
|
6052
6640
|
let at = 0;
|
|
6053
|
-
for (const match of [...html2.matchAll(
|
|
6641
|
+
for (const match of [...html2.matchAll(TAG4)]) {
|
|
6054
6642
|
if (match.index < at) continue;
|
|
6055
6643
|
const tag = match[2].toLowerCase();
|
|
6056
6644
|
if (match[1] || !/^(p|ul|ol|h[1-6])$/.test(tag)) continue;
|
|
@@ -6147,7 +6735,7 @@ function readList(tag, attrs2, inner, runs, fontSize, left) {
|
|
|
6147
6735
|
const items2 = [];
|
|
6148
6736
|
let gap;
|
|
6149
6737
|
let at = 0;
|
|
6150
|
-
for (const match of [...inner.matchAll(
|
|
6738
|
+
for (const match of [...inner.matchAll(TAG4)]) {
|
|
6151
6739
|
if (match.index < at || match[1] || match[2].toLowerCase() !== "li") continue;
|
|
6152
6740
|
const end = closing(inner, "li", match.index + match[0].length);
|
|
6153
6741
|
if (end === null) {
|
|
@@ -6393,14 +6981,14 @@ function sayUnread(tool, values, defaults, say) {
|
|
|
6393
6981
|
const given = defaults ?? FRAMES[tool] ?? {};
|
|
6394
6982
|
const known = /* @__PURE__ */ new Set([...NOTHING, ...read2.values, ...hidden2]);
|
|
6395
6983
|
for (const [key, value] of Object.entries(values)) {
|
|
6396
|
-
if (known.has(key) || tool === "body" && key.startsWith("popup") || empty(value) || Object.hasOwn(given, key) &&
|
|
6984
|
+
if (known.has(key) || tool === "body" && key.startsWith("popup") || empty(value) || Object.hasOwn(given, key) && same5(value, given[key])) continue;
|
|
6397
6985
|
say(Object.hasOwn(WHAT, key) ? `its ${WHAT[key]}` : `its ${key} ${JSON.stringify(value) ?? ""} isn't read: left out`);
|
|
6398
6986
|
}
|
|
6399
6987
|
const override = values._override && typeof values._override === "object" ? values._override : {};
|
|
6400
6988
|
const phone = override.mobile && typeof override.mobile === "object" ? override.mobile : {};
|
|
6401
6989
|
const phoneKnown = /* @__PURE__ */ new Set([...read2.phone, ...hidden2]);
|
|
6402
6990
|
for (const [key, value] of Object.entries(phone)) {
|
|
6403
|
-
if (phoneKnown.has(key) || empty(value) ||
|
|
6991
|
+
if (phoneKnown.has(key) || empty(value) || same5(value, values[key])) continue;
|
|
6404
6992
|
say(`its ${key} on phones ${JSON.stringify(value) ?? ""} isn't read: left out`);
|
|
6405
6993
|
}
|
|
6406
6994
|
for (const device of Object.keys(override)) {
|
|
@@ -6648,8 +7236,8 @@ function fold(blocks, space) {
|
|
|
6648
7236
|
if (!some(s)) return block2;
|
|
6649
7237
|
const holds = ["Text", "Heading", "Divider", "Column"].includes(block2.type) && !BOX2.some((key) => block2.style?.[key] !== void 0);
|
|
6650
7238
|
if (holds) return { ...block2, style: { ...block2.style, padding: add2(block2.style?.padding ?? ZERO2, s) } };
|
|
6651
|
-
const { hidden: hidden2, ...
|
|
6652
|
-
return { type: "Column", style: { padding: s }, ...hidden2 ? { hidden: hidden2 } : {}, children: [
|
|
7239
|
+
const { hidden: hidden2, ...shown2 } = block2;
|
|
7240
|
+
return { type: "Column", style: { padding: s }, ...hidden2 ? { hidden: hidden2 } : {}, children: [shown2] };
|
|
6653
7241
|
});
|
|
6654
7242
|
}
|
|
6655
7243
|
function phoneOf(columnValues) {
|
|
@@ -6682,14 +7270,14 @@ function columnOf(columnValues, children, share3, say) {
|
|
|
6682
7270
|
function stretched(columns, stacks) {
|
|
6683
7271
|
if (columns.length < 2) return { style: {} };
|
|
6684
7272
|
const style = (column) => column.style ?? {};
|
|
6685
|
-
const
|
|
7273
|
+
const same6 = (values) => values[0] !== void 0 && values.every((value) => JSON.stringify(value) === JSON.stringify(values[0]));
|
|
6686
7274
|
const out = {};
|
|
6687
7275
|
const backgrounds = columns.map((column) => style(column).backgroundColor);
|
|
6688
|
-
if (
|
|
7276
|
+
if (same6(backgrounds) && columns.every((column) => style(column).borderRadius === void 0)) out.backgroundColor = backgrounds[0];
|
|
6689
7277
|
const lines = {};
|
|
6690
7278
|
for (const side2 of ["top", "bottom"]) {
|
|
6691
7279
|
const each = columns.map((column) => style(column).border?.[side2]);
|
|
6692
|
-
if (
|
|
7280
|
+
if (same6(each)) lines[side2] = each[0];
|
|
6693
7281
|
}
|
|
6694
7282
|
if (!out.backgroundColor && !Object.keys(lines).length) return { style: {} };
|
|
6695
7283
|
for (const column of columns) {
|
|
@@ -6786,13 +7374,13 @@ var defined3 = (record) => Object.fromEntries(Object.entries(record).filter(([,
|
|
|
6786
7374
|
var wordsIn = (value, depth) => typeof value === "string" ? [value] : depth > 0 && value && typeof value === "object" ? Object.values(value).flatMap((held) => wordsIn(held, depth - 1)) : [];
|
|
6787
7375
|
var ROOTS = { email: "Email", web: "Page", document: "Document" };
|
|
6788
7376
|
var SIZES2 = /* @__PURE__ */ new Set(["A3", "A4", "A5", "Letter", "Legal", "Tabloid"]);
|
|
6789
|
-
function readDesign(given, displayMode,
|
|
6790
|
-
const design = withoutMalformed(given,
|
|
7377
|
+
function readDesign(given, displayMode, report, toSafeHtml) {
|
|
7378
|
+
const design = withoutMalformed(given, report);
|
|
6791
7379
|
const body = obj3(obj3(design).body);
|
|
6792
|
-
if (!Object.keys(body).length)
|
|
7380
|
+
if (!Object.keys(body).length) report.push("the design has no body: an empty template");
|
|
6793
7381
|
const values = { ...BodyDefaults, ...obj3(body.values) };
|
|
6794
7382
|
const rowsOf = (key) => {
|
|
6795
|
-
if (body[key] !== void 0 && !Array.isArray(body[key]))
|
|
7383
|
+
if (body[key] !== void 0 && !Array.isArray(body[key])) report.push(`body.${key} isn't a list of rows: left out`);
|
|
6796
7384
|
return arr2(body[key]);
|
|
6797
7385
|
};
|
|
6798
7386
|
const rows = rowsOf("rows");
|
|
@@ -6800,15 +7388,15 @@ function readDesign(given, displayMode, report2, toSafeHtml) {
|
|
|
6800
7388
|
const footers = rowsOf("footers");
|
|
6801
7389
|
const breaks = rows.some((row) => arr2(obj3(row).columns).some((column) => arr2(obj3(column).contents).some((content2) => obj3(content2).type === "page_break")));
|
|
6802
7390
|
const mode = displayMode && Object.hasOwn(ROOTS, displayMode) ? displayMode : headers.length || footers.length || breaks ? "document" : "email";
|
|
6803
|
-
const root = rootOf(mode, values, (message) =>
|
|
6804
|
-
sayUnread("body", obj3(body.values), void 0, (message) =>
|
|
7391
|
+
const root = rootOf(mode, values, (message) => report.push(`body: ${message}`));
|
|
7392
|
+
sayUnread("body", obj3(body.values), void 0, (message) => report.push(`body: ${message}`));
|
|
6805
7393
|
const context = (where) => ({
|
|
6806
7394
|
inherited: { color: root.style?.color ?? "#000000", fontFamily: root.style?.fontFamily ?? "Arial, Helvetica, sans-serif", fontWeight: root.style?.fontWeight ?? 400 },
|
|
6807
7395
|
mode,
|
|
6808
7396
|
dir: root.props?.dir ?? "ltr",
|
|
6809
7397
|
safe: (html2) => toSafeHtml ? toSafeHtml(html2) : html2,
|
|
6810
7398
|
document: mode === "document",
|
|
6811
|
-
say: (message) =>
|
|
7399
|
+
say: (message) => report.push(`${where}: ${message}`)
|
|
6812
7400
|
});
|
|
6813
7401
|
const children = [];
|
|
6814
7402
|
const place = (list, part) => list.flatMap((row, r) => readRow(obj3(row), `${part}[${r}]`, { context, body: values, document: mode === "document", part: mode === "document" && part !== "rows" }));
|
|
@@ -6823,12 +7411,12 @@ function readDesign(given, displayMode, report2, toSafeHtml) {
|
|
|
6823
7411
|
}
|
|
6824
7412
|
const all = [...rows, ...headers, ...footers];
|
|
6825
7413
|
const items2 = all.flatMap((row) => [row, ...arr2(obj3(row).columns).flatMap((column) => [column, ...arr2(obj3(column).contents)])]).map((item) => obj3(obj3(item).values));
|
|
6826
|
-
if (items2.some((held) => held.hideMobile === true || held.hideDesktop === true))
|
|
7414
|
+
if (items2.some((held) => held.hideMobile === true || held.hideDesktop === true)) report.push("body: what it hides on phones or on desktop is written as an older design's, which the builder moves when it opens one: left out, so it shows. Open it in the builder and save it first");
|
|
6827
7415
|
const ownFonts = new Set(arr2(root.props?.fonts).map((face) => obj3(face).family));
|
|
6828
7416
|
const webFonts = [...new Set([values, ...items2].map((held) => obj3(held.fontFamily)).filter((family) => typeof family.url === "string" && family.url && !ownFonts.has(family.label)).map((family) => String(family.label ?? family.value)))];
|
|
6829
|
-
if (webFonts.length)
|
|
7417
|
+
if (webFonts.length) report.push(`body: ${webFonts.join(", ")}: web fonts the builder loads from a stylesheet, which a root doesn't take (its fonts are font files): left out, so text shows in the next font of its stack`);
|
|
6830
7418
|
const tags = [...new Set(wordsIn(all, 12).flatMap((text4) => [...text4.matchAll(/\[\[\s*[\w.-]+\s*\]\]/g)].map((match) => match[0])))];
|
|
6831
|
-
if (tags.length)
|
|
7419
|
+
if (tags.length) report.push(`body: ${tags.join(", ")}: design tags are filled in by the builder, from what it's given: written as they are`);
|
|
6832
7420
|
return { ...root, children };
|
|
6833
7421
|
}
|
|
6834
7422
|
function rootOf(mode, values, say) {
|
|
@@ -6870,23 +7458,23 @@ function rootOf(mode, values, say) {
|
|
|
6870
7458
|
// src/builder/check.ts
|
|
6871
7459
|
var LINK = definitionOf("Link").props.href.kind;
|
|
6872
7460
|
var holdsText = (type) => !!definitionOf(type)?.children?.text;
|
|
6873
|
-
function itemLinks(value, key, path,
|
|
7461
|
+
function itemLinks(value, key, path, report) {
|
|
6874
7462
|
if (!Array.isArray(value)) return value;
|
|
6875
7463
|
return value.map((item, i) => {
|
|
6876
7464
|
if (!item || typeof item !== "object" || Array.isArray(item) || !("href" in item) || LINK.parse(item.href).ok) return item;
|
|
6877
|
-
|
|
7465
|
+
report.push(`${path}: its ${key}[${i}]'s link ${JSON.stringify(item.href)} isn't one Elements writes: it's "#"`);
|
|
6878
7466
|
return { ...item, href: "#" };
|
|
6879
7467
|
});
|
|
6880
7468
|
}
|
|
6881
|
-
function checkNode(node, path,
|
|
7469
|
+
function checkNode(node, path, report, inText2 = false) {
|
|
6882
7470
|
const definition = definitionOf(node.type);
|
|
6883
7471
|
if (!definition) {
|
|
6884
|
-
|
|
7472
|
+
report.push(`${path}: ${JSON.stringify(node.type)} isn't a component: left out`);
|
|
6885
7473
|
return null;
|
|
6886
7474
|
}
|
|
6887
7475
|
const out = { ...node };
|
|
6888
7476
|
if (out.id !== void 0 && !/^[A-Za-z][\w-]*$/.test(out.id)) {
|
|
6889
|
-
|
|
7477
|
+
report.push(`${path}: its id ${JSON.stringify(out.id)} isn't one a template takes: left out`);
|
|
6890
7478
|
delete out.id;
|
|
6891
7479
|
}
|
|
6892
7480
|
for (const part of ["props", "style", "mobile", "hover"]) {
|
|
@@ -6895,7 +7483,7 @@ function checkNode(node, path, report2, inText2 = false) {
|
|
|
6895
7483
|
const specs = part === "props" ? definition.props : definition.style;
|
|
6896
7484
|
const kept = {};
|
|
6897
7485
|
for (const [key, given] of Object.entries(values)) {
|
|
6898
|
-
const value = part === "props" ? itemLinks(given, key, path,
|
|
7486
|
+
const value = part === "props" ? itemLinks(given, key, path, report) : given;
|
|
6899
7487
|
const spec = Object.hasOwn(specs, key) ? specs[key] : void 0;
|
|
6900
7488
|
const parsed = spec ? spec.kind.parse(value) : void 0;
|
|
6901
7489
|
if (parsed?.ok) {
|
|
@@ -6904,35 +7492,35 @@ function checkNode(node, path, report2, inText2 = false) {
|
|
|
6904
7492
|
}
|
|
6905
7493
|
if (part === "props" && (key === "href" || key === "src")) {
|
|
6906
7494
|
if (node.type === "Button") {
|
|
6907
|
-
|
|
7495
|
+
report.push(`${path}: its link ${JSON.stringify(value)} isn't one Elements writes: it's "#"`);
|
|
6908
7496
|
kept.href = "#";
|
|
6909
7497
|
continue;
|
|
6910
7498
|
}
|
|
6911
7499
|
if (node.type !== "Link") {
|
|
6912
|
-
|
|
7500
|
+
report.push(`${path}: its ${key} ${JSON.stringify(value)} isn't one Elements writes: left out`);
|
|
6913
7501
|
return null;
|
|
6914
7502
|
}
|
|
6915
|
-
|
|
6916
|
-
const children = checkChildren(node.children ?? [], path,
|
|
7503
|
+
report.push(`${path}: its ${key} ${JSON.stringify(value)} isn't one Elements writes: ${inText2 ? "its words" : "what it holds"} without it`);
|
|
7504
|
+
const children = checkChildren(node.children ?? [], path, report, true);
|
|
6917
7505
|
return inText2 ? { type: "Span", children } : children;
|
|
6918
7506
|
}
|
|
6919
|
-
|
|
7507
|
+
report.push(`${path}: its ${part === "props" ? "" : `${part} `}${key} ${JSON.stringify(value)}${parsed && !parsed.ok ? ` (${parsed.message})` : ""} isn't one a ${node.type} takes: left out`);
|
|
6920
7508
|
}
|
|
6921
7509
|
if (Object.keys(kept).length) out[part] = kept;
|
|
6922
7510
|
else delete out[part];
|
|
6923
7511
|
}
|
|
6924
|
-
if (node.children) out.children = checkChildren(node.children, path,
|
|
7512
|
+
if (node.children) out.children = checkChildren(node.children, path, report, holdsText(node.type));
|
|
6925
7513
|
return out;
|
|
6926
7514
|
}
|
|
6927
|
-
function checkChildren(children, path,
|
|
7515
|
+
function checkChildren(children, path, report, inText2) {
|
|
6928
7516
|
return children.flatMap((child, i) => {
|
|
6929
7517
|
if (typeof child === "string") return [child];
|
|
6930
|
-
const checked = checkNode(child, `${path}.children[${i}]`,
|
|
7518
|
+
const checked = checkNode(child, `${path}.children[${i}]`, report, inText2);
|
|
6931
7519
|
return Array.isArray(checked) ? checked : checked ? [checked] : [];
|
|
6932
7520
|
});
|
|
6933
7521
|
}
|
|
6934
|
-
function checkTemplate(root,
|
|
6935
|
-
const checked = checkNode(root.type === "Document" ? withoutPhones(root) : root, "root",
|
|
7522
|
+
function checkTemplate(root, report) {
|
|
7523
|
+
const checked = checkNode(root.type === "Document" ? withoutPhones(root) : root, "root", report);
|
|
6936
7524
|
return checked && !Array.isArray(checked) ? checked : { type: root.type };
|
|
6937
7525
|
}
|
|
6938
7526
|
function withoutPhones(node) {
|
|
@@ -7608,7 +8196,8 @@ function renderSubtree(node, width, root, components, inherited) {
|
|
|
7608
8196
|
const tokens2 = (n) => n.type === "PageNumber" ? { type: "MergeTag", props: { name: n.props?.total ? "page_count" : "page_number" } } : n.children ? { ...n, children: n.children.map((child) => typeof child === "string" ? child : tokens2(child)) } : n;
|
|
7609
8197
|
const own3 = tokens2(node);
|
|
7610
8198
|
const around = { ...inherited, ...drawn < 200 ? { width: drawn } : {} };
|
|
7611
|
-
const
|
|
8199
|
+
const breakpoints2 = root.type === "Page" && root.props?.breakpoints ? { breakpoints: root.props.breakpoints } : {};
|
|
8200
|
+
const page = { type: root.type === "Email" ? "Email" : "Page", props: { width: Math.max(200, drawn), ...breakpoints2 }, style: root.style, children: [Object.keys(around).length ? { type: "Column", style: around, children: [own3] } : own3] };
|
|
7612
8201
|
const { head, body } = renderTemplate({ root: page }, { components });
|
|
7613
8202
|
const style = /<style>[\s\S]*?<\/style>/.exec(head)?.[0] ?? "";
|
|
7614
8203
|
const filled = body.replace(/^<main style="/, '<main style="width:100%;');
|
|
@@ -7620,14 +8209,14 @@ var holdsPageNumber = (node) => node.type === "PageNumber" || (node.children ??
|
|
|
7620
8209
|
var PAINT2 = ["backgroundColor", "border", "borderRadius"];
|
|
7621
8210
|
var BROWSER_TEXT = { fontSize: 16, fontStyle: "normal", letterSpacing: 0, textAlign: "left", textTransform: "none" };
|
|
7622
8211
|
function raise(element2) {
|
|
7623
|
-
const
|
|
8212
|
+
const report = [];
|
|
7624
8213
|
const { document, diagnostics, components } = collectTemplate(element2);
|
|
7625
8214
|
if (!document || diagnostics.some((d) => d.severity === "error")) return { body: null, report: diagnostics.filter((d) => d.severity === "error").map((d) => `${d.path}: ${d.message}`) };
|
|
7626
8215
|
const root = document.root;
|
|
7627
8216
|
if (root.type !== "Email" && root.type !== "Page" && root.type !== "Document") {
|
|
7628
8217
|
return { body: null, report: [`root: the builder edits an Email, a Page or a Document, not a ${root.type}`] };
|
|
7629
8218
|
}
|
|
7630
|
-
if (holdsPageNumber(root))
|
|
8219
|
+
if (holdsPageNumber(root)) report.push("root: a PageNumber goes to the builder as its token ({{page_number}}), which the editor's PDF prints as it is: the builder has no page numbers yet");
|
|
7631
8220
|
const prepared = prepareTemplate(document, { components });
|
|
7632
8221
|
if (!prepared.resolved) return { body: null, report: prepared.diagnostics.filter((d) => d.severity === "error").map((d) => `${d.path}: ${d.message}`) };
|
|
7633
8222
|
const resolvedRoot = prepared.resolved.root;
|
|
@@ -7644,7 +8233,7 @@ function raise(element2) {
|
|
|
7644
8233
|
};
|
|
7645
8234
|
const sayLeftOut = (tool, node, path, fields) => {
|
|
7646
8235
|
const left = leftOut(tool, node, fields);
|
|
7647
|
-
if (left.length)
|
|
8236
|
+
if (left.length) report.push(`${path}: the builder's ${tool} has no place for ${left.join(", ")}: left out`);
|
|
7648
8237
|
};
|
|
7649
8238
|
const unsized = (node, path) => childrenOf2(node, path).some(({ child, path: childPath }) => !child.hidden?.desktop && resolved.get(childPath)?.width === null);
|
|
7650
8239
|
const childrenOf2 = (node, path) => (node.children ?? []).map((child, i) => ({ child, path: `${path}.children[${i}]` })).filter((c) => typeof c.child !== "string");
|
|
@@ -7663,11 +8252,11 @@ function raise(element2) {
|
|
|
7663
8252
|
};
|
|
7664
8253
|
const place = {
|
|
7665
8254
|
at,
|
|
7666
|
-
report
|
|
8255
|
+
report,
|
|
7667
8256
|
put,
|
|
7668
8257
|
sayLeftOut,
|
|
7669
8258
|
locked: (node, path, why, around) => {
|
|
7670
|
-
|
|
8259
|
+
report.push(`${path}: ${why}: ${root.type === "Document" ? `it's an HTML block of its own markup in the design: ${NOT_IN_DOCUMENTS}` : "it's an HTML block of its own markup in the builder (it can be moved, not edited)"}`);
|
|
7671
8260
|
const own3 = at(path);
|
|
7672
8261
|
return put(lockedHtml(node, own3.width ?? own3.available ?? Number(resolvedRoot.width), root, components, around, inheritedText(path)));
|
|
7673
8262
|
},
|
|
@@ -7801,7 +8390,7 @@ function raise(element2) {
|
|
|
7801
8390
|
const own3 = at(path);
|
|
7802
8391
|
sayLeftOut("row", child, path, ["style.backgroundColor", "style.padding", "style.border", "style.gap", "style.alignItems", ...TEXT_FOLDED]);
|
|
7803
8392
|
const band = own3.style.backgroundColor !== void 0 ? { backgroundColor: own3.style.backgroundColor } : {};
|
|
7804
|
-
if (child.mobile?.border !== void 0)
|
|
8393
|
+
if (child.mobile?.border !== void 0) report.push(`${path}: the builder's row has no border on phones: left out`);
|
|
7805
8394
|
const sectionRows = rows(childrenOf2(child, path), own3, band, padding2(child), child);
|
|
7806
8395
|
if (child.id && sectionRows[0]) put(sectionRows[0], builderId(child.id, "u_row"));
|
|
7807
8396
|
body.push(...sectionRows.length ? sectionRows : [oneColumn([], own3, band, padding2(child), {}, hiding2(child))]);
|
|
@@ -7815,7 +8404,7 @@ function raise(element2) {
|
|
|
7815
8404
|
} else if (child.type === "PageBreak") {
|
|
7816
8405
|
flushLoose();
|
|
7817
8406
|
const content2 = top.slice(i + 1).some((c) => !["PageHeader", "PageFooter", "PageBreak"].includes(c.child.type));
|
|
7818
|
-
if (last === null || !content2)
|
|
8407
|
+
if (last === null || !content2) report.push(`${path}: a page break with nothing before or after it is no page in Elements, and a blank page in the builder`);
|
|
7819
8408
|
body.push(pageBreak());
|
|
7820
8409
|
last = "break";
|
|
7821
8410
|
} else if (child.type === "PageHeader" || child.type === "PageFooter") {
|
|
@@ -7858,10 +8447,10 @@ function raise(element2) {
|
|
|
7858
8447
|
const bodyHidden = { ...hiddenOf("body", root, ["props.margin"]), ...margin, ...title, backgroundColor: style.backgroundColor ?? "" };
|
|
7859
8448
|
if (root.type === "Document") {
|
|
7860
8449
|
const paper = { documentSize: root.props?.size ?? "A4", documentOrientation: root.props?.orientation ?? "portrait" };
|
|
7861
|
-
return { body: { root: root.type, fields: { ...common, ...paper }, values: bodyHidden, rows: body, headers, footers }, report
|
|
8450
|
+
return { body: { root: root.type, fields: { ...common, ...paper }, values: bodyHidden, rows: body, headers, footers }, report };
|
|
7862
8451
|
}
|
|
7863
8452
|
const previewText = root.type === "Email" && root.props?.previewText ? { previewText: String(root.props.previewText) } : {};
|
|
7864
|
-
return { body: { root: root.type, fields: common, ...previewText, values: bodyHidden, rows: body }, report
|
|
8453
|
+
return { body: { root: root.type, fields: common, ...previewText, values: bodyHidden, rows: body }, report };
|
|
7865
8454
|
}
|
|
7866
8455
|
function countRows(children) {
|
|
7867
8456
|
let count = 0;
|
|
@@ -7907,10 +8496,10 @@ function nodeOf(node) {
|
|
|
7907
8496
|
|
|
7908
8497
|
// src/builder/write.ts
|
|
7909
8498
|
function writeDesign(element2) {
|
|
7910
|
-
const { body, report
|
|
8499
|
+
const { body, report } = raise(element2);
|
|
7911
8500
|
if (!body) throw new Error(`[Elements] This template can't go to the builder:
|
|
7912
|
-
${
|
|
7913
|
-
return { design: withFontStylesheets(writeParts(body)), report
|
|
8501
|
+
${report.join("\n")}`);
|
|
8502
|
+
return { design: withFontStylesheets(writeParts(body)), report };
|
|
7914
8503
|
}
|
|
7915
8504
|
|
|
7916
8505
|
// src/builder/index.ts
|
|
@@ -7918,10 +8507,10 @@ function toDesign(element2) {
|
|
|
7918
8507
|
return writeDesign(element2);
|
|
7919
8508
|
}
|
|
7920
8509
|
function fromDesign(design, options = {}) {
|
|
7921
|
-
const
|
|
7922
|
-
const root = checkTemplate(uniqueIds(readDesign(design, options.displayMode,
|
|
7923
|
-
const code = printTemplate(root, options.name,
|
|
7924
|
-
return { element: toElement(root), code, report
|
|
8510
|
+
const report = [];
|
|
8511
|
+
const root = checkTemplate(uniqueIds(readDesign(design, options.displayMode, report, options.toSafeHtml)), report);
|
|
8512
|
+
const code = printTemplate(root, options.name, report);
|
|
8513
|
+
return { element: toElement(root), code, report };
|
|
7925
8514
|
}
|
|
7926
8515
|
|
|
7927
8516
|
export { fromDesign, toDesign };
|