@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.cjs
CHANGED
|
@@ -84,21 +84,26 @@ var Diagnostics = class {
|
|
|
84
84
|
constructor() {
|
|
85
85
|
this.list = [];
|
|
86
86
|
}
|
|
87
|
-
add(code, severity, message, path, nodeId) {
|
|
88
|
-
|
|
87
|
+
add(code, severity, message, path, nodeId, extra = {}) {
|
|
88
|
+
const d = { code, severity, message, path };
|
|
89
|
+
if (nodeId !== void 0) d.nodeId = nodeId;
|
|
90
|
+
if (extra.clients) d.clients = [...extra.clients];
|
|
91
|
+
if (extra.fix) d.fix = extra.fix;
|
|
92
|
+
if (extra.see) d.see = extra.see;
|
|
93
|
+
this.list.push(d);
|
|
89
94
|
}
|
|
90
95
|
error(code, message, path, nodeId) {
|
|
91
96
|
this.add(code, "error", message, path, nodeId);
|
|
92
97
|
}
|
|
93
|
-
warning(code, message, path, nodeId) {
|
|
94
|
-
this.add(code, "warning", message, path, nodeId);
|
|
98
|
+
warning(code, message, path, nodeId, extra) {
|
|
99
|
+
this.add(code, "warning", message, path, nodeId, extra);
|
|
95
100
|
}
|
|
96
101
|
get hasErrors() {
|
|
97
102
|
return this.list.some((d) => d.severity === "error");
|
|
98
103
|
}
|
|
99
104
|
};
|
|
100
105
|
function formatDiagnostics(diagnostics) {
|
|
101
|
-
return diagnostics.map((d) => `${d.severity} ${d.code} at ${d.path}: ${d.message}`).join("\n");
|
|
106
|
+
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");
|
|
102
107
|
}
|
|
103
108
|
var ElementsError = class extends Error {
|
|
104
109
|
constructor(diagnostics) {
|
|
@@ -109,6 +114,64 @@ ${formatDiagnostics(errors)}`);
|
|
|
109
114
|
this.diagnostics = diagnostics;
|
|
110
115
|
}
|
|
111
116
|
};
|
|
117
|
+
var captured = (against, wordsWithin4px, lineBreaks) => ({ elements: "0.3.0", against, captured: "2026-11-08", wordsWithin4px, lineBreaks });
|
|
118
|
+
var tables = (name) => ({ name, widths: {}, dark: false, approximate: true, measured: null });
|
|
119
|
+
var EMAIL_CLIENTS = {
|
|
120
|
+
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) },
|
|
121
|
+
"outlook-com": { name: "Outlook.com and new Outlook", widths: { desktop: 820 }, dark: false, approximate: false, measured: captured("Outlook on the web", 1, 1) },
|
|
122
|
+
"outlook-mac": tables("Outlook for Mac"),
|
|
123
|
+
"outlook-app": tables("Outlook app (iOS and Android)"),
|
|
124
|
+
gmail: { name: "Gmail", widths: { desktop: 820 }, dark: false, approximate: false, measured: captured("Gmail on the web", 1, 1) },
|
|
125
|
+
"gmail-app": { name: "Gmail app", widths: { phone: 375 }, dark: false, approximate: true, measured: null },
|
|
126
|
+
"gmail-app-other": { name: "Gmail app, another account", widths: { phone: 375 }, dark: false, approximate: true, measured: null },
|
|
127
|
+
"apple-mail": { name: "Apple Mail", widths: { desktop: 820 }, dark: true, approximate: false, measured: captured("Mail 16 on macOS 26", 0.999, 0.999) },
|
|
128
|
+
yahoo: tables("Yahoo Mail"),
|
|
129
|
+
aol: tables("AOL Mail"),
|
|
130
|
+
"samsung-email": tables("Samsung Email"),
|
|
131
|
+
thunderbird: tables("Thunderbird"),
|
|
132
|
+
protonmail: tables("Proton Mail"),
|
|
133
|
+
fastmail: tables("Fastmail"),
|
|
134
|
+
hey: tables("HEY"),
|
|
135
|
+
gmx: tables("GMX"),
|
|
136
|
+
"web-de": tables("WEB.DE"),
|
|
137
|
+
orange: tables("Orange"),
|
|
138
|
+
sfr: tables("SFR"),
|
|
139
|
+
laposte: tables("La Poste"),
|
|
140
|
+
"free-fr": tables("Free"),
|
|
141
|
+
"t-online-de": tables("T-Online"),
|
|
142
|
+
"mail-ru": tables("Mail.ru"),
|
|
143
|
+
ionos: tables("IONOS (1&1)"),
|
|
144
|
+
"wp-pl": tables("WP Poczta"),
|
|
145
|
+
rainloop: tables("RainLoop")
|
|
146
|
+
};
|
|
147
|
+
var DEFAULT_EMAIL_CLIENTS = ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail", "gmail-app", "gmail-app-other", "apple-mail", "yahoo", "aol", "samsung-email"];
|
|
148
|
+
var caniemail = (feature) => `https://www.caniemail.com/features/${feature}/`;
|
|
149
|
+
function checkEmailClients(clients) {
|
|
150
|
+
if (clients === void 0) return;
|
|
151
|
+
const unknown = Array.isArray(clients) ? clients.filter((c) => !Object.prototype.hasOwnProperty.call(EMAIL_CLIENTS, c)) : [clients];
|
|
152
|
+
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(", ")}`);
|
|
153
|
+
}
|
|
154
|
+
function forEmailClients(diagnostics, selection = DEFAULT_EMAIL_CLIENTS) {
|
|
155
|
+
const out = [];
|
|
156
|
+
const seen = /* @__PURE__ */ new Map();
|
|
157
|
+
for (const d of diagnostics) {
|
|
158
|
+
if (!d.clients) {
|
|
159
|
+
out.push(d);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const clients = d.clients.filter((c) => selection.includes(c));
|
|
163
|
+
if (!clients.length) continue;
|
|
164
|
+
const key = JSON.stringify([d.code, d.path, d.severity, d.message]);
|
|
165
|
+
const same42 = seen.get(key);
|
|
166
|
+
if (same42) same42.clients = [.../* @__PURE__ */ new Set([...same42.clients, ...clients])];
|
|
167
|
+
else {
|
|
168
|
+
const kept = { ...d, clients };
|
|
169
|
+
seen.set(key, kept);
|
|
170
|
+
out.push(kept);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
}
|
|
112
175
|
var ok = (value) => ({ ok: true, value });
|
|
113
176
|
var fail = (message) => ({ ok: false, message });
|
|
114
177
|
var show = (input) => {
|
|
@@ -694,6 +757,26 @@ function attributes() {
|
|
|
694
757
|
jsonSchema: () => ({ type: "object", additionalProperties: { type: "string" } })
|
|
695
758
|
});
|
|
696
759
|
}
|
|
760
|
+
var MIN_PHONE_WIDTH = 240;
|
|
761
|
+
function breakpoints() {
|
|
762
|
+
const accepts = `{ mobile }: the phone width in px (from ${MIN_PHONE_WIDTH})`;
|
|
763
|
+
return kind({
|
|
764
|
+
name: "breakpoints",
|
|
765
|
+
editor: "number",
|
|
766
|
+
accepts,
|
|
767
|
+
parse(input) {
|
|
768
|
+
if (!isObject(input)) return fail(`expected ${accepts}, not ${show(input)}`);
|
|
769
|
+
const unknown = Object.keys(input).find((key) => key !== "mobile");
|
|
770
|
+
if (unknown !== void 0) return fail(`${show(unknown)} isn't a breakpoint a Page has: it has mobile`);
|
|
771
|
+
const { mobile } = input;
|
|
772
|
+
if (mobile === void 0) return ok({});
|
|
773
|
+
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)}`);
|
|
774
|
+
return ok({ mobile });
|
|
775
|
+
},
|
|
776
|
+
empty: (value) => value.mobile === void 0,
|
|
777
|
+
jsonSchema: () => ({ type: "object", properties: { mobile: { type: "integer", minimum: MIN_PHONE_WIDTH } }, additionalProperties: false })
|
|
778
|
+
});
|
|
779
|
+
}
|
|
697
780
|
function linkParts(value) {
|
|
698
781
|
return typeof value === "string" ? { href: value } : value;
|
|
699
782
|
}
|
|
@@ -706,6 +789,54 @@ function defineComponent(definition) {
|
|
|
706
789
|
function prop(kind2, options) {
|
|
707
790
|
return { kind: kind2, ...options };
|
|
708
791
|
}
|
|
792
|
+
var MOBILE = {
|
|
793
|
+
name: "mobile",
|
|
794
|
+
on: "on phones",
|
|
795
|
+
for: "for phones",
|
|
796
|
+
roots: (root) => !!root?.responsive,
|
|
797
|
+
notAllowed: { code: "mobile/not-supported", message: "Phone styles are for an Email or a Page, which have a phone width" },
|
|
798
|
+
sheet: { media: "phone", selector: "&" },
|
|
799
|
+
allows: (spec) => !!spec.mobile,
|
|
800
|
+
support: { web: { level: "full", source: "writer" }, email: { level: "full", source: "writer" } },
|
|
801
|
+
styles: {
|
|
802
|
+
// The email's phone text styles are rules that override a size; "normal" isn't one.
|
|
803
|
+
lineHeight: { email: { level: "none", values: ["normal"], shows: "the line height it has on desktop", source: "writer" } }
|
|
804
|
+
},
|
|
805
|
+
outlookApplies: false,
|
|
806
|
+
hiddenKey: "mobile"
|
|
807
|
+
};
|
|
808
|
+
var HOVER = {
|
|
809
|
+
name: "hover",
|
|
810
|
+
on: "on hover",
|
|
811
|
+
for: "for hover",
|
|
812
|
+
// Keyboard focus gets the same styles as the pointer: a link or button reached by Tab shows its hover colors.
|
|
813
|
+
// A box around the link (an email Button's cell, its background) takes them while the link in it has focus.
|
|
814
|
+
sheet: { media: "all", selector: "&:hover,&:focus-visible", within: "&:hover,&:focus-within", level: "hover" },
|
|
815
|
+
allows: (_, key, definition) => !!definition.hover?.includes(key),
|
|
816
|
+
support: {
|
|
817
|
+
web: { level: "full", source: "css" },
|
|
818
|
+
// On type selectors only in Outlook.com and Outlook for Mac (Elements' are classes); the phone apps' results don't
|
|
819
|
+
// matter, as nothing hovers there.
|
|
820
|
+
email: {
|
|
821
|
+
level: "partial",
|
|
822
|
+
shows: "no change (where it shows, keyboard focus gets it too)",
|
|
823
|
+
source: "caniemail",
|
|
824
|
+
feature: "css-pseudo-class-hover",
|
|
825
|
+
clients: ["outlook", "outlook-com", "outlook-mac", "gmx", "web-de", "sfr", "laposte", "mail-ru"]
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
paged: (node, where) => `A hover isn't possible in ${where}: nothing points at it. The ${node.type} shows its own colors`,
|
|
829
|
+
outlookApplies: false
|
|
830
|
+
};
|
|
831
|
+
var CONDITIONS = [MOBILE, HOVER];
|
|
832
|
+
var CONDITION_NAMES = new Set(CONDITIONS.map((condition) => condition.name));
|
|
833
|
+
function blockOf(node, condition) {
|
|
834
|
+
const block2 = node[condition.name];
|
|
835
|
+
return block2 && Object.keys(block2).length ? block2 : void 0;
|
|
836
|
+
}
|
|
837
|
+
function shownAnywhere(node) {
|
|
838
|
+
return !(node.hidden?.mobile && node.hidden?.desktop);
|
|
839
|
+
}
|
|
709
840
|
var writer = { level: "full", source: "writer" };
|
|
710
841
|
var css = { level: "full", source: "css" };
|
|
711
842
|
var textStyle = {
|
|
@@ -714,7 +845,12 @@ var textStyle = {
|
|
|
714
845
|
fontFamily: { kind: fontFamily(), label: "Font", inherited: true, support: { email: { level: "full", source: "outlook-captures" }, web: css } },
|
|
715
846
|
// Word rounds sizes to half points (13px is 10pt): a fraction of a px.
|
|
716
847
|
fontSize: { kind: length(["px"], { min: 1 }), label: "Font size", inherited: true, mobile: true, support: { email: { level: "full", source: "outlook-captures" }, web: css } },
|
|
717
|
-
fontWeight: {
|
|
848
|
+
fontWeight: {
|
|
849
|
+
kind: fontWeight(),
|
|
850
|
+
label: "Font weight",
|
|
851
|
+
inherited: true,
|
|
852
|
+
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 }
|
|
853
|
+
},
|
|
718
854
|
fontStyle: { kind: enumOf(["normal", "italic"]), label: "Font style", inherited: true, support: { email: { level: "full", source: "caniemail" }, web: css } },
|
|
719
855
|
// Written in px with mso-line-height-rule: exactly, which Word keeps (it drops unitless ones).
|
|
720
856
|
lineHeight: { kind: lineHeight(), label: "Line height", inherited: true, mobile: true, support: { email: { level: "full", source: "outlook-captures" }, web: css } },
|
|
@@ -726,8 +862,21 @@ var textStyle = {
|
|
|
726
862
|
support: { email: { level: "outlook", shows: "normal spacing: Word keeps letter spacing but doesn't draw it", source: "outlook-captures" }, web: css }
|
|
727
863
|
},
|
|
728
864
|
textAlign: { kind: enumOf(["left", "center", "right", "justify"]), label: "Alignment", inherited: true, mobile: true, support: { email: { level: "full", source: "caniemail" }, web: css } },
|
|
729
|
-
textTransform: {
|
|
730
|
-
|
|
865
|
+
textTransform: {
|
|
866
|
+
kind: enumOf(["none", "uppercase", "lowercase", "capitalize"]),
|
|
867
|
+
label: "Letter case",
|
|
868
|
+
inherited: true,
|
|
869
|
+
support: { email: { level: "outlook", values: ["lowercase"], shows: "the text as written: Word doesn't lowercase it", source: "caniemail", feature: "css-text-transform" }, web: css }
|
|
870
|
+
},
|
|
871
|
+
textDecoration: {
|
|
872
|
+
kind: enumOf(["none", "underline", "line-through"]),
|
|
873
|
+
label: "Decoration",
|
|
874
|
+
support: {
|
|
875
|
+
// Removed, so "none" leaves a link the client's own underline: said only where a line is asked for.
|
|
876
|
+
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"] },
|
|
877
|
+
web: css
|
|
878
|
+
}
|
|
879
|
+
}
|
|
731
880
|
};
|
|
732
881
|
var inheritedTextStyle = (({ textDecoration: _, ...rest }) => rest)(textStyle);
|
|
733
882
|
var boxStyle = {
|
|
@@ -740,7 +889,17 @@ var boxStyle = {
|
|
|
740
889
|
padding: { kind: insets(), label: "Padding", mobile: true, support: { email: { level: "full", source: "outlook-captures" }, web: css } },
|
|
741
890
|
backgroundColor: { kind: color(), label: "Background color", support: { email: writer, web: css } },
|
|
742
891
|
border: { kind: border(), label: "Border", mobile: true, support: { email: { level: "full", source: "caniemail" }, web: css } },
|
|
743
|
-
borderRadius: {
|
|
892
|
+
borderRadius: {
|
|
893
|
+
kind: radius(),
|
|
894
|
+
label: "Corner radius",
|
|
895
|
+
support: {
|
|
896
|
+
email: [
|
|
897
|
+
{ level: "outlook", shows: "square corners", source: "caniemail", feature: "css-border-radius" },
|
|
898
|
+
{ level: "partial", shows: "square corners", source: "caniemail", feature: "css-border-radius", clients: ["gmx", "web-de", "orange"] }
|
|
899
|
+
],
|
|
900
|
+
web: css
|
|
901
|
+
}
|
|
902
|
+
},
|
|
744
903
|
overflow: {
|
|
745
904
|
kind: enumOf(["visible", "hidden"]),
|
|
746
905
|
label: "Overflow",
|
|
@@ -948,6 +1107,15 @@ var Grid = defineComponent({
|
|
|
948
1107
|
},
|
|
949
1108
|
children: { categories: ["layout", "block"] },
|
|
950
1109
|
sizing: "stretch",
|
|
1110
|
+
// Stacked on phones, every two of its children are its phone gap apart: there are no rows for a row gap.
|
|
1111
|
+
conditionSupport: {
|
|
1112
|
+
mobile: {
|
|
1113
|
+
rowGap: {
|
|
1114
|
+
web: { level: "none", when: (_, node) => node.props.stackOnMobile !== false, shows: "its phone gap between every two children", source: "writer" },
|
|
1115
|
+
email: { level: "none", when: (_, node) => node.props.stackOnMobile !== false, shows: "its phone gap between every two children", source: "writer" }
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
},
|
|
951
1119
|
targets: { email: {}, web: {} },
|
|
952
1120
|
editor: {
|
|
953
1121
|
groups: [
|
|
@@ -960,7 +1128,7 @@ var Grid = defineComponent({
|
|
|
960
1128
|
var Layer = defineComponent({
|
|
961
1129
|
type: "Layer",
|
|
962
1130
|
label: "Layer",
|
|
963
|
-
description: "Its children over each other, in one box as tall as the tallest: an image
|
|
1131
|
+
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.",
|
|
964
1132
|
categories: ["layout", "block"],
|
|
965
1133
|
props: {},
|
|
966
1134
|
style: {
|
|
@@ -992,13 +1160,27 @@ var Layer = defineComponent({
|
|
|
992
1160
|
}
|
|
993
1161
|
});
|
|
994
1162
|
var WRITTEN3 = { email: { level: "full", source: "writer" }, web: { level: "full", source: "writer" } };
|
|
1163
|
+
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"];
|
|
995
1164
|
var ROOT_PROPS = {
|
|
996
1165
|
title: prop(text(), { support: WRITTEN3, label: "Title", description: "The document's title (shown in a browser tab, and by some email clients)." }),
|
|
997
|
-
lang: prop(text({ maxLength: 35 }), {
|
|
998
|
-
|
|
1166
|
+
lang: prop(text({ maxLength: 35 }), {
|
|
1167
|
+
support: {
|
|
1168
|
+
...WRITTEN3,
|
|
1169
|
+
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"] }
|
|
1170
|
+
},
|
|
1171
|
+
label: "Language",
|
|
1172
|
+
description: 'Its language, as a BCP 47 tag ("en", "ar").'
|
|
1173
|
+
}),
|
|
1174
|
+
dir: prop(enumOf(["ltr", "rtl", "auto"]), {
|
|
1175
|
+
support: {
|
|
1176
|
+
...WRITTEN3,
|
|
1177
|
+
email: { level: "partial", values: ["rtl", "auto"], shows: "left to right: the client removes the direction", source: "caniemail", feature: "html-dir", clients: ["gmx", "web-de"] }
|
|
1178
|
+
},
|
|
1179
|
+
label: "Text direction"
|
|
1180
|
+
}),
|
|
999
1181
|
fonts: prop(fontFaces(), {
|
|
1000
1182
|
support: {
|
|
1001
|
-
email: { level: "partial", shows: "
|
|
1183
|
+
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 },
|
|
1002
1184
|
web: { level: "full", source: "writer" }
|
|
1003
1185
|
},
|
|
1004
1186
|
label: "Web fonts",
|
|
@@ -1138,7 +1320,10 @@ var PageNumber = defineComponent({
|
|
|
1138
1320
|
style: {},
|
|
1139
1321
|
children: {},
|
|
1140
1322
|
sizing: "none",
|
|
1141
|
-
targets: { web: {} }
|
|
1323
|
+
targets: { web: {} },
|
|
1324
|
+
// A Page's or a Canvas's text has no page to number.
|
|
1325
|
+
roots: ["Document"],
|
|
1326
|
+
notAllowed: { code: "document/page-number", message: (root) => `A PageNumber is a page's number in a Document; this ${root} has no pages` }
|
|
1142
1327
|
});
|
|
1143
1328
|
function documentPages(root) {
|
|
1144
1329
|
const pages = [];
|
|
@@ -1274,6 +1459,11 @@ var Page = defineComponent({
|
|
|
1274
1459
|
props: {
|
|
1275
1460
|
width: prop(length(["px"], { min: 200 }), { support: WRITTEN6, label: "Content width", description: "The widest the content gets.", default: 1200 }),
|
|
1276
1461
|
description: prop(text(), { support: WRITTEN6, label: "Description", description: "The page's meta description." }),
|
|
1462
|
+
breakpoints: prop(breakpoints(), {
|
|
1463
|
+
support: { web: { level: "full", source: "writer" } },
|
|
1464
|
+
label: "Breakpoints",
|
|
1465
|
+
description: "Where its phone styles start: mobile, the widest window they apply to, in px (480 where it's not set)."
|
|
1466
|
+
}),
|
|
1277
1467
|
...ROOT_PROPS
|
|
1278
1468
|
},
|
|
1279
1469
|
style: ROOT_STYLE,
|
|
@@ -1281,6 +1471,12 @@ var Page = defineComponent({
|
|
|
1281
1471
|
children: { categories: ["section", "layout", "block"] },
|
|
1282
1472
|
sizing: "none",
|
|
1283
1473
|
responsive: true,
|
|
1474
|
+
// The page writer writes the root's phone gap, not its phone text styles.
|
|
1475
|
+
conditionSupport: {
|
|
1476
|
+
mobile: Object.fromEntries(
|
|
1477
|
+
["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" } }])
|
|
1478
|
+
)
|
|
1479
|
+
},
|
|
1284
1480
|
targets: { web: {} },
|
|
1285
1481
|
editor: {
|
|
1286
1482
|
groups: [
|
|
@@ -1524,6 +1720,7 @@ var Image = defineComponent({
|
|
|
1524
1720
|
},
|
|
1525
1721
|
children: {},
|
|
1526
1722
|
sizing: "shrink",
|
|
1723
|
+
conditionSupport: { mobile: { border: { email: { level: "none", shows: "its desktop border", source: "writer" } } } },
|
|
1527
1724
|
targets: { email: {}, web: {} },
|
|
1528
1725
|
editor: { groups: [{ title: "Image", paths: ["props.src", "props.alt", "style.width"] }, { title: "Box", paths: ["style.border", "style.borderRadius"] }] }
|
|
1529
1726
|
});
|
|
@@ -1654,6 +1851,7 @@ var TableCell = defineComponent({
|
|
|
1654
1851
|
},
|
|
1655
1852
|
children: { text: true, categories: ["inline"], types: ["Image"] },
|
|
1656
1853
|
sizing: "none",
|
|
1854
|
+
conditionSupport: { mobile: { border: { email: { level: "none", shows: "its desktop border", source: "writer" } } } },
|
|
1657
1855
|
targets: { email: {}, web: {} },
|
|
1658
1856
|
editor: { groups: [{ title: "Cell", paths: ["props.colSpan", "style.width", "style.verticalAlign"] }, TEXT_EDITOR, { title: "Box", paths: ["style.padding", "style.backgroundColor", "style.border"] }] }
|
|
1659
1857
|
});
|
|
@@ -1752,17 +1950,27 @@ function checkDocument(input, options) {
|
|
|
1752
1950
|
const extra = Object.keys(input).filter((key) => key !== "root");
|
|
1753
1951
|
for (const key of extra) d.error("document/unknown-key", `"${key}" isn't part of a document (root)`, key);
|
|
1754
1952
|
const rootDefinition = isObject2(input.root) && typeof input.root.type === "string" ? registry.get(input.root.type) : void 0;
|
|
1755
|
-
const context = { d, registry, used: /* @__PURE__ */ new Set(),
|
|
1953
|
+
const context = { d, registry, used: /* @__PURE__ */ new Set(), root: rootDefinition };
|
|
1756
1954
|
const root = input.root === void 0 ? null : readNode(input.root, "root", null, context);
|
|
1757
1955
|
if (input.root === void 0) d.error("document/root", 'A document has a root: { type: "Email" }, "Page", \u2026', "root");
|
|
1758
1956
|
return { document: root ? { root } : null, diagnostics: d.list };
|
|
1759
1957
|
}
|
|
1760
1958
|
function checkFragment(input, options) {
|
|
1761
1959
|
const d = new Diagnostics();
|
|
1762
|
-
const context = { d, registry: options.registry, idPrefix: options.idPrefix, used: new Set(options.taken),
|
|
1960
|
+
const context = { d, registry: options.registry, idPrefix: options.idPrefix, used: new Set(options.taken), root: options.root };
|
|
1763
1961
|
const node = readNode(input, options.path, options.parent, context);
|
|
1764
1962
|
return { node, diagnostics: d.list };
|
|
1765
1963
|
}
|
|
1964
|
+
function rootList(roots) {
|
|
1965
|
+
const named = roots.map((root) => a(root).replace(/^A/, "a"));
|
|
1966
|
+
return named.length > 1 ? `${named.slice(0, -1).join(", ")} or ${named[named.length - 1]}` : named[0] ?? "no root";
|
|
1967
|
+
}
|
|
1968
|
+
function rootTakes(roots, root) {
|
|
1969
|
+
return !roots || !root || roots.includes(root.type);
|
|
1970
|
+
}
|
|
1971
|
+
function sharesOutput(definition, root) {
|
|
1972
|
+
return Object.keys(definition.targets).some((target) => target in root.targets);
|
|
1973
|
+
}
|
|
1766
1974
|
function allowedIn(parent, child) {
|
|
1767
1975
|
const rule2 = parent.children;
|
|
1768
1976
|
return !!rule2.types?.includes(child.type) || child.categories.some((category) => rule2.categories?.includes(category));
|
|
@@ -1788,7 +1996,29 @@ function editDistance(from, to) {
|
|
|
1788
1996
|
}
|
|
1789
1997
|
return row[to.length];
|
|
1790
1998
|
}
|
|
1999
|
+
var OTHER_BREAKPOINTS = /* @__PURE__ */ new Set(["base", "initial", "xs", "sm", "md", "lg", "xl", "2xl", "tablet", "desktop", "wide", "small", "medium", "large"]);
|
|
2000
|
+
function conditionHint(definition, key) {
|
|
2001
|
+
const name = key.trim().replace(/^(&?:+|_+|\$|@)/, "").replace(/Style$/, "").toLowerCase();
|
|
2002
|
+
const own3 = CUSTOM_TYPE.test(definition.type);
|
|
2003
|
+
const hoverStyles = definition.hover ?? [];
|
|
2004
|
+
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)`;
|
|
2005
|
+
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 } }}`;
|
|
2006
|
+
if (name === "hover") return toHover;
|
|
2007
|
+
if (name === "mobile" || name === "phone" || name === "phones" || key.trim().startsWith("@media")) return toPhone;
|
|
2008
|
+
if (name === "focus" || name === "focus-visible" || name === "focusvisible" || name === "focus-within") {
|
|
2009
|
+
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`;
|
|
2010
|
+
}
|
|
2011
|
+
if (OTHER_BREAKPOINTS.has(name)) {
|
|
2012
|
+
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 }})`;
|
|
2013
|
+
}
|
|
2014
|
+
if (name === "active" || name === "pressed" || name === "press" || name === "visited" || name === "dark" || name === "light" || name === "print") {
|
|
2015
|
+
return `there are no ${name} styles: Elements has phone styles (mobile) and hover styles (hover) in style`;
|
|
2016
|
+
}
|
|
2017
|
+
return void 0;
|
|
2018
|
+
}
|
|
1791
2019
|
function unknownProp(definition, key) {
|
|
2020
|
+
const condition = conditionHint(definition, key);
|
|
2021
|
+
if (condition) return `${a(definition.type)} has no prop "${key}": ${condition}`;
|
|
1792
2022
|
if (Object.prototype.hasOwnProperty.call(definition.style, key)) return `${a(definition.type)} has no prop "${key}": it's a style, style={{ ${key}: \u2026 }}`;
|
|
1793
2023
|
const known = Object.keys(definition.props);
|
|
1794
2024
|
const meant = nearest(key, known);
|
|
@@ -1828,6 +2058,15 @@ function readNode(input, path, parent, context) {
|
|
|
1828
2058
|
d.error("children/not-allowed", placementMessage(parent, definition), path);
|
|
1829
2059
|
return null;
|
|
1830
2060
|
}
|
|
2061
|
+
const root = context.root;
|
|
2062
|
+
if (root && !rootTakes(definition.roots, root) && sharesOutput(definition, root)) {
|
|
2063
|
+
const { code, message } = definition.notAllowed ?? {
|
|
2064
|
+
code: "component/not-supported",
|
|
2065
|
+
message: (under) => `${a(definition.type)} goes in ${rootList(definition.roots ?? [])}, not in ${a(under).replace(/^A/, "a")}`
|
|
2066
|
+
};
|
|
2067
|
+
d.error(code, message(root.type), path);
|
|
2068
|
+
return null;
|
|
2069
|
+
}
|
|
1831
2070
|
let id;
|
|
1832
2071
|
if (input.id !== void 0) {
|
|
1833
2072
|
if (typeof input.id !== "string" || !ID.test(input.id)) {
|
|
@@ -1847,21 +2086,49 @@ function readNode(input, path, parent, context) {
|
|
|
1847
2086
|
if (!NODE_KEYS.includes(key)) d.error("node/unknown-key", `"${key}" isn't part of a component (${NODE_KEYS.join(", ")})`, `${path}.${key}`, id);
|
|
1848
2087
|
}
|
|
1849
2088
|
const node = id === void 0 ? { type: definition.type } : { id, type: definition.type };
|
|
1850
|
-
const props = readProps(input.props, definition, `${path}.props`, id, d);
|
|
2089
|
+
const props = readProps(input.props, definition, `${path}.props`, id, d, context.root);
|
|
1851
2090
|
if (props) node.props = props;
|
|
1852
|
-
const
|
|
1853
|
-
|
|
1854
|
-
if (
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
2091
|
+
const lifted = {};
|
|
2092
|
+
let styleInput = input.style;
|
|
2093
|
+
if (isObject2(styleInput) && !CUSTOM_TYPE.test(definition.type)) {
|
|
2094
|
+
const rest = {};
|
|
2095
|
+
for (const [key, value] of Object.entries(styleInput)) {
|
|
2096
|
+
if (CONDITION_NAMES.has(key)) lifted[key] = value;
|
|
2097
|
+
else rest[key] = value;
|
|
1859
2098
|
}
|
|
2099
|
+
styleInput = rest;
|
|
1860
2100
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
2101
|
+
const style = readStyles(styleInput, definition, definition.style, `${path}.style`, id, d, "style", () => true, void 0, context.root);
|
|
2102
|
+
if (style) node.style = style;
|
|
2103
|
+
for (const condition of CONDITIONS) {
|
|
2104
|
+
const inStyle = lifted[condition.name];
|
|
2105
|
+
const given = input[condition.name];
|
|
2106
|
+
if (inStyle !== void 0 && given !== void 0) {
|
|
2107
|
+
d.error(
|
|
2108
|
+
"style/twice",
|
|
2109
|
+
`${condition.name} is given twice: as ${condition.name}={\u2026} and in style. Give it once, in style: style={{ ${condition.name}: { \u2026 } }}`,
|
|
2110
|
+
`${path}.style.${condition.name}`,
|
|
2111
|
+
id
|
|
2112
|
+
);
|
|
2113
|
+
continue;
|
|
2114
|
+
}
|
|
2115
|
+
let value = inStyle ?? given;
|
|
2116
|
+
if (value === void 0) continue;
|
|
2117
|
+
const at = inStyle !== void 0 ? `${path}.style.${condition.name}` : `${path}.${condition.name}`;
|
|
2118
|
+
if (condition.roots && !condition.roots(context.root)) {
|
|
2119
|
+
if (condition.notAllowed) d.error(condition.notAllowed.code, condition.notAllowed.message, at, id);
|
|
2120
|
+
continue;
|
|
2121
|
+
}
|
|
2122
|
+
if (isObject2(value) && Object.keys(value).some((key) => CONDITION_NAMES.has(key))) {
|
|
2123
|
+
const own3 = {};
|
|
2124
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
2125
|
+
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);
|
|
2126
|
+
else own3[key] = inner;
|
|
2127
|
+
}
|
|
2128
|
+
value = own3;
|
|
2129
|
+
}
|
|
2130
|
+
const read2 = readStyles(value, definition, definition.style, at, id, d, condition.name, (spec, key) => condition.allows(spec, key, definition), condition.for, context.root);
|
|
2131
|
+
if (read2) node[condition.name] = read2;
|
|
1865
2132
|
}
|
|
1866
2133
|
if (input.hidden !== void 0) {
|
|
1867
2134
|
const hidden2 = readHidden(input.hidden, `${path}.hidden`, id, context);
|
|
@@ -1871,7 +2138,7 @@ function readNode(input, path, parent, context) {
|
|
|
1871
2138
|
if (children.length) node.children = children;
|
|
1872
2139
|
return node;
|
|
1873
2140
|
}
|
|
1874
|
-
function readProps(input, definition, path, id, d) {
|
|
2141
|
+
function readProps(input, definition, path, id, d, root) {
|
|
1875
2142
|
const out = {};
|
|
1876
2143
|
if (input !== void 0 && !isObject2(input)) {
|
|
1877
2144
|
d.error("props/invalid", "props is an object", path, id);
|
|
@@ -1884,9 +2151,14 @@ function readProps(input, definition, path, id, d) {
|
|
|
1884
2151
|
d.error("prop/unknown", unknownProp(definition, key), `${path}.${key}`, id);
|
|
1885
2152
|
continue;
|
|
1886
2153
|
}
|
|
2154
|
+
if (!rootTakes(spec.roots, root)) {
|
|
2155
|
+
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);
|
|
2156
|
+
continue;
|
|
2157
|
+
}
|
|
1887
2158
|
const parsed = spec.kind.parse(value);
|
|
1888
|
-
if (parsed.ok)
|
|
1889
|
-
|
|
2159
|
+
if (parsed.ok) {
|
|
2160
|
+
if (!spec.kind.empty?.(parsed.value)) out[key] = parsed.value;
|
|
2161
|
+
} else d.error("prop/invalid", `${key}: ${parsed.message}`, `${path}.${key}`, id);
|
|
1890
2162
|
}
|
|
1891
2163
|
for (const [key, spec] of Object.entries(definition.props)) {
|
|
1892
2164
|
if (spec.required && !(key in out) && !(isObject2(input) && input[key] !== void 0)) {
|
|
@@ -1903,6 +2175,8 @@ var UNSUPPORTED_CSS = {
|
|
|
1903
2175
|
marginRight: "use padding",
|
|
1904
2176
|
display: "the component sets it",
|
|
1905
2177
|
position: "use a Layer for content placed over other content",
|
|
2178
|
+
backgroundImage: "put a Layer around it, its first child an Image (the Layer's height crops it to cover), the rest over it",
|
|
2179
|
+
background: "backgroundColor for a color; for an image, a Layer: its first child an Image, the rest over it",
|
|
1906
2180
|
float: "use a Row to put things side by side",
|
|
1907
2181
|
// Names Elements used before it took CSS's.
|
|
1908
2182
|
align: "it's alignItems now",
|
|
@@ -1916,10 +2190,12 @@ function styleHint(definition, specs, key) {
|
|
|
1916
2190
|
if (Object.prototype.hasOwnProperty.call(definition.props, key)) return `: it's a prop, ${key}={\u2026}`;
|
|
1917
2191
|
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";
|
|
1918
2192
|
if (UNSUPPORTED_CSS[key]) return `: ${UNSUPPORTED_CSS[key]}`;
|
|
2193
|
+
const condition = conditionHint(definition, key);
|
|
2194
|
+
if (condition) return `: ${condition}`;
|
|
1919
2195
|
const meant = nearest(key, Object.keys(specs));
|
|
1920
2196
|
return meant ? `: did you mean "${meant}"?` : "";
|
|
1921
2197
|
}
|
|
1922
|
-
function readStyles(input, definition, specs, path, id, d, what, allowed) {
|
|
2198
|
+
function readStyles(input, definition, specs, path, id, d, what, allowed, forLabel, root) {
|
|
1923
2199
|
if (input === void 0) return void 0;
|
|
1924
2200
|
if (!isObject2(input)) {
|
|
1925
2201
|
d.error(`${what}/invalid`, `${what} is an object of style properties`, path, id);
|
|
@@ -1933,8 +2209,12 @@ function readStyles(input, definition, specs, path, id, d, what, allowed) {
|
|
|
1933
2209
|
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);
|
|
1934
2210
|
continue;
|
|
1935
2211
|
}
|
|
2212
|
+
if (!rootTakes(spec.roots, root)) {
|
|
2213
|
+
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);
|
|
2214
|
+
continue;
|
|
2215
|
+
}
|
|
1936
2216
|
if (!allowed(spec, key)) {
|
|
1937
|
-
d.error(`${what}/not-allowed`,
|
|
2217
|
+
d.error(`${what}/not-allowed`, `"${key}" can't be set ${forLabel ?? "here"}`, `${path}.${key}`, id);
|
|
1938
2218
|
continue;
|
|
1939
2219
|
}
|
|
1940
2220
|
const parsed = spec.kind.parse(value);
|
|
@@ -1945,7 +2225,7 @@ function readStyles(input, definition, specs, path, id, d, what, allowed) {
|
|
|
1945
2225
|
}
|
|
1946
2226
|
function readHidden(input, path, id, context) {
|
|
1947
2227
|
const { d } = context;
|
|
1948
|
-
if (!context.responsive) {
|
|
2228
|
+
if (!context.root?.responsive) {
|
|
1949
2229
|
d.error("hidden/not-supported", "Hiding on phones or desktops is for an Email or a Page", path, id);
|
|
1950
2230
|
return void 0;
|
|
1951
2231
|
}
|
|
@@ -1956,7 +2236,7 @@ function readHidden(input, path, id, context) {
|
|
|
1956
2236
|
const out = {};
|
|
1957
2237
|
if (input.mobile === true) out.mobile = true;
|
|
1958
2238
|
if (input.desktop === true) out.desktop = true;
|
|
1959
|
-
if (
|
|
2239
|
+
if (!shownAnywhere({ hidden: out })) d.warning("hidden/everywhere", "Hidden on phones and on desktops, it never shows", path, id);
|
|
1960
2240
|
return Object.keys(out).length ? out : void 0;
|
|
1961
2241
|
}
|
|
1962
2242
|
function readChildren(input, definition, path, id, context) {
|
|
@@ -1989,6 +2269,22 @@ function readChildren(input, definition, path, id, context) {
|
|
|
1989
2269
|
});
|
|
1990
2270
|
return out;
|
|
1991
2271
|
}
|
|
2272
|
+
var same = (a2, b) => JSON.stringify(a2) === JSON.stringify(b);
|
|
2273
|
+
function reportDeclared(d, node, at, key, value, declared, target, outlook = true) {
|
|
2274
|
+
if (!declared) return;
|
|
2275
|
+
for (const rule2 of [].concat(declared)) {
|
|
2276
|
+
if (rule2.level === "full") continue;
|
|
2277
|
+
if (rule2.level === "outlook" && !outlook) continue;
|
|
2278
|
+
if (rule2.values && !rule2.values.some((v) => same(v, value))) continue;
|
|
2279
|
+
if (rule2.when && !rule2.when(value, node)) continue;
|
|
2280
|
+
const asked = typeof value === "string" ? `${key}: "${value}"` : key;
|
|
2281
|
+
const clients = target === "email" ? rule2.clients ?? (rule2.level === "outlook" ? ["outlook"] : void 0) : void 0;
|
|
2282
|
+
const output = clients ? "some email clients" : target === "web" ? "a page" : target;
|
|
2283
|
+
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}`];
|
|
2284
|
+
const [said, as] = rule2.source === "assumed" ? ["info", `${message} (not checked)`] : [severity, message];
|
|
2285
|
+
d.add(code, said, as, at, node.id, { clients, fix: rule2.fix, see: rule2.feature && caniemail(rule2.feature) });
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
1992
2288
|
var MAX_DEPTH = 32;
|
|
1993
2289
|
function templatePathsOf(document, paths) {
|
|
1994
2290
|
const places = /* @__PURE__ */ new Map();
|
|
@@ -2005,7 +2301,7 @@ function templatePathsOf(document, paths) {
|
|
|
2005
2301
|
return path;
|
|
2006
2302
|
};
|
|
2007
2303
|
}
|
|
2008
|
-
function expandDocument(document, options) {
|
|
2304
|
+
function expandDocument(document, options, target) {
|
|
2009
2305
|
const registry = registryOf(options);
|
|
2010
2306
|
const d = new Diagnostics();
|
|
2011
2307
|
const paths = /* @__PURE__ */ new WeakMap();
|
|
@@ -2017,7 +2313,20 @@ function expandDocument(document, options) {
|
|
|
2017
2313
|
collect(document.root);
|
|
2018
2314
|
const rootDefinition = registry.get(document.root.type);
|
|
2019
2315
|
const responsive = !!rootDefinition?.responsive;
|
|
2316
|
+
const unshown = (node, path) => {
|
|
2317
|
+
const declared = target && registry.get(node.type)?.conditionSupport;
|
|
2318
|
+
if (!declared) return;
|
|
2319
|
+
for (const condition of CONDITIONS) {
|
|
2320
|
+
const block2 = blockOf(node, condition);
|
|
2321
|
+
const byStyle = declared[condition.name];
|
|
2322
|
+
if (!block2 || !byStyle || condition.hiddenKey && node.hidden?.[condition.hiddenKey]) continue;
|
|
2323
|
+
for (const [key, value] of Object.entries(block2)) {
|
|
2324
|
+
reportDeclared(d, { type: node.type, id: node.id, props: node.props ?? {} }, `${path}.${condition.name}.${key}`, key, value, byStyle[key]?.[target], target);
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
};
|
|
2020
2328
|
const visit = (given, path, depth, self = path) => {
|
|
2329
|
+
unshown(given, path);
|
|
2021
2330
|
const arrange = registry.get(given.type)?.arrange;
|
|
2022
2331
|
if (!arrange) return walk(given, path, self, depth);
|
|
2023
2332
|
const origins = /* @__PURE__ */ new Map();
|
|
@@ -2044,27 +2353,34 @@ function expandDocument(document, options) {
|
|
|
2044
2353
|
return void 0;
|
|
2045
2354
|
}
|
|
2046
2355
|
let raw2;
|
|
2356
|
+
const reported = [];
|
|
2047
2357
|
try {
|
|
2048
2358
|
const style = { ...definition.defaultStyle, ...node.style };
|
|
2049
|
-
raw2 = definition.expand(propsWithDefaults(definition, node.props), { register: (custom) => registry.add(custom), style, responsive });
|
|
2359
|
+
raw2 = definition.expand(propsWithDefaults(definition, node.props), { register: (custom) => registry.add(custom), style, responsive, report: (diagnostic) => reported.push(diagnostic) });
|
|
2050
2360
|
} catch (error) {
|
|
2051
2361
|
d.error("custom/render", `${node.type} failed to render: ${error instanceof Error ? error.message : String(error)}`, path, node.id);
|
|
2052
2362
|
return void 0;
|
|
2053
2363
|
}
|
|
2364
|
+
const at = `${path}(${node.type})`;
|
|
2365
|
+
for (const diagnostic of reported) d.list.push({ ...diagnostic, path: diagnostic.path === "root" || diagnostic.path.startsWith("root.") ? at + diagnostic.path.slice(4) : at });
|
|
2054
2366
|
if (Array.isArray(raw2) || typeof raw2 !== "object" || raw2 === null) {
|
|
2055
2367
|
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);
|
|
2056
2368
|
return void 0;
|
|
2057
2369
|
}
|
|
2058
|
-
const { node: expanded, diagnostics } = checkFragment(raw2, { parent,
|
|
2370
|
+
const { node: expanded, diagnostics } = checkFragment(raw2, { parent, root: rootDefinition, idPrefix: node.id ?? null, taken, registry, path: `${path}(${node.type})` });
|
|
2059
2371
|
for (const diagnostic of diagnostics) d.list.push(diagnostic);
|
|
2060
2372
|
if (!expanded) return void 0;
|
|
2061
2373
|
collectIds(expanded);
|
|
2062
2374
|
if (node.style?.flex !== void 0) expanded.style = { ...expanded.style, flex: node.style.flex };
|
|
2063
2375
|
if (node.hidden) expanded.hidden = { ...expanded.hidden, ...node.hidden };
|
|
2064
2376
|
if (node.id && !expanded.id && !CUSTOM_TYPE.test(node.type)) expanded.id = node.id;
|
|
2065
|
-
for (const
|
|
2066
|
-
|
|
2067
|
-
|
|
2377
|
+
for (const condition of CONDITIONS) {
|
|
2378
|
+
const rendered = registry.get(expanded.type);
|
|
2379
|
+
for (const [key, value] of Object.entries(node[condition.name] ?? {})) {
|
|
2380
|
+
const spec = rendered?.style[key];
|
|
2381
|
+
if (rendered && spec && condition.allows(spec, key, rendered)) expanded[condition.name] = { [key]: value, ...expanded[condition.name] };
|
|
2382
|
+
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);
|
|
2383
|
+
}
|
|
2068
2384
|
}
|
|
2069
2385
|
const inner = registry.get(expanded.type);
|
|
2070
2386
|
if (isCustom(inner)) return expandOne(expanded, inner, parent, `${path}(${node.type})`, depth + 1, self);
|
|
@@ -2516,11 +2832,11 @@ var TEXT_CSS = [
|
|
|
2516
2832
|
["textAlign", "text-align", (v) => v],
|
|
2517
2833
|
["textTransform", "text-transform", (v) => v]
|
|
2518
2834
|
];
|
|
2519
|
-
var
|
|
2835
|
+
var same2 = (a2, b) => a2 === b || JSON.stringify(a2) === JSON.stringify(b);
|
|
2520
2836
|
function textDeclarations(text22, parent, always = []) {
|
|
2521
2837
|
const out = [];
|
|
2522
2838
|
for (const [key, property, write3] of TEXT_CSS) {
|
|
2523
|
-
if (!parent || always.includes(key) || !
|
|
2839
|
+
if (!parent || always.includes(key) || !same2(text22[key], parent[key])) out.push([property, write3(text22[key])]);
|
|
2524
2840
|
}
|
|
2525
2841
|
return out;
|
|
2526
2842
|
}
|
|
@@ -2596,7 +2912,9 @@ function classesOf(name, value) {
|
|
|
2596
2912
|
if (name === "class") return (Array.isArray(value) ? value : [value]).filter((v) => typeof v === "string" && v !== "");
|
|
2597
2913
|
const text22 = styleText(value, true);
|
|
2598
2914
|
if (!text22) return [];
|
|
2599
|
-
|
|
2915
|
+
if (name === "phone") return [rule(MOBILE.sheet.media, `${MOBILE.sheet.selector}{${text22}}`)];
|
|
2916
|
+
const selector = name === "hoverWithin" ? HOVER.sheet.within ?? HOVER.sheet.selector : HOVER.sheet.selector;
|
|
2917
|
+
return [rule(HOVER.sheet.media, `${selector}{${text22}}`, HOVER.sheet.level)];
|
|
2600
2918
|
}
|
|
2601
2919
|
function attributes2(props) {
|
|
2602
2920
|
const out = [];
|
|
@@ -2604,7 +2922,7 @@ function attributes2(props) {
|
|
|
2604
2922
|
let slot = -1;
|
|
2605
2923
|
for (const [name, value] of Object.entries(props)) {
|
|
2606
2924
|
if (name === "children" || name === "style" || value === void 0 || value === null || value === false) continue;
|
|
2607
|
-
if (name === "class" || name === "phone" || name === "hover") {
|
|
2925
|
+
if (name === "class" || name === "phone" || name === "hover" || name === "hoverWithin") {
|
|
2608
2926
|
const found = classesOf(name, value);
|
|
2609
2927
|
if (found.length && slot < 0) slot = out.push("") - 1;
|
|
2610
2928
|
classes.push(...found);
|
|
@@ -2620,8 +2938,8 @@ function withId(element2, id) {
|
|
|
2620
2938
|
if (!id) return element2;
|
|
2621
2939
|
const tag = /^<[a-zA-Z0-9]+/.exec(element2.markup)?.[0];
|
|
2622
2940
|
if (!tag) throw new Error("[Elements] An id goes on an element");
|
|
2623
|
-
const
|
|
2624
|
-
return new Html2(
|
|
2941
|
+
const insert2 = (markup2) => `${tag} id="${escapeAttr(id)}"${markup2.slice(tag.length)}`;
|
|
2942
|
+
return new Html2(insert2(element2.markup), { ...element2.options, start: element2.options.start && insert2(element2.options.start) });
|
|
2625
2943
|
}
|
|
2626
2944
|
function h(type, props, ...children) {
|
|
2627
2945
|
const all = props ?? {};
|
|
@@ -2648,6 +2966,98 @@ function Merged({ children }) {
|
|
|
2648
2966
|
}
|
|
2649
2967
|
var open = (element2) => new Html2(element2.options.start ?? element2.markup);
|
|
2650
2968
|
var close = (tag) => new Html2(`</${tag}>`);
|
|
2969
|
+
var BLOCK_MAP_VERSION = 1;
|
|
2970
|
+
var MARK_PAIRS = 16;
|
|
2971
|
+
var BlockMarks = class {
|
|
2972
|
+
constructor(pair) {
|
|
2973
|
+
this.nodes = [];
|
|
2974
|
+
this.open = String.fromCharCode(64976 + pair * 2);
|
|
2975
|
+
this.close = String.fromCharCode(64977 + pair * 2);
|
|
2976
|
+
}
|
|
2977
|
+
};
|
|
2978
|
+
var current = null;
|
|
2979
|
+
function withBlockMarks(marks, write3) {
|
|
2980
|
+
const before = current;
|
|
2981
|
+
current = marks;
|
|
2982
|
+
try {
|
|
2983
|
+
return write3();
|
|
2984
|
+
} finally {
|
|
2985
|
+
current = before;
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
function markBlock(html2, node) {
|
|
2989
|
+
if (!current) return html2;
|
|
2990
|
+
const mark = `${current.open}${current.nodes.length}${current.close}`;
|
|
2991
|
+
const marked = insert(html2, mark, false) ?? insert(html2, mark, true);
|
|
2992
|
+
if (!marked) return html2;
|
|
2993
|
+
current.nodes.push(node);
|
|
2994
|
+
return marked;
|
|
2995
|
+
}
|
|
2996
|
+
var OUTLOOK_OPEN = "<!--[if mso]>";
|
|
2997
|
+
var OUTLOOK_CLOSE = "<![endif]-->";
|
|
2998
|
+
var TAG2 = /<[a-zA-Z][a-zA-Z0-9]*/y;
|
|
2999
|
+
function tagEnd(markup2, outlook) {
|
|
3000
|
+
for (let i = markup2.indexOf("<"); i >= 0; i = markup2.indexOf("<", i + 1)) {
|
|
3001
|
+
if (markup2.startsWith(OUTLOOK_OPEN, i)) {
|
|
3002
|
+
if (outlook) {
|
|
3003
|
+
i += OUTLOOK_OPEN.length - 1;
|
|
3004
|
+
continue;
|
|
3005
|
+
}
|
|
3006
|
+
const end = markup2.indexOf(OUTLOOK_CLOSE, i);
|
|
3007
|
+
if (end < 0) return -1;
|
|
3008
|
+
i = end + OUTLOOK_CLOSE.length - 1;
|
|
3009
|
+
continue;
|
|
3010
|
+
}
|
|
3011
|
+
if (markup2.startsWith("<!--", i)) {
|
|
3012
|
+
const end = markup2.indexOf("-->", i + 4);
|
|
3013
|
+
if (end < 0) return -1;
|
|
3014
|
+
i = end + 2;
|
|
3015
|
+
continue;
|
|
3016
|
+
}
|
|
3017
|
+
TAG2.lastIndex = i;
|
|
3018
|
+
const tag = TAG2.exec(markup2);
|
|
3019
|
+
if (tag) return i + tag[0].length;
|
|
3020
|
+
}
|
|
3021
|
+
return -1;
|
|
3022
|
+
}
|
|
3023
|
+
function insert(html2, mark, outlook) {
|
|
3024
|
+
const { list, kind: kind2, start } = html2.options;
|
|
3025
|
+
if (list) {
|
|
3026
|
+
for (let i = 0; i < list.length; i++) {
|
|
3027
|
+
const marked = insert(list[i], mark, outlook);
|
|
3028
|
+
if (marked) return new Html2(html2.markup, { ...html2.options, list: list.map((piece, j) => j === i ? marked : piece) });
|
|
3029
|
+
}
|
|
3030
|
+
return null;
|
|
3031
|
+
}
|
|
3032
|
+
if (kind2 === "outlook" && !outlook) return null;
|
|
3033
|
+
const at = tagEnd(html2.markup, outlook);
|
|
3034
|
+
if (at < 0) return null;
|
|
3035
|
+
const put = (text22) => text22.slice(0, at) + mark + text22.slice(at);
|
|
3036
|
+
return new Html2(put(html2.markup), { ...html2.options, start: start !== void 0 && at <= start.length ? put(start) : start });
|
|
3037
|
+
}
|
|
3038
|
+
function readMarks(marks, written) {
|
|
3039
|
+
const found = [];
|
|
3040
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3041
|
+
const pieces2 = [];
|
|
3042
|
+
let length32 = 0;
|
|
3043
|
+
let last = 0;
|
|
3044
|
+
for (const match of written.matchAll(new RegExp(`${marks.open}(\\d+)${marks.close}`, "g"))) {
|
|
3045
|
+
const index = Number(match[1]);
|
|
3046
|
+
const node = marks.nodes[index];
|
|
3047
|
+
if (!node || seen.has(index)) return null;
|
|
3048
|
+
seen.add(index);
|
|
3049
|
+
const before = written.slice(last, match.index);
|
|
3050
|
+
pieces2.push(before);
|
|
3051
|
+
length32 += before.length;
|
|
3052
|
+
last = match.index + match[0].length;
|
|
3053
|
+
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;
|
|
3054
|
+
found.push({ node, at: length32 });
|
|
3055
|
+
}
|
|
3056
|
+
pieces2.push(written.slice(last));
|
|
3057
|
+
const text22 = pieces2.join("");
|
|
3058
|
+
if (text22.includes(marks.open) || text22.includes(marks.close)) return null;
|
|
3059
|
+
return { text: text22, found };
|
|
3060
|
+
}
|
|
2651
3061
|
function utf8Bytes(text22) {
|
|
2652
3062
|
let bytes = 0;
|
|
2653
3063
|
for (let i = 0; i < text22.length; i++) {
|
|
@@ -2734,7 +3144,7 @@ function fontFaces2(root, { swap }) {
|
|
|
2734
3144
|
return [...faces.values()];
|
|
2735
3145
|
}
|
|
2736
3146
|
var isNode2 = (child) => typeof child !== "string";
|
|
2737
|
-
var shown = (node) =>
|
|
3147
|
+
var shown = (node) => shownAnywhere(node);
|
|
2738
3148
|
function link2(props) {
|
|
2739
3149
|
const { href, target } = linkParts(props.href);
|
|
2740
3150
|
return {
|
|
@@ -2969,7 +3379,7 @@ var Button2 = ({ node, style, mobile, fill, phoneText: phoneText22, text: text22
|
|
|
2969
3379
|
{
|
|
2970
3380
|
align: TEXT_ALIGN[node.text.textAlign],
|
|
2971
3381
|
bgcolor: bgcolor(style.backgroundColor),
|
|
2972
|
-
|
|
3382
|
+
hoverWithin: { backgroundColor: node.hover?.backgroundColor },
|
|
2973
3383
|
phone: { border: mobile.border, backgroundColor: mobile.backgroundColor },
|
|
2974
3384
|
style: { backgroundColor: style.backgroundColor, border: style.border, borderRadius: style.borderRadius, msoPaddingAlt: insets2(style.padding) }
|
|
2975
3385
|
},
|
|
@@ -2980,8 +3390,11 @@ var Button2 = ({ node, style, mobile, fill, phoneText: phoneText22, text: text22
|
|
|
2980
3390
|
phone: [phoneText22, { padding: mobile.padding }],
|
|
2981
3391
|
hover: { color: node.hover?.color },
|
|
2982
3392
|
style: [
|
|
2983
|
-
// A button with a width fills it.
|
|
2984
|
-
|
|
3393
|
+
// A button with a width fills it. Its label's words stay whole (the email's word-break: break-word
|
|
3394
|
+
// is for long links in text): a button narrower than its padding otherwise sized itself to one letter
|
|
3395
|
+
// and drew its label a letter a line, in Gmail and browsers; it grows to its longest word instead, as
|
|
3396
|
+
// classic Outlook's and Apple Mail's do (captures of Elements 0.3.0, e3-fresh/g-0054).
|
|
3397
|
+
{ display: width.width !== void 0 ? "block" : "inline-block", padding: style.padding, wordBreak: "normal" },
|
|
2985
3398
|
fullText(node),
|
|
2986
3399
|
{ textDecoration: style.textDecoration ?? "none", borderRadius: style.borderRadius }
|
|
2987
3400
|
]
|
|
@@ -3445,27 +3858,42 @@ function propsOf(node, place, options) {
|
|
|
3445
3858
|
options
|
|
3446
3859
|
};
|
|
3447
3860
|
}
|
|
3861
|
+
var UNMAPPED = /* @__PURE__ */ new Set(["LineBreak", "MergeTag"]);
|
|
3448
3862
|
function write(node, place, options) {
|
|
3449
3863
|
if (place.inline) {
|
|
3450
3864
|
if (!shown(node)) return raw("");
|
|
3451
3865
|
const component2 = INLINES[node.type];
|
|
3452
3866
|
if (!component2) throw new Error(`[Elements] The email exporter can't render a ${node.type} inside text`);
|
|
3453
|
-
|
|
3867
|
+
const html2 = component2(propsOf(node, place, options));
|
|
3868
|
+
return UNMAPPED.has(node.type) ? html2 : markBlock(html2, node);
|
|
3454
3869
|
}
|
|
3455
3870
|
const component = BLOCKS[node.type];
|
|
3456
3871
|
if (!component) throw new Error(`[Elements] The email exporter can't render a ${node.type} here (in a ${place.parent.type})`);
|
|
3457
|
-
return component(propsOf(node, place, options));
|
|
3872
|
+
return markBlock(component(propsOf(node, place, options)), node);
|
|
3458
3873
|
}
|
|
3459
3874
|
var EMAIL_PHONE_MARGIN = 20;
|
|
3460
3875
|
var GMAIL_CLIP_BYTES = 102400;
|
|
3461
3876
|
var GMAIL_STYLE_BYTES = 16384;
|
|
3462
3877
|
function renderEmail(resolved, options = {}) {
|
|
3463
3878
|
const root = resolved.root;
|
|
3464
|
-
const sheet = new Stylesheet(options.classPrefix);
|
|
3465
3879
|
const d = new Diagnostics();
|
|
3466
3880
|
const fonts = fontFaces2(root, { swap: false });
|
|
3467
3881
|
const width = root.width ?? 600;
|
|
3468
|
-
const
|
|
3882
|
+
const write3 = (marks) => {
|
|
3883
|
+
const sheet2 = new Stylesheet(options.classPrefix);
|
|
3884
|
+
const markup2 = withStylesheet(sheet2, () => withBlockMarks(marks, () => String(markBlock(Email2(propsOf(root, { parent: root, fill: false, mobile: {}, inline: false, rtl: false }, options)), root))));
|
|
3885
|
+
return { sheet: sheet2, markup: markup2 };
|
|
3886
|
+
};
|
|
3887
|
+
let written = options.blockMap ? null : write3(null);
|
|
3888
|
+
let marked = null;
|
|
3889
|
+
for (let pair = 0; !written; pair++) {
|
|
3890
|
+
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");
|
|
3891
|
+
const marks = new BlockMarks(pair);
|
|
3892
|
+
const attempt = write3(marks);
|
|
3893
|
+
marked = readMarks(marks, attempt.markup);
|
|
3894
|
+
if (marked) written = { sheet: attempt.sheet, markup: marked.text };
|
|
3895
|
+
}
|
|
3896
|
+
const { sheet, markup: email } = written;
|
|
3469
3897
|
const background = root.style.backgroundColor;
|
|
3470
3898
|
const lang = root.props.lang;
|
|
3471
3899
|
const dir = root.props.dir;
|
|
@@ -3489,7 +3917,8 @@ function renderEmail(resolved, options = {}) {
|
|
|
3489
3917
|
`<style>${stylesheet}</style>`
|
|
3490
3918
|
].join("");
|
|
3491
3919
|
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>` : "";
|
|
3492
|
-
const
|
|
3920
|
+
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"]])}>`;
|
|
3921
|
+
const body = `${preheader}${opening}${email}</div>`;
|
|
3493
3922
|
const bodyDeclarations = [
|
|
3494
3923
|
["margin", "0"],
|
|
3495
3924
|
["padding", "0"],
|
|
@@ -3497,20 +3926,38 @@ function renderEmail(resolved, options = {}) {
|
|
|
3497
3926
|
["background-color", background]
|
|
3498
3927
|
];
|
|
3499
3928
|
const bodyStyle = css2(bodyDeclarations);
|
|
3500
|
-
const
|
|
3929
|
+
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)}>`;
|
|
3930
|
+
const html2 = `${shell}${body}</body></html>`;
|
|
3501
3931
|
const bytes = utf8Bytes(html2);
|
|
3932
|
+
const gmails = ["gmail", "gmail-app", "gmail-app-other"];
|
|
3502
3933
|
if (bytes > GMAIL_CLIP_BYTES) {
|
|
3503
|
-
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"
|
|
3934
|
+
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, {
|
|
3935
|
+
clients: gmails,
|
|
3936
|
+
fix: "shorten the email, or move long sections to a page: what follows the clip is behind a link"
|
|
3937
|
+
});
|
|
3504
3938
|
}
|
|
3505
3939
|
const styleBytes = utf8Bytes(stylesheet);
|
|
3506
3940
|
if (styleBytes > GMAIL_STYLE_BYTES) {
|
|
3507
|
-
d.warning("email/style-size", `The stylesheet is ${Math.round(styleBytes / 1024)} KB; Gmail reads ${GMAIL_STYLE_BYTES / 1024} KB of a <style>`, "root"
|
|
3941
|
+
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, {
|
|
3942
|
+
clients: gmails,
|
|
3943
|
+
fix: "use fewer phone and hover styles: each one adds a rule"
|
|
3944
|
+
});
|
|
3508
3945
|
}
|
|
3509
|
-
return { html: html2, head, body, bodyStyle, diagnostics: d.list };
|
|
3946
|
+
return { html: html2, head, body, bodyStyle, diagnostics: d.list, ...marked ? { blocks: blockMap(resolved, marked.found, email, utf8Bytes(shell + preheader + opening)) } : {} };
|
|
3947
|
+
}
|
|
3948
|
+
function blockMap(resolved, found, email, start) {
|
|
3949
|
+
const templatePath = resolved.templatePath ?? ((path) => path);
|
|
3950
|
+
let offset = start;
|
|
3951
|
+
let from = 0;
|
|
3952
|
+
const entries = found.map(({ node, at }) => {
|
|
3953
|
+
offset += utf8Bytes(email.slice(from, at));
|
|
3954
|
+
from = at;
|
|
3955
|
+
return { path: templatePath(node.path), ...node.id ? { nodeId: node.id } : {}, offset };
|
|
3956
|
+
});
|
|
3957
|
+
return { version: BLOCK_MAP_VERSION, unit: "utf8", entries };
|
|
3510
3958
|
}
|
|
3511
3959
|
var isNode3 = (child) => typeof child !== "string";
|
|
3512
|
-
var
|
|
3513
|
-
var same2 = (a2, b) => JSON.stringify(a2) === JSON.stringify(b);
|
|
3960
|
+
var same3 = (a2, b) => JSON.stringify(a2) === JSON.stringify(b);
|
|
3514
3961
|
function support(root, target) {
|
|
3515
3962
|
const d = new Diagnostics();
|
|
3516
3963
|
const visit = (node) => {
|
|
@@ -3518,41 +3965,81 @@ function support(root, target) {
|
|
|
3518
3965
|
const byProp = definition.defaultStyleBy ? definition.defaultStyleBy.values[String(node.props[definition.defaultStyleBy.prop])] : void 0;
|
|
3519
3966
|
for (const [key, value] of Object.entries(node.style)) {
|
|
3520
3967
|
const fallback2 = byProp?.[key] ?? definition.defaultStyle?.[key];
|
|
3521
|
-
|
|
3968
|
+
if (same3(value, fallback2)) continue;
|
|
3969
|
+
reportDeclared(d, node, `${node.path}.style.${key}`, key, value, definition.style[key]?.support?.[target], target);
|
|
3522
3970
|
}
|
|
3523
|
-
for (const [key, value] of Object.entries(node.props))
|
|
3524
|
-
|
|
3971
|
+
for (const [key, value] of Object.entries(node.props)) {
|
|
3972
|
+
if (same3(value, definition.props[key]?.default)) continue;
|
|
3973
|
+
reportDeclared(d, node, `${node.path}.props.${key}`, key, value, definition.props[key]?.support?.[target], target);
|
|
3974
|
+
}
|
|
3975
|
+
for (const condition of CONDITIONS) {
|
|
3976
|
+
const block2 = blockOf(node, condition);
|
|
3977
|
+
if (!block2 || condition.hiddenKey && node.hidden?.[condition.hiddenKey]) continue;
|
|
3978
|
+
const own3 = condition.support[target];
|
|
3979
|
+
reportDeclared(d, node, `${node.path}.${condition.name}`, condition.name, block2, own3, target);
|
|
3980
|
+
if (!own3 || own3.level === "none") continue;
|
|
3981
|
+
for (const [key, value] of Object.entries(block2)) {
|
|
3982
|
+
const rules = condition.styles?.[key]?.[target] ?? definition.style[key]?.support?.[target];
|
|
3983
|
+
reportDeclared(d, node, `${node.path}.${condition.name}.${key}`, key, value, rules, target, condition.outlookApplies);
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
for (const child of node.children) if (isNode3(child) && shownAnywhere(child)) visit(child);
|
|
3525
3987
|
};
|
|
3526
3988
|
visit(root);
|
|
3527
3989
|
return d.list;
|
|
3528
3990
|
}
|
|
3529
|
-
function
|
|
3530
|
-
const rules = declared?.[target];
|
|
3531
|
-
if (!rules || same2(value, fallback2)) return;
|
|
3532
|
-
for (const rule2 of [].concat(rules)) {
|
|
3533
|
-
if (rule2.level === "full") continue;
|
|
3534
|
-
if (rule2.values && !rule2.values.some((v) => same2(v, value))) continue;
|
|
3535
|
-
if (rule2.when && !rule2.when(value, node)) continue;
|
|
3536
|
-
const at = `${node.path}.${where}.${key}`;
|
|
3537
|
-
const asked = typeof value === "string" ? `${key}: "${value}"` : key;
|
|
3538
|
-
const output = target === "web" ? "a page" : target;
|
|
3539
|
-
if (rule2.level === "none") d.warning(`${target}/unsupported`, `${asked} isn't possible in ${output}: ${rule2.shows}`, at, node.id);
|
|
3540
|
-
else if (rule2.level === "partial") d.add(`${target}/degraded`, "info", `${asked} on this ${node.type} in ${output}: ${rule2.shows}`, at, node.id);
|
|
3541
|
-
else d.add("email/outlook-degraded", "info", `${asked} on this ${node.type} in classic Outlook: ${rule2.shows}`, at, node.id);
|
|
3542
|
-
}
|
|
3543
|
-
}
|
|
3544
|
-
function emailSupport(root) {
|
|
3991
|
+
function emailSupport(root, templatePath = (path) => path) {
|
|
3545
3992
|
const d = new Diagnostics();
|
|
3546
3993
|
d.list.push(...support(root, "email"));
|
|
3547
3994
|
const webFonts = new Set((root.props.fonts ?? []).map((face) => face.family.toLowerCase()));
|
|
3995
|
+
const phone = { styled: false, phoneHidden: false, desktopHidden: false };
|
|
3548
3996
|
const visit = (node) => {
|
|
3549
3997
|
structure(node, d);
|
|
3550
3998
|
fallback(node, webFonts, d);
|
|
3999
|
+
if (node.type === "Html") htmlBlock(node, d);
|
|
4000
|
+
if (node.type === "Image") imageFormat(node, "src", d);
|
|
4001
|
+
if (node.type === "Video" && node.props.thumbnail !== void 0) imageFormat(node, "thumbnail", d);
|
|
4002
|
+
if (node.mobile && Object.keys(node.mobile).length) phone.styled = true;
|
|
4003
|
+
if (node.hidden?.mobile && !builtIn(templatePath(node.path))) phone.phoneHidden = true;
|
|
4004
|
+
if (node.hidden?.desktop && !builtIn(templatePath(node.path))) phone.desktopHidden = true;
|
|
3551
4005
|
for (const child of node.children) if (isNode2(child) && shown(child)) visit(child);
|
|
3552
4006
|
};
|
|
3553
4007
|
visit(root);
|
|
4008
|
+
phoneStyles(root, phone, d);
|
|
3554
4009
|
return d.list;
|
|
3555
4010
|
}
|
|
4011
|
+
function builtIn(place) {
|
|
4012
|
+
const made = [...place.matchAll(/\((\w+)\)/g)].pop()?.[1];
|
|
4013
|
+
return !!made && Object.prototype.hasOwnProperty.call(COMPONENTS, made);
|
|
4014
|
+
}
|
|
4015
|
+
var NO_PHONE_STYLES = ["gmail-app-other", "sfr", "laposte", "gmx", "web-de", "rainloop"];
|
|
4016
|
+
function phoneStyles(root, phone, d) {
|
|
4017
|
+
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);
|
|
4018
|
+
if (!parts.length) return;
|
|
4019
|
+
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, {
|
|
4020
|
+
clients: NO_PHONE_STYLES,
|
|
4021
|
+
fix: phone.desktopHidden ? "show what a reader needs on desktop too, and hide on phones only what they can do without" : void 0,
|
|
4022
|
+
see: caniemail("css-at-media")
|
|
4023
|
+
});
|
|
4024
|
+
}
|
|
4025
|
+
var IMAGE_FORMATS = {
|
|
4026
|
+
webp: { clients: ["outlook"], feature: "image-webp", name: "a WebP" },
|
|
4027
|
+
avif: { clients: ["outlook", "fastmail", "gmx", "web-de", "orange", "sfr", "mail-ru", "ionos"], feature: "image-avif", name: "an AVIF" },
|
|
4028
|
+
svg: { clients: ["mail-ru"], feature: "image-svg", name: "an SVG" }
|
|
4029
|
+
};
|
|
4030
|
+
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];
|
|
4031
|
+
function imageFormat(node, key, d) {
|
|
4032
|
+
const src = assetParts(node.props[key]).src;
|
|
4033
|
+
const format = FORMAT_IN.map((pattern) => pattern.exec(src)?.[1]).find(Boolean)?.toLowerCase();
|
|
4034
|
+
const known = format && Object.prototype.hasOwnProperty.call(IMAGE_FORMATS, format) ? IMAGE_FORMATS[format] : void 0;
|
|
4035
|
+
if (!known) return;
|
|
4036
|
+
const what = key === "src" ? "This Image is" : "This Video's thumbnail is";
|
|
4037
|
+
d.warning("email/image-format", `${what} ${known.name} file, which these email clients don't show`, `${node.path}.props.${key}`, node.id, {
|
|
4038
|
+
clients: known.clients,
|
|
4039
|
+
fix: "use a PNG, JPEG or GIF file",
|
|
4040
|
+
see: caniemail(known.feature)
|
|
4041
|
+
});
|
|
4042
|
+
}
|
|
3556
4043
|
function fallback(node, webFonts, d) {
|
|
3557
4044
|
const stack = node.style.fontFamily;
|
|
3558
4045
|
if (typeof stack !== "string" || !webFonts.size) return;
|
|
@@ -3561,9 +4048,10 @@ function fallback(node, webFonts, d) {
|
|
|
3561
4048
|
if (!last || !webFonts.has(last.toLowerCase())) return;
|
|
3562
4049
|
d.warning(
|
|
3563
4050
|
"email/font-fallback",
|
|
3564
|
-
`This font stack ends with the web font "${last}": where it doesn't load
|
|
4051
|
+
`This font stack ends with the web font "${last}": where it doesn't load, the email client picks its own font`,
|
|
3565
4052
|
`${node.path}.style.fontFamily`,
|
|
3566
|
-
node.id
|
|
4053
|
+
node.id,
|
|
4054
|
+
{ clients: NO_WEB_FONTS, fix: `end it with a font the client has: "${stack}, Arial, sans-serif"`, see: caniemail("css-at-font-face") }
|
|
3567
4055
|
);
|
|
3568
4056
|
}
|
|
3569
4057
|
function structure(node, d) {
|
|
@@ -3576,7 +4064,8 @@ function structure(node, d) {
|
|
|
3576
4064
|
"info",
|
|
3577
4065
|
`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`,
|
|
3578
4066
|
path,
|
|
3579
|
-
id
|
|
4067
|
+
id,
|
|
4068
|
+
{ clients: ["outlook"], fix: "put less in it, or let it stack on phones (stackOnMobile)" }
|
|
3580
4069
|
);
|
|
3581
4070
|
}
|
|
3582
4071
|
const lines = wrapsWithGap(node);
|
|
@@ -3612,6 +4101,86 @@ function structure(node, d) {
|
|
|
3612
4101
|
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);
|
|
3613
4102
|
}
|
|
3614
4103
|
}
|
|
4104
|
+
var HTML_CSS = [
|
|
4105
|
+
{ 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" },
|
|
4106
|
+
{ 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" },
|
|
4107
|
+
{
|
|
4108
|
+
pattern: /(^|[\s;{"'])position\s*:\s*absolute\b/i,
|
|
4109
|
+
clients: ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail", "gmail-app", "gmail-app-other", "yahoo", "aol", "hey", "sfr", "laposte", "mail-ru", "gmx", "web-de"],
|
|
4110
|
+
removes: "position: absolute: the client removes it",
|
|
4111
|
+
fix: "lay it out without position (a Layer puts content over an image)",
|
|
4112
|
+
feature: "css-position"
|
|
4113
|
+
},
|
|
4114
|
+
{
|
|
4115
|
+
// Outlook.com removes every position (captures); the others not named keep relative.
|
|
4116
|
+
pattern: /(^|[\s;{"'])position\s*:\s*relative\b/i,
|
|
4117
|
+
clients: ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail", "gmail-app", "gmail-app-other", "mail-ru", "gmx", "web-de"],
|
|
4118
|
+
removes: "position: relative: the client removes it, and what it moves stays where it is",
|
|
4119
|
+
fix: "move it with padding instead",
|
|
4120
|
+
feature: "css-position",
|
|
4121
|
+
use: "relative"
|
|
4122
|
+
},
|
|
4123
|
+
{
|
|
4124
|
+
pattern: /var\(\s*--|(^|[\s;{"'])--[\w-]+\s*:/i,
|
|
4125
|
+
clients: ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail", "gmail-app", "gmail-app-other", "yahoo", "aol", "orange", "mail-ru", "fastmail", "gmx", "web-de"],
|
|
4126
|
+
removes: "CSS variables: the client removes var() and their declarations",
|
|
4127
|
+
fix: "write the values themselves",
|
|
4128
|
+
feature: "css-variables"
|
|
4129
|
+
},
|
|
4130
|
+
{
|
|
4131
|
+
pattern: /gradient\(/i,
|
|
4132
|
+
clients: ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail-app", "gmail-app-other", "yahoo", "aol", "orange", "gmx", "web-de"],
|
|
4133
|
+
removes: "a gradient: the client removes it",
|
|
4134
|
+
fix: "give it a solid background color too, or use an image",
|
|
4135
|
+
feature: "css-linear-gradient"
|
|
4136
|
+
},
|
|
4137
|
+
{ 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()" },
|
|
4138
|
+
{ 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)" },
|
|
4139
|
+
{
|
|
4140
|
+
pattern: /<style[\s>]/i,
|
|
4141
|
+
clients: ["gmail", "gmail-app", "gmail-app-other", "protonmail", "gmx", "web-de", "sfr", "laposte", "rainloop"],
|
|
4142
|
+
removes: "a <style> in the email's body: the client removes it, and what it styles shows without it",
|
|
4143
|
+
fix: "write the styles inline, on the elements they're for",
|
|
4144
|
+
feature: "html-style"
|
|
4145
|
+
},
|
|
4146
|
+
{ 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" },
|
|
4147
|
+
{ 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" },
|
|
4148
|
+
{ 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" },
|
|
4149
|
+
{
|
|
4150
|
+
pattern: /display\s*:\s*inline-flex\b/i,
|
|
4151
|
+
clients: ["outlook", "gmail-app-other", "yahoo", "aol", "orange", "gmx", "web-de"],
|
|
4152
|
+
removes: "display: inline-flex: the client lays it out without flex",
|
|
4153
|
+
fix: "lay it out with a Row",
|
|
4154
|
+
feature: "css-display-flex",
|
|
4155
|
+
use: "inline"
|
|
4156
|
+
},
|
|
4157
|
+
{ 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" },
|
|
4158
|
+
{ 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" },
|
|
4159
|
+
{
|
|
4160
|
+
pattern: /object-fit\s*:/i,
|
|
4161
|
+
clients: ["outlook", "gmail-app-other", "yahoo", "aol", "orange", "gmx", "web-de"],
|
|
4162
|
+
removes: "object-fit: the client stretches the image to its box",
|
|
4163
|
+
fix: "crop the image file to the size it's shown at",
|
|
4164
|
+
feature: "css-object-fit"
|
|
4165
|
+
},
|
|
4166
|
+
{
|
|
4167
|
+
pattern: /(^|[\s;{"'])aspect-ratio\s*:/i,
|
|
4168
|
+
clients: ["outlook", "outlook-com", "outlook-mac", "outlook-app", "gmail", "gmail-app", "gmail-app-other", "yahoo", "aol", "samsung-email", "thunderbird", "fastmail", "gmx", "web-de", "orange"],
|
|
4169
|
+
removes: "aspect-ratio: the client removes it",
|
|
4170
|
+
fix: "give it a width and a height",
|
|
4171
|
+
feature: "css-aspect-ratio"
|
|
4172
|
+
}
|
|
4173
|
+
];
|
|
4174
|
+
var OUTLOOK_ONLY2 = /<!--\[if\s+(?:(?:gte?|lte?)\s+)?mso[^\]]*\]>[\s\S]*?<!\[endif\]-->/gi;
|
|
4175
|
+
function htmlBlock(node, d) {
|
|
4176
|
+
if (typeof node.props.html !== "string") return;
|
|
4177
|
+
const markup2 = node.props.html.replace(OUTLOOK_ONLY2, "");
|
|
4178
|
+
for (const check of HTML_CSS) {
|
|
4179
|
+
if (check.pattern.test(markup2)) {
|
|
4180
|
+
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) });
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
3615
4184
|
var isNode4 = (child) => typeof child !== "string";
|
|
3616
4185
|
var CLEARLY = 1.25;
|
|
3617
4186
|
var sides = (value) => {
|
|
@@ -3743,15 +4312,15 @@ function classesFor(node, parent) {
|
|
|
3743
4312
|
const classes = [];
|
|
3744
4313
|
if (node.mobile) {
|
|
3745
4314
|
const { width, ...rest } = node.mobile;
|
|
3746
|
-
if (Object.keys(rest).length) classes.push(rule(
|
|
4315
|
+
if (Object.keys(rest).length) classes.push(rule(MOBILE.sheet.media, `${MOBILE.sheet.selector}{${css2(declarationsOf(rest), true)}}`));
|
|
3747
4316
|
const line22 = node.type === "Divider" && node.style.padding !== void 0 ? ">hr" : "";
|
|
3748
|
-
if (width !== void 0) classes.push(rule(
|
|
4317
|
+
if (width !== void 0) classes.push(rule(MOBILE.sheet.media, `${MOBILE.sheet.selector}${line22}{${css2(declarationsOf({ width }), true)}}`));
|
|
3749
4318
|
const own3 = node.style.width === void 0 && (node.style.flex === "none" || node.style.flex === 0);
|
|
3750
4319
|
const basis = own3 ? void 0 : length2(phoneWidthInRow(node, parent));
|
|
3751
4320
|
if (basis !== void 0) classes.push(rule("phone", `&{flex:0 1 ${basis}!important}`));
|
|
3752
4321
|
}
|
|
3753
4322
|
classes.push(...hidingClasses(node.hidden));
|
|
3754
|
-
if (node.hover) classes.push(rule(
|
|
4323
|
+
if (node.hover) classes.push(rule(HOVER.sheet.media, `${HOVER.sheet.selector}{${css2(declarationsOf(node.hover), true)}}`, HOVER.sheet.level));
|
|
3755
4324
|
if (node.type === "Row" && node.props.stackOnMobile !== false) {
|
|
3756
4325
|
classes.push(
|
|
3757
4326
|
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")
|
|
@@ -3959,17 +4528,17 @@ var Layer3 = ({ node, style, parent, placement, classes, children }) => /* @__PU
|
|
|
3959
4528
|
},
|
|
3960
4529
|
children()
|
|
3961
4530
|
);
|
|
3962
|
-
var
|
|
4531
|
+
var current2 = { page: 1, total: 1 };
|
|
3963
4532
|
function numbering(numbers, write3) {
|
|
3964
|
-
const before =
|
|
3965
|
-
|
|
4533
|
+
const before = current2;
|
|
4534
|
+
current2 = numbers;
|
|
3966
4535
|
try {
|
|
3967
4536
|
return write3();
|
|
3968
4537
|
} finally {
|
|
3969
|
-
|
|
4538
|
+
current2 = before;
|
|
3970
4539
|
}
|
|
3971
4540
|
}
|
|
3972
|
-
var PageNumber2 = ({ node }) => /* @__PURE__ */ h(Fragment, null, String(node.props.total ?
|
|
4541
|
+
var PageNumber2 = ({ node }) => /* @__PURE__ */ h(Fragment, null, String(node.props.total ? current2.total : current2.page));
|
|
3973
4542
|
function documentCss(root) {
|
|
3974
4543
|
const box2 = pageBox(root.props);
|
|
3975
4544
|
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}}`;
|
|
@@ -4143,8 +4712,23 @@ var Video3 = ({ node, style, placement, classes }) => /* @__PURE__ */ h(
|
|
|
4143
4712
|
))
|
|
4144
4713
|
);
|
|
4145
4714
|
var PAGE_PHONE_WIDTH = 480;
|
|
4146
|
-
|
|
4147
|
-
|
|
4715
|
+
function pagePhoneWidth(root) {
|
|
4716
|
+
const mobile = root.props.breakpoints?.mobile;
|
|
4717
|
+
return typeof mobile === "number" ? mobile : PAGE_PHONE_WIDTH;
|
|
4718
|
+
}
|
|
4719
|
+
function pageBreakpoints(root) {
|
|
4720
|
+
const mobile = root.props.breakpoints?.mobile;
|
|
4721
|
+
const width = typeof root.props.width === "number" ? root.props.width : void 0;
|
|
4722
|
+
if (root.type !== "Page" || typeof mobile !== "number" || width === void 0 || mobile < width) return [];
|
|
4723
|
+
return [
|
|
4724
|
+
{
|
|
4725
|
+
code: "page/phone-width",
|
|
4726
|
+
severity: "warning",
|
|
4727
|
+
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}`,
|
|
4728
|
+
path: "root.props.breakpoints"
|
|
4729
|
+
}
|
|
4730
|
+
];
|
|
4731
|
+
}
|
|
4148
4732
|
var COMPONENTS2 = {
|
|
4149
4733
|
Section: Section3,
|
|
4150
4734
|
Row: Row3,
|
|
@@ -4205,7 +4789,8 @@ function renderPage(resolved, options = {}) {
|
|
|
4205
4789
|
const writer2 = document ? Document2 : root.type === "Canvas" ? Canvas2 : Page2;
|
|
4206
4790
|
const body = withStylesheet(sheet, () => String(writer2(propsOf2(root, root, [], options))));
|
|
4207
4791
|
const fixed = !root.definition.responsive;
|
|
4208
|
-
const
|
|
4792
|
+
const phoneWidth2 = pagePhoneWidth(root);
|
|
4793
|
+
const stylesheet = [...fonts, sheet.css(fixed ? null : `@media (max-width: ${phoneWidth2}px)`, fixed ? null : `@media (min-width: ${phoneWidth2 + 1}px)`), document ? documentCss(root) : ""].join("");
|
|
4209
4794
|
const head = [
|
|
4210
4795
|
'<meta charset="utf-8">',
|
|
4211
4796
|
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
@@ -4225,7 +4810,7 @@ function renderText(resolved, options = {}) {
|
|
|
4225
4810
|
const tag = options.mergeTag ?? ((name) => `{{${name}}}`);
|
|
4226
4811
|
const inline2 = (node, upper = false) => node.children.map((child) => {
|
|
4227
4812
|
if (typeof child === "string") return upper ? child.toUpperCase() : child;
|
|
4228
|
-
if (child
|
|
4813
|
+
if (!shownAnywhere(child)) return "";
|
|
4229
4814
|
switch (child.type) {
|
|
4230
4815
|
case "LineBreak":
|
|
4231
4816
|
return "\n";
|
|
@@ -4249,7 +4834,7 @@ function renderText(resolved, options = {}) {
|
|
|
4249
4834
|
return alt ? `[${upper ? alt.toUpperCase() : alt}]` : "";
|
|
4250
4835
|
};
|
|
4251
4836
|
const blocks = (node) => node.children.flatMap((child) => {
|
|
4252
|
-
if (typeof child === "string" || child
|
|
4837
|
+
if (typeof child === "string" || !shownAnywhere(child)) return [];
|
|
4253
4838
|
switch (child.type) {
|
|
4254
4839
|
case "Heading":
|
|
4255
4840
|
return [tidy(inline2(child, Number(child.props.level) <= 2))];
|
|
@@ -4287,8 +4872,8 @@ function renderText(resolved, options = {}) {
|
|
|
4287
4872
|
return blocks(child);
|
|
4288
4873
|
}
|
|
4289
4874
|
});
|
|
4290
|
-
const
|
|
4291
|
-
const list = (node, indent) => node.children.filter((child) => typeof child !== "string" &&
|
|
4875
|
+
const shown2 = (node) => shownAnywhere(node);
|
|
4876
|
+
const list = (node, indent) => node.children.filter((child) => typeof child !== "string" && shown2(child)).map((item, index) => {
|
|
4292
4877
|
const marker2 = node.props.type === "number" ? `${Number(node.props.start) + index}. ` : "- ";
|
|
4293
4878
|
const lines = [];
|
|
4294
4879
|
let run = [];
|
|
@@ -4304,13 +4889,13 @@ function renderText(resolved, options = {}) {
|
|
|
4304
4889
|
}
|
|
4305
4890
|
flush();
|
|
4306
4891
|
if (!lines.length) lines.push(`${indent}${marker2}`.trimEnd());
|
|
4307
|
-
if (
|
|
4892
|
+
if (shown2(child)) lines.push(list(child, `${indent} `));
|
|
4308
4893
|
}
|
|
4309
4894
|
flush();
|
|
4310
4895
|
return (lines.length ? lines : [`${indent}${marker2}`.trimEnd()]).join("\n");
|
|
4311
4896
|
}).join("\n");
|
|
4312
|
-
const table = (node) => node.children.filter((row) => typeof row !== "string" &&
|
|
4313
|
-
(row) => row.children.filter((cell) => typeof cell !== "string" &&
|
|
4897
|
+
const table = (node) => node.children.filter((row) => typeof row !== "string" && shown2(row)).map(
|
|
4898
|
+
(row) => row.children.filter((cell) => typeof cell !== "string" && shown2(cell)).map((cell) => tidy(inline2(cell))).join(" | ")
|
|
4314
4899
|
).join("\n");
|
|
4315
4900
|
return blocks(resolved.root).filter((block2) => block2.length > 0).join("\n\n");
|
|
4316
4901
|
}
|
|
@@ -4353,52 +4938,56 @@ var OUTPUTS = {
|
|
|
4353
4938
|
Email: { target: "email", ready: true }
|
|
4354
4939
|
};
|
|
4355
4940
|
function prepareTemplate(input, options = {}) {
|
|
4941
|
+
checkEmailClients(options.emailClients);
|
|
4356
4942
|
const registry = registryOf(options);
|
|
4357
4943
|
const checked = checkDocument(input, registry);
|
|
4358
4944
|
const diagnostics = [...checked.diagnostics];
|
|
4359
4945
|
const failed = () => diagnostics.some((d) => d.severity === "error");
|
|
4360
4946
|
if (!checked.document || failed()) return { resolved: null, target: null, diagnostics };
|
|
4361
4947
|
const root = checked.document.root.type;
|
|
4948
|
+
const done = (resolved2, target) => {
|
|
4949
|
+
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" });
|
|
4950
|
+
return { resolved: resolved2, target, diagnostics: root === "Email" ? forEmailClients(diagnostics, options.emailClients) : diagnostics };
|
|
4951
|
+
};
|
|
4362
4952
|
const output = OUTPUTS[root];
|
|
4363
4953
|
if (!output?.ready) {
|
|
4364
4954
|
diagnostics.push({ code: "output/unavailable", severity: "error", message: output?.later ?? `A ${root} can't be rendered yet`, path: "root" });
|
|
4365
|
-
return
|
|
4955
|
+
return done(null, null);
|
|
4366
4956
|
}
|
|
4367
|
-
const expanded = expandDocument(checked.document, registry);
|
|
4957
|
+
const expanded = expandDocument(checked.document, registry, output.target);
|
|
4368
4958
|
diagnostics.push(...expanded.diagnostics);
|
|
4369
|
-
if (failed()) return
|
|
4959
|
+
if (failed()) return done(null, output.target);
|
|
4370
4960
|
diagnostics.push(...checkTargetExpanded(expanded.document, output.target, registry, expanded.paths));
|
|
4371
|
-
if (failed()) return
|
|
4961
|
+
if (failed()) return done(null, output.target);
|
|
4372
4962
|
const resolved = resolveExpanded(expanded.document, registry, expanded.paths);
|
|
4373
4963
|
diagnostics.push(...resolved.diagnostics);
|
|
4374
|
-
const found = output.target === "email" ? emailSupport(resolved.root) : support(resolved.root, output.target);
|
|
4964
|
+
const found = output.target === "email" ? emailSupport(resolved.root, resolved.templatePath) : support(resolved.root, output.target);
|
|
4375
4965
|
if (root === "Document" || root === "Canvas") found.push(...overflow(resolved.root));
|
|
4376
|
-
|
|
4966
|
+
found.push(...pageBreakpoints(resolved.root));
|
|
4377
4967
|
diagnostics.push(...inTemplate(resolved, found));
|
|
4378
|
-
return
|
|
4968
|
+
return done(failed() ? null : resolved, output.target);
|
|
4379
4969
|
}
|
|
4380
4970
|
function inTemplate(resolved, diagnostics) {
|
|
4381
4971
|
const { templatePath } = resolved;
|
|
4382
4972
|
return templatePath ? diagnostics.map((diagnostic) => ({ ...diagnostic, path: templatePath(diagnostic.path) })) : diagnostics;
|
|
4383
4973
|
}
|
|
4384
|
-
function pageNumbersOutside(root) {
|
|
4385
|
-
const out = [];
|
|
4386
|
-
const visit = (node) => {
|
|
4387
|
-
if (node.type === "PageNumber") {
|
|
4388
|
-
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 } : {} });
|
|
4389
|
-
}
|
|
4390
|
-
for (const child of node.children) if (typeof child !== "string") visit(child);
|
|
4391
|
-
};
|
|
4392
|
-
visit(root);
|
|
4393
|
-
return out;
|
|
4394
|
-
}
|
|
4395
4974
|
function renderTemplate(input, options = {}) {
|
|
4975
|
+
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" }]);
|
|
4396
4976
|
const { resolved, target, diagnostics } = prepareTemplate(input, options);
|
|
4397
4977
|
if (!resolved) throw new ElementsError(diagnostics);
|
|
4398
4978
|
const text22 = renderText(resolved, options);
|
|
4399
4979
|
const written = target === "email" ? renderEmail(resolved, options) : { ...renderPage(resolved, options), diagnostics: [] };
|
|
4400
|
-
const
|
|
4401
|
-
|
|
4980
|
+
const shown2 = (html2) => options.pretty ? prettyHtml(html2) : html2;
|
|
4981
|
+
const blocks = "blocks" in written ? written.blocks : void 0;
|
|
4982
|
+
return {
|
|
4983
|
+
html: shown2(written.html),
|
|
4984
|
+
head: shown2(written.head),
|
|
4985
|
+
body: shown2(written.body),
|
|
4986
|
+
bodyStyle: written.bodyStyle,
|
|
4987
|
+
text: text22,
|
|
4988
|
+
diagnostics: [...diagnostics, ...inTemplate(resolved, forEmailClients(written.diagnostics, options.emailClients))],
|
|
4989
|
+
...blocks ? { blocks } : {}
|
|
4990
|
+
};
|
|
4402
4991
|
}
|
|
4403
4992
|
|
|
4404
4993
|
// src/utils/collect.ts
|
|
@@ -4429,7 +5018,7 @@ function isAsyncComponent(type) {
|
|
|
4429
5018
|
}
|
|
4430
5019
|
var SHARED2 = Symbol.for("@hyper-elements/react/render-builds");
|
|
4431
5020
|
var using = globalThis[SHARED2] ?? (globalThis[SHARED2] = []);
|
|
4432
|
-
var
|
|
5021
|
+
var same4 = (a2, b) => a2.renderToString === b.renderToString;
|
|
4433
5022
|
function dispatcher() {
|
|
4434
5023
|
const internals = React2__default.default;
|
|
4435
5024
|
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;
|
|
@@ -4476,7 +5065,7 @@ function secondBuild(outer) {
|
|
|
4476
5065
|
for (const requirer of requirers) {
|
|
4477
5066
|
try {
|
|
4478
5067
|
const build = requirer()?.(id);
|
|
4479
|
-
if (build?.renderToStaticMarkup && !
|
|
5068
|
+
if (build?.renderToStaticMarkup && !same4(build, defaultBuild__namespace) && ![null, outer].includes(dispatcherOf(build))) {
|
|
4480
5069
|
second = build;
|
|
4481
5070
|
break search;
|
|
4482
5071
|
}
|
|
@@ -4490,11 +5079,11 @@ function secondBuild(outer) {
|
|
|
4490
5079
|
var buildOf = (outer) => outer === null ? void 0 : [defaultBuild__namespace, second].find((b) => !!b && dispatcherOf(b) === outer);
|
|
4491
5080
|
function withBuild(render) {
|
|
4492
5081
|
const outer = dispatcher();
|
|
4493
|
-
let build =
|
|
5082
|
+
let build = current3();
|
|
4494
5083
|
const theirs = using.length === 0 || collecting() !== void 0 ? buildOf(outer) : void 0;
|
|
4495
5084
|
const busy = [];
|
|
4496
5085
|
if (theirs) {
|
|
4497
|
-
const free = (b) => !
|
|
5086
|
+
const free = (b) => !same4(b, theirs) && !using.some((u) => same4(u, b));
|
|
4498
5087
|
const other = free(defaultBuild__namespace) ? defaultBuild__namespace : secondBuild(outer);
|
|
4499
5088
|
if (other && free(other)) {
|
|
4500
5089
|
busy.push(theirs);
|
|
@@ -4519,10 +5108,10 @@ function rendersHere(build) {
|
|
|
4519
5108
|
return false;
|
|
4520
5109
|
}
|
|
4521
5110
|
}
|
|
4522
|
-
function
|
|
5111
|
+
function current3() {
|
|
4523
5112
|
return using[using.length - 1] ?? defaultBuild__namespace;
|
|
4524
5113
|
}
|
|
4525
|
-
var renderToString = (element2, options) =>
|
|
5114
|
+
var renderToString = (element2, options) => current3().renderToString(element2, options);
|
|
4526
5115
|
|
|
4527
5116
|
// src/utils/render-scope.ts
|
|
4528
5117
|
function withRenderScope(render) {
|
|
@@ -4571,7 +5160,7 @@ function withoutAsync(children, collection) {
|
|
|
4571
5160
|
}
|
|
4572
5161
|
var ENTITIES2 = { "&": "&", "<": "<", ">": ">", """: '"', "'": "'" };
|
|
4573
5162
|
var unescape = (text4) => text4.replace(/&|<|>|"|'/g, (entity) => ENTITIES2[entity]);
|
|
4574
|
-
var NODE_KEYS2 = /* @__PURE__ */ new Set(["id", "style", "
|
|
5163
|
+
var NODE_KEYS2 = /* @__PURE__ */ new Set(["id", "style", "hidden"]);
|
|
4575
5164
|
function toJsonNode({ type, props }, diagnostics) {
|
|
4576
5165
|
const node = { type, children: [] };
|
|
4577
5166
|
const semantic = {};
|
|
@@ -4696,10 +5285,10 @@ function collectTemplate(element2) {
|
|
|
4696
5285
|
path: "jsx"
|
|
4697
5286
|
});
|
|
4698
5287
|
}
|
|
4699
|
-
return { document: null, diagnostics, components };
|
|
5288
|
+
return { document: null, diagnostics, components, collected: null };
|
|
4700
5289
|
}
|
|
4701
5290
|
const result = checkDocument({ root: top[0] }, new Registry(components));
|
|
4702
|
-
return { document: result.document, diagnostics: [...diagnostics, ...result.diagnostics], components };
|
|
5291
|
+
return { document: result.document, diagnostics: [...diagnostics, ...result.diagnostics], components, collected: top[0] };
|
|
4703
5292
|
}
|
|
4704
5293
|
|
|
4705
5294
|
// src/primitives/components.tsx
|
|
@@ -4770,9 +5359,8 @@ function propsOf3(node) {
|
|
|
4770
5359
|
return {
|
|
4771
5360
|
...node.id !== void 0 ? { id: node.id } : {},
|
|
4772
5361
|
...node.props,
|
|
4773
|
-
|
|
4774
|
-
...node.mobile ? { mobile: node.mobile } : {},
|
|
4775
|
-
...node.hover ? { hover: node.hover } : {},
|
|
5362
|
+
// Phone and hover styles inside the style, as a template writes them now.
|
|
5363
|
+
...node.style && Object.keys(node.style).length || node.mobile || node.hover ? { style: { ...node.style, ...node.mobile ? { mobile: node.mobile } : {}, ...node.hover ? { hover: node.hover } : {} } } : {},
|
|
4776
5364
|
...node.hidden ? { hidden: node.hidden } : {}
|
|
4777
5365
|
};
|
|
4778
5366
|
}
|
|
@@ -4826,15 +5414,15 @@ function text2(value, flat) {
|
|
|
4826
5414
|
return kept ? value : `{${JSON.stringify(value)}}`;
|
|
4827
5415
|
}
|
|
4828
5416
|
var inline = (node) => (node.children ?? []).some((child) => typeof child === "string" || ["Span", "Link", "LineBreak", "MergeTag", "PageNumber"].includes(child.type));
|
|
4829
|
-
function element(node, indent, flat, path,
|
|
5417
|
+
function element(node, indent, flat, path, report) {
|
|
4830
5418
|
if (!TYPE_NAME.test(node.type)) {
|
|
4831
|
-
|
|
5419
|
+
report.push(`${path}: ${JSON.stringify(node.type)} isn't a component's name: it's left out of the code`);
|
|
4832
5420
|
return flat ? "" : "{/* left out: not a component's name */}";
|
|
4833
5421
|
}
|
|
4834
5422
|
const name = tagName(node.type);
|
|
4835
|
-
const attributes5 = Object.entries(propsOf3(node)).filter(([key]) => PROP_NAME.test(key) || (
|
|
5423
|
+
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("");
|
|
4836
5424
|
const children = node.children ?? [];
|
|
4837
|
-
const child = (c, i, inner2, oneLine) => typeof c === "string" ? text2(c, oneLine) : element(c, inner2, oneLine, `${path}.children[${i}]`,
|
|
5425
|
+
const child = (c, i, inner2, oneLine) => typeof c === "string" ? text2(c, oneLine) : element(c, inner2, oneLine, `${path}.children[${i}]`, report);
|
|
4838
5426
|
if (!children.length) return `<${name}${attributes5} />`;
|
|
4839
5427
|
if (flat || inline(node)) return `<${name}${attributes5}>${children.map((c, i) => child(c, i, indent, true)).join("")}</${name}>`;
|
|
4840
5428
|
const inner = `${indent} `;
|
|
@@ -4842,9 +5430,9 @@ function element(node, indent, flat, path, report2) {
|
|
|
4842
5430
|
${children.map((c, i) => `${inner}${child(c, i, inner, false)}`).join("\n")}
|
|
4843
5431
|
${indent}</${name}>`;
|
|
4844
5432
|
}
|
|
4845
|
-
function printTemplate(root, name = "Template",
|
|
5433
|
+
function printTemplate(root, name = "Template", report = []) {
|
|
4846
5434
|
if (!IDENTIFIER.test(name)) {
|
|
4847
|
-
|
|
5435
|
+
report.push(`the component's name ${JSON.stringify(name)} isn't one JavaScript can write: it's named Template`);
|
|
4848
5436
|
name = "Template";
|
|
4849
5437
|
}
|
|
4850
5438
|
const used = /* @__PURE__ */ new Set();
|
|
@@ -4861,7 +5449,7 @@ function printTemplate(root, name = "Template", report2 = []) {
|
|
|
4861
5449
|
|
|
4862
5450
|
export default function ${name}() {
|
|
4863
5451
|
return (
|
|
4864
|
-
${element(root, " ", false, "root",
|
|
5452
|
+
${element(root, " ", false, "root", report)}
|
|
4865
5453
|
);
|
|
4866
5454
|
}
|
|
4867
5455
|
`;
|
|
@@ -5382,7 +5970,7 @@ function readHidden2(tool, type, values, say) {
|
|
|
5382
5970
|
// src/builder/malformed.ts
|
|
5383
5971
|
var DEPTH = 32;
|
|
5384
5972
|
var coerces = (value) => ["toString", "valueOf"].some((key) => Object.hasOwn(value, key) && typeof value[key] !== "function");
|
|
5385
|
-
function withoutMalformed(design,
|
|
5973
|
+
function withoutMalformed(design, report) {
|
|
5386
5974
|
const found = [];
|
|
5387
5975
|
const walk = (value, path) => {
|
|
5388
5976
|
if (!value || typeof value !== "object" || path.length > DEPTH) return;
|
|
@@ -5400,7 +5988,7 @@ function withoutMalformed(design, report2) {
|
|
|
5400
5988
|
const last = path[path.length - 1];
|
|
5401
5989
|
if (Array.isArray(holder)) holder[Number(last)] = null;
|
|
5402
5990
|
else if (holder) delete holder[last];
|
|
5403
|
-
|
|
5991
|
+
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`);
|
|
5404
5992
|
}
|
|
5405
5993
|
return copy2;
|
|
5406
5994
|
}
|
|
@@ -5409,8 +5997,8 @@ function withoutMalformed(design, report2) {
|
|
|
5409
5997
|
var ZERO = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
5410
5998
|
var obj = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
5411
5999
|
var str = (value) => typeof value === "string" ? value : void 0;
|
|
5412
|
-
var
|
|
5413
|
-
var unless = (value, taken) => value === void 0 ||
|
|
6000
|
+
var same5 = (a2, b) => JSON.stringify(a2)?.toLowerCase() === JSON.stringify(b)?.toLowerCase();
|
|
6001
|
+
var unless = (value, taken) => value === void 0 || same5(value, taken) ? void 0 : value;
|
|
5414
6002
|
var lineHeight4 = (value) => {
|
|
5415
6003
|
const read2 = lineHeight3(value);
|
|
5416
6004
|
return typeof read2 === "string" && read2.endsWith("%") ? Math.round(parseFloat(read2) * 100) / 1e4 : read2;
|
|
@@ -5459,7 +6047,7 @@ function phoneText2(values) {
|
|
|
5459
6047
|
}
|
|
5460
6048
|
|
|
5461
6049
|
// src/builder/read-runs.ts
|
|
5462
|
-
var
|
|
6050
|
+
var TAG3 = /<(\/?)([a-zA-Z][a-zA-Z0-9]*)([^>]*?)\/?\s*>/g;
|
|
5463
6051
|
var ATTRIBUTE = /([a-zA-Z-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
5464
6052
|
var ENTITIES3 = {
|
|
5465
6053
|
amp: "&",
|
|
@@ -5600,16 +6188,16 @@ function parseRuns(html2, { weight = 400, links = true, builder = false } = {})
|
|
|
5600
6188
|
const left = [];
|
|
5601
6189
|
if (/<[!?]|<\/(?![a-zA-Z])/.test(html2)) left.push("a comment or a processing instruction");
|
|
5602
6190
|
const lines = [];
|
|
5603
|
-
let
|
|
5604
|
-
const stack = [
|
|
6191
|
+
let current4 = { tag: "", node: null, children: [], weight };
|
|
6192
|
+
const stack = [current4];
|
|
5605
6193
|
const top = () => stack[stack.length - 1];
|
|
5606
6194
|
const endParagraph = (last2 = false) => {
|
|
5607
6195
|
if (stack.length > 1) left.push("a paragraph inside text that's styled");
|
|
5608
|
-
const runs = tidy2(
|
|
6196
|
+
const runs = tidy2(current4.children);
|
|
5609
6197
|
if (runs.length > 1 && isBreak(runs[runs.length - 1]) && !(builder && last2)) runs.pop();
|
|
5610
6198
|
if (runs.length) lines.push(runs.length === 1 && isBreak(runs[0]) ? [] : runs);
|
|
5611
|
-
|
|
5612
|
-
stack.splice(0, stack.length,
|
|
6199
|
+
current4 = { tag: "", node: null, children: [], weight };
|
|
6200
|
+
stack.splice(0, stack.length, current4);
|
|
5613
6201
|
};
|
|
5614
6202
|
let last = 0;
|
|
5615
6203
|
let afterParagraph = false;
|
|
@@ -5619,7 +6207,7 @@ function parseRuns(html2, { weight = 400, links = true, builder = false } = {})
|
|
|
5619
6207
|
const value = decode3(raw2, left);
|
|
5620
6208
|
if (value) top().children.push(value);
|
|
5621
6209
|
};
|
|
5622
|
-
for (const match of html2.matchAll(
|
|
6210
|
+
for (const match of html2.matchAll(TAG3)) {
|
|
5623
6211
|
const tag = match[2].toLowerCase();
|
|
5624
6212
|
text4(html2.slice(last, match.index), tag === "p" || tag === "div");
|
|
5625
6213
|
last = match.index + match[0].length;
|
|
@@ -5962,7 +6550,7 @@ function textJsonHtml(textJson) {
|
|
|
5962
6550
|
}
|
|
5963
6551
|
|
|
5964
6552
|
// src/builder/text.ts
|
|
5965
|
-
var
|
|
6553
|
+
var TAG4 = /<\s*(\/?)\s*([a-zA-Z][a-zA-Z0-9]*)([^>]*?)(\/?)\s*>/g;
|
|
5966
6554
|
var styleOf2 = (attrs2) => /\bstyle\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(attrs2)?.slice(1).find((v) => v !== void 0) ?? "";
|
|
5967
6555
|
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()]));
|
|
5968
6556
|
var ATTRIBUTES = `(?:[^>"']|"[^"]*"|'[^']*')*`;
|
|
@@ -6051,7 +6639,7 @@ var DEEPEST = 64;
|
|
|
6051
6639
|
var VOID3 = /* @__PURE__ */ new Set(["br", "img", "hr", "input", "meta", "link", "wbr", "source", "area", "col", "embed", "param", "track"]);
|
|
6052
6640
|
function tooDeep(html2) {
|
|
6053
6641
|
let depth = 0;
|
|
6054
|
-
for (const match of html2.matchAll(
|
|
6642
|
+
for (const match of html2.matchAll(TAG4)) {
|
|
6055
6643
|
if (match[1]) depth = Math.max(0, depth - 1);
|
|
6056
6644
|
else if (!match[4] && !VOID3.has(match[2].toLowerCase()) && ++depth > DEEPEST) return true;
|
|
6057
6645
|
}
|
|
@@ -6076,7 +6664,7 @@ function readText(html2, { weight = 400, pages = false, links = true, fontSize =
|
|
|
6076
6664
|
inline2 = "";
|
|
6077
6665
|
};
|
|
6078
6666
|
let at = 0;
|
|
6079
|
-
for (const match of [...html2.matchAll(
|
|
6667
|
+
for (const match of [...html2.matchAll(TAG4)]) {
|
|
6080
6668
|
if (match.index < at) continue;
|
|
6081
6669
|
const tag = match[2].toLowerCase();
|
|
6082
6670
|
if (match[1] || !/^(p|ul|ol|h[1-6])$/.test(tag)) continue;
|
|
@@ -6173,7 +6761,7 @@ function readList(tag, attrs2, inner, runs, fontSize, left) {
|
|
|
6173
6761
|
const items2 = [];
|
|
6174
6762
|
let gap;
|
|
6175
6763
|
let at = 0;
|
|
6176
|
-
for (const match of [...inner.matchAll(
|
|
6764
|
+
for (const match of [...inner.matchAll(TAG4)]) {
|
|
6177
6765
|
if (match.index < at || match[1] || match[2].toLowerCase() !== "li") continue;
|
|
6178
6766
|
const end = closing(inner, "li", match.index + match[0].length);
|
|
6179
6767
|
if (end === null) {
|
|
@@ -6419,14 +7007,14 @@ function sayUnread(tool, values, defaults, say) {
|
|
|
6419
7007
|
const given = defaults ?? FRAMES[tool] ?? {};
|
|
6420
7008
|
const known = /* @__PURE__ */ new Set([...NOTHING, ...read2.values, ...hidden2]);
|
|
6421
7009
|
for (const [key, value] of Object.entries(values)) {
|
|
6422
|
-
if (known.has(key) || tool === "body" && key.startsWith("popup") || empty(value) || Object.hasOwn(given, key) &&
|
|
7010
|
+
if (known.has(key) || tool === "body" && key.startsWith("popup") || empty(value) || Object.hasOwn(given, key) && same5(value, given[key])) continue;
|
|
6423
7011
|
say(Object.hasOwn(WHAT, key) ? `its ${WHAT[key]}` : `its ${key} ${JSON.stringify(value) ?? ""} isn't read: left out`);
|
|
6424
7012
|
}
|
|
6425
7013
|
const override = values._override && typeof values._override === "object" ? values._override : {};
|
|
6426
7014
|
const phone = override.mobile && typeof override.mobile === "object" ? override.mobile : {};
|
|
6427
7015
|
const phoneKnown = /* @__PURE__ */ new Set([...read2.phone, ...hidden2]);
|
|
6428
7016
|
for (const [key, value] of Object.entries(phone)) {
|
|
6429
|
-
if (phoneKnown.has(key) || empty(value) ||
|
|
7017
|
+
if (phoneKnown.has(key) || empty(value) || same5(value, values[key])) continue;
|
|
6430
7018
|
say(`its ${key} on phones ${JSON.stringify(value) ?? ""} isn't read: left out`);
|
|
6431
7019
|
}
|
|
6432
7020
|
for (const device of Object.keys(override)) {
|
|
@@ -6674,8 +7262,8 @@ function fold(blocks, space) {
|
|
|
6674
7262
|
if (!some(s)) return block2;
|
|
6675
7263
|
const holds = ["Text", "Heading", "Divider", "Column"].includes(block2.type) && !BOX2.some((key) => block2.style?.[key] !== void 0);
|
|
6676
7264
|
if (holds) return { ...block2, style: { ...block2.style, padding: add2(block2.style?.padding ?? ZERO2, s) } };
|
|
6677
|
-
const { hidden: hidden2, ...
|
|
6678
|
-
return { type: "Column", style: { padding: s }, ...hidden2 ? { hidden: hidden2 } : {}, children: [
|
|
7265
|
+
const { hidden: hidden2, ...shown2 } = block2;
|
|
7266
|
+
return { type: "Column", style: { padding: s }, ...hidden2 ? { hidden: hidden2 } : {}, children: [shown2] };
|
|
6679
7267
|
});
|
|
6680
7268
|
}
|
|
6681
7269
|
function phoneOf(columnValues) {
|
|
@@ -6708,14 +7296,14 @@ function columnOf(columnValues, children, share3, say) {
|
|
|
6708
7296
|
function stretched(columns, stacks) {
|
|
6709
7297
|
if (columns.length < 2) return { style: {} };
|
|
6710
7298
|
const style = (column) => column.style ?? {};
|
|
6711
|
-
const
|
|
7299
|
+
const same6 = (values) => values[0] !== void 0 && values.every((value) => JSON.stringify(value) === JSON.stringify(values[0]));
|
|
6712
7300
|
const out = {};
|
|
6713
7301
|
const backgrounds = columns.map((column) => style(column).backgroundColor);
|
|
6714
|
-
if (
|
|
7302
|
+
if (same6(backgrounds) && columns.every((column) => style(column).borderRadius === void 0)) out.backgroundColor = backgrounds[0];
|
|
6715
7303
|
const lines = {};
|
|
6716
7304
|
for (const side2 of ["top", "bottom"]) {
|
|
6717
7305
|
const each = columns.map((column) => style(column).border?.[side2]);
|
|
6718
|
-
if (
|
|
7306
|
+
if (same6(each)) lines[side2] = each[0];
|
|
6719
7307
|
}
|
|
6720
7308
|
if (!out.backgroundColor && !Object.keys(lines).length) return { style: {} };
|
|
6721
7309
|
for (const column of columns) {
|
|
@@ -6812,13 +7400,13 @@ var defined3 = (record) => Object.fromEntries(Object.entries(record).filter(([,
|
|
|
6812
7400
|
var wordsIn = (value, depth) => typeof value === "string" ? [value] : depth > 0 && value && typeof value === "object" ? Object.values(value).flatMap((held) => wordsIn(held, depth - 1)) : [];
|
|
6813
7401
|
var ROOTS = { email: "Email", web: "Page", document: "Document" };
|
|
6814
7402
|
var SIZES2 = /* @__PURE__ */ new Set(["A3", "A4", "A5", "Letter", "Legal", "Tabloid"]);
|
|
6815
|
-
function readDesign(given, displayMode,
|
|
6816
|
-
const design = withoutMalformed(given,
|
|
7403
|
+
function readDesign(given, displayMode, report, toSafeHtml) {
|
|
7404
|
+
const design = withoutMalformed(given, report);
|
|
6817
7405
|
const body = obj3(obj3(design).body);
|
|
6818
|
-
if (!Object.keys(body).length)
|
|
7406
|
+
if (!Object.keys(body).length) report.push("the design has no body: an empty template");
|
|
6819
7407
|
const values = { ...BodyDefaults, ...obj3(body.values) };
|
|
6820
7408
|
const rowsOf = (key) => {
|
|
6821
|
-
if (body[key] !== void 0 && !Array.isArray(body[key]))
|
|
7409
|
+
if (body[key] !== void 0 && !Array.isArray(body[key])) report.push(`body.${key} isn't a list of rows: left out`);
|
|
6822
7410
|
return arr2(body[key]);
|
|
6823
7411
|
};
|
|
6824
7412
|
const rows = rowsOf("rows");
|
|
@@ -6826,15 +7414,15 @@ function readDesign(given, displayMode, report2, toSafeHtml) {
|
|
|
6826
7414
|
const footers = rowsOf("footers");
|
|
6827
7415
|
const breaks = rows.some((row) => arr2(obj3(row).columns).some((column) => arr2(obj3(column).contents).some((content2) => obj3(content2).type === "page_break")));
|
|
6828
7416
|
const mode = displayMode && Object.hasOwn(ROOTS, displayMode) ? displayMode : headers.length || footers.length || breaks ? "document" : "email";
|
|
6829
|
-
const root = rootOf(mode, values, (message) =>
|
|
6830
|
-
sayUnread("body", obj3(body.values), void 0, (message) =>
|
|
7417
|
+
const root = rootOf(mode, values, (message) => report.push(`body: ${message}`));
|
|
7418
|
+
sayUnread("body", obj3(body.values), void 0, (message) => report.push(`body: ${message}`));
|
|
6831
7419
|
const context = (where) => ({
|
|
6832
7420
|
inherited: { color: root.style?.color ?? "#000000", fontFamily: root.style?.fontFamily ?? "Arial, Helvetica, sans-serif", fontWeight: root.style?.fontWeight ?? 400 },
|
|
6833
7421
|
mode,
|
|
6834
7422
|
dir: root.props?.dir ?? "ltr",
|
|
6835
7423
|
safe: (html2) => toSafeHtml ? toSafeHtml(html2) : html2,
|
|
6836
7424
|
document: mode === "document",
|
|
6837
|
-
say: (message) =>
|
|
7425
|
+
say: (message) => report.push(`${where}: ${message}`)
|
|
6838
7426
|
});
|
|
6839
7427
|
const children = [];
|
|
6840
7428
|
const place = (list, part) => list.flatMap((row, r) => readRow(obj3(row), `${part}[${r}]`, { context, body: values, document: mode === "document", part: mode === "document" && part !== "rows" }));
|
|
@@ -6849,12 +7437,12 @@ function readDesign(given, displayMode, report2, toSafeHtml) {
|
|
|
6849
7437
|
}
|
|
6850
7438
|
const all = [...rows, ...headers, ...footers];
|
|
6851
7439
|
const items2 = all.flatMap((row) => [row, ...arr2(obj3(row).columns).flatMap((column) => [column, ...arr2(obj3(column).contents)])]).map((item) => obj3(obj3(item).values));
|
|
6852
|
-
if (items2.some((held) => held.hideMobile === true || held.hideDesktop === true))
|
|
7440
|
+
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");
|
|
6853
7441
|
const ownFonts = new Set(arr2(root.props?.fonts).map((face) => obj3(face).family));
|
|
6854
7442
|
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)))];
|
|
6855
|
-
if (webFonts.length)
|
|
7443
|
+
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`);
|
|
6856
7444
|
const tags = [...new Set(wordsIn(all, 12).flatMap((text4) => [...text4.matchAll(/\[\[\s*[\w.-]+\s*\]\]/g)].map((match) => match[0])))];
|
|
6857
|
-
if (tags.length)
|
|
7445
|
+
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`);
|
|
6858
7446
|
return { ...root, children };
|
|
6859
7447
|
}
|
|
6860
7448
|
function rootOf(mode, values, say) {
|
|
@@ -6896,23 +7484,23 @@ function rootOf(mode, values, say) {
|
|
|
6896
7484
|
// src/builder/check.ts
|
|
6897
7485
|
var LINK = definitionOf("Link").props.href.kind;
|
|
6898
7486
|
var holdsText = (type) => !!definitionOf(type)?.children?.text;
|
|
6899
|
-
function itemLinks(value, key, path,
|
|
7487
|
+
function itemLinks(value, key, path, report) {
|
|
6900
7488
|
if (!Array.isArray(value)) return value;
|
|
6901
7489
|
return value.map((item, i) => {
|
|
6902
7490
|
if (!item || typeof item !== "object" || Array.isArray(item) || !("href" in item) || LINK.parse(item.href).ok) return item;
|
|
6903
|
-
|
|
7491
|
+
report.push(`${path}: its ${key}[${i}]'s link ${JSON.stringify(item.href)} isn't one Elements writes: it's "#"`);
|
|
6904
7492
|
return { ...item, href: "#" };
|
|
6905
7493
|
});
|
|
6906
7494
|
}
|
|
6907
|
-
function checkNode(node, path,
|
|
7495
|
+
function checkNode(node, path, report, inText2 = false) {
|
|
6908
7496
|
const definition = definitionOf(node.type);
|
|
6909
7497
|
if (!definition) {
|
|
6910
|
-
|
|
7498
|
+
report.push(`${path}: ${JSON.stringify(node.type)} isn't a component: left out`);
|
|
6911
7499
|
return null;
|
|
6912
7500
|
}
|
|
6913
7501
|
const out = { ...node };
|
|
6914
7502
|
if (out.id !== void 0 && !/^[A-Za-z][\w-]*$/.test(out.id)) {
|
|
6915
|
-
|
|
7503
|
+
report.push(`${path}: its id ${JSON.stringify(out.id)} isn't one a template takes: left out`);
|
|
6916
7504
|
delete out.id;
|
|
6917
7505
|
}
|
|
6918
7506
|
for (const part of ["props", "style", "mobile", "hover"]) {
|
|
@@ -6921,7 +7509,7 @@ function checkNode(node, path, report2, inText2 = false) {
|
|
|
6921
7509
|
const specs = part === "props" ? definition.props : definition.style;
|
|
6922
7510
|
const kept = {};
|
|
6923
7511
|
for (const [key, given] of Object.entries(values)) {
|
|
6924
|
-
const value = part === "props" ? itemLinks(given, key, path,
|
|
7512
|
+
const value = part === "props" ? itemLinks(given, key, path, report) : given;
|
|
6925
7513
|
const spec = Object.hasOwn(specs, key) ? specs[key] : void 0;
|
|
6926
7514
|
const parsed = spec ? spec.kind.parse(value) : void 0;
|
|
6927
7515
|
if (parsed?.ok) {
|
|
@@ -6930,35 +7518,35 @@ function checkNode(node, path, report2, inText2 = false) {
|
|
|
6930
7518
|
}
|
|
6931
7519
|
if (part === "props" && (key === "href" || key === "src")) {
|
|
6932
7520
|
if (node.type === "Button") {
|
|
6933
|
-
|
|
7521
|
+
report.push(`${path}: its link ${JSON.stringify(value)} isn't one Elements writes: it's "#"`);
|
|
6934
7522
|
kept.href = "#";
|
|
6935
7523
|
continue;
|
|
6936
7524
|
}
|
|
6937
7525
|
if (node.type !== "Link") {
|
|
6938
|
-
|
|
7526
|
+
report.push(`${path}: its ${key} ${JSON.stringify(value)} isn't one Elements writes: left out`);
|
|
6939
7527
|
return null;
|
|
6940
7528
|
}
|
|
6941
|
-
|
|
6942
|
-
const children = checkChildren(node.children ?? [], path,
|
|
7529
|
+
report.push(`${path}: its ${key} ${JSON.stringify(value)} isn't one Elements writes: ${inText2 ? "its words" : "what it holds"} without it`);
|
|
7530
|
+
const children = checkChildren(node.children ?? [], path, report, true);
|
|
6943
7531
|
return inText2 ? { type: "Span", children } : children;
|
|
6944
7532
|
}
|
|
6945
|
-
|
|
7533
|
+
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`);
|
|
6946
7534
|
}
|
|
6947
7535
|
if (Object.keys(kept).length) out[part] = kept;
|
|
6948
7536
|
else delete out[part];
|
|
6949
7537
|
}
|
|
6950
|
-
if (node.children) out.children = checkChildren(node.children, path,
|
|
7538
|
+
if (node.children) out.children = checkChildren(node.children, path, report, holdsText(node.type));
|
|
6951
7539
|
return out;
|
|
6952
7540
|
}
|
|
6953
|
-
function checkChildren(children, path,
|
|
7541
|
+
function checkChildren(children, path, report, inText2) {
|
|
6954
7542
|
return children.flatMap((child, i) => {
|
|
6955
7543
|
if (typeof child === "string") return [child];
|
|
6956
|
-
const checked = checkNode(child, `${path}.children[${i}]`,
|
|
7544
|
+
const checked = checkNode(child, `${path}.children[${i}]`, report, inText2);
|
|
6957
7545
|
return Array.isArray(checked) ? checked : checked ? [checked] : [];
|
|
6958
7546
|
});
|
|
6959
7547
|
}
|
|
6960
|
-
function checkTemplate(root,
|
|
6961
|
-
const checked = checkNode(root.type === "Document" ? withoutPhones(root) : root, "root",
|
|
7548
|
+
function checkTemplate(root, report) {
|
|
7549
|
+
const checked = checkNode(root.type === "Document" ? withoutPhones(root) : root, "root", report);
|
|
6962
7550
|
return checked && !Array.isArray(checked) ? checked : { type: root.type };
|
|
6963
7551
|
}
|
|
6964
7552
|
function withoutPhones(node) {
|
|
@@ -7634,7 +8222,8 @@ function renderSubtree(node, width, root, components, inherited) {
|
|
|
7634
8222
|
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;
|
|
7635
8223
|
const own3 = tokens2(node);
|
|
7636
8224
|
const around = { ...inherited, ...drawn < 200 ? { width: drawn } : {} };
|
|
7637
|
-
const
|
|
8225
|
+
const breakpoints2 = root.type === "Page" && root.props?.breakpoints ? { breakpoints: root.props.breakpoints } : {};
|
|
8226
|
+
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] };
|
|
7638
8227
|
const { head, body } = renderTemplate({ root: page }, { components });
|
|
7639
8228
|
const style = /<style>[\s\S]*?<\/style>/.exec(head)?.[0] ?? "";
|
|
7640
8229
|
const filled = body.replace(/^<main style="/, '<main style="width:100%;');
|
|
@@ -7646,14 +8235,14 @@ var holdsPageNumber = (node) => node.type === "PageNumber" || (node.children ??
|
|
|
7646
8235
|
var PAINT2 = ["backgroundColor", "border", "borderRadius"];
|
|
7647
8236
|
var BROWSER_TEXT = { fontSize: 16, fontStyle: "normal", letterSpacing: 0, textAlign: "left", textTransform: "none" };
|
|
7648
8237
|
function raise(element2) {
|
|
7649
|
-
const
|
|
8238
|
+
const report = [];
|
|
7650
8239
|
const { document, diagnostics, components } = collectTemplate(element2);
|
|
7651
8240
|
if (!document || diagnostics.some((d) => d.severity === "error")) return { body: null, report: diagnostics.filter((d) => d.severity === "error").map((d) => `${d.path}: ${d.message}`) };
|
|
7652
8241
|
const root = document.root;
|
|
7653
8242
|
if (root.type !== "Email" && root.type !== "Page" && root.type !== "Document") {
|
|
7654
8243
|
return { body: null, report: [`root: the builder edits an Email, a Page or a Document, not a ${root.type}`] };
|
|
7655
8244
|
}
|
|
7656
|
-
if (holdsPageNumber(root))
|
|
8245
|
+
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");
|
|
7657
8246
|
const prepared = prepareTemplate(document, { components });
|
|
7658
8247
|
if (!prepared.resolved) return { body: null, report: prepared.diagnostics.filter((d) => d.severity === "error").map((d) => `${d.path}: ${d.message}`) };
|
|
7659
8248
|
const resolvedRoot = prepared.resolved.root;
|
|
@@ -7670,7 +8259,7 @@ function raise(element2) {
|
|
|
7670
8259
|
};
|
|
7671
8260
|
const sayLeftOut = (tool, node, path, fields) => {
|
|
7672
8261
|
const left = leftOut(tool, node, fields);
|
|
7673
|
-
if (left.length)
|
|
8262
|
+
if (left.length) report.push(`${path}: the builder's ${tool} has no place for ${left.join(", ")}: left out`);
|
|
7674
8263
|
};
|
|
7675
8264
|
const unsized = (node, path) => childrenOf2(node, path).some(({ child, path: childPath }) => !child.hidden?.desktop && resolved.get(childPath)?.width === null);
|
|
7676
8265
|
const childrenOf2 = (node, path) => (node.children ?? []).map((child, i) => ({ child, path: `${path}.children[${i}]` })).filter((c) => typeof c.child !== "string");
|
|
@@ -7689,11 +8278,11 @@ function raise(element2) {
|
|
|
7689
8278
|
};
|
|
7690
8279
|
const place = {
|
|
7691
8280
|
at,
|
|
7692
|
-
report
|
|
8281
|
+
report,
|
|
7693
8282
|
put,
|
|
7694
8283
|
sayLeftOut,
|
|
7695
8284
|
locked: (node, path, why, around) => {
|
|
7696
|
-
|
|
8285
|
+
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)"}`);
|
|
7697
8286
|
const own3 = at(path);
|
|
7698
8287
|
return put(lockedHtml(node, own3.width ?? own3.available ?? Number(resolvedRoot.width), root, components, around, inheritedText(path)));
|
|
7699
8288
|
},
|
|
@@ -7827,7 +8416,7 @@ function raise(element2) {
|
|
|
7827
8416
|
const own3 = at(path);
|
|
7828
8417
|
sayLeftOut("row", child, path, ["style.backgroundColor", "style.padding", "style.border", "style.gap", "style.alignItems", ...TEXT_FOLDED]);
|
|
7829
8418
|
const band = own3.style.backgroundColor !== void 0 ? { backgroundColor: own3.style.backgroundColor } : {};
|
|
7830
|
-
if (child.mobile?.border !== void 0)
|
|
8419
|
+
if (child.mobile?.border !== void 0) report.push(`${path}: the builder's row has no border on phones: left out`);
|
|
7831
8420
|
const sectionRows = rows(childrenOf2(child, path), own3, band, padding2(child), child);
|
|
7832
8421
|
if (child.id && sectionRows[0]) put(sectionRows[0], builderId(child.id, "u_row"));
|
|
7833
8422
|
body.push(...sectionRows.length ? sectionRows : [oneColumn([], own3, band, padding2(child), {}, hiding2(child))]);
|
|
@@ -7841,7 +8430,7 @@ function raise(element2) {
|
|
|
7841
8430
|
} else if (child.type === "PageBreak") {
|
|
7842
8431
|
flushLoose();
|
|
7843
8432
|
const content2 = top.slice(i + 1).some((c) => !["PageHeader", "PageFooter", "PageBreak"].includes(c.child.type));
|
|
7844
|
-
if (last === null || !content2)
|
|
8433
|
+
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`);
|
|
7845
8434
|
body.push(pageBreak());
|
|
7846
8435
|
last = "break";
|
|
7847
8436
|
} else if (child.type === "PageHeader" || child.type === "PageFooter") {
|
|
@@ -7884,10 +8473,10 @@ function raise(element2) {
|
|
|
7884
8473
|
const bodyHidden = { ...hiddenOf("body", root, ["props.margin"]), ...margin, ...title, backgroundColor: style.backgroundColor ?? "" };
|
|
7885
8474
|
if (root.type === "Document") {
|
|
7886
8475
|
const paper = { documentSize: root.props?.size ?? "A4", documentOrientation: root.props?.orientation ?? "portrait" };
|
|
7887
|
-
return { body: { root: root.type, fields: { ...common, ...paper }, values: bodyHidden, rows: body, headers, footers }, report
|
|
8476
|
+
return { body: { root: root.type, fields: { ...common, ...paper }, values: bodyHidden, rows: body, headers, footers }, report };
|
|
7888
8477
|
}
|
|
7889
8478
|
const previewText = root.type === "Email" && root.props?.previewText ? { previewText: String(root.props.previewText) } : {};
|
|
7890
|
-
return { body: { root: root.type, fields: common, ...previewText, values: bodyHidden, rows: body }, report
|
|
8479
|
+
return { body: { root: root.type, fields: common, ...previewText, values: bodyHidden, rows: body }, report };
|
|
7891
8480
|
}
|
|
7892
8481
|
function countRows(children) {
|
|
7893
8482
|
let count = 0;
|
|
@@ -7933,10 +8522,10 @@ function nodeOf(node) {
|
|
|
7933
8522
|
|
|
7934
8523
|
// src/builder/write.ts
|
|
7935
8524
|
function writeDesign(element2) {
|
|
7936
|
-
const { body, report
|
|
8525
|
+
const { body, report } = raise(element2);
|
|
7937
8526
|
if (!body) throw new Error(`[Elements] This template can't go to the builder:
|
|
7938
|
-
${
|
|
7939
|
-
return { design: withFontStylesheets(writeParts(body)), report
|
|
8527
|
+
${report.join("\n")}`);
|
|
8528
|
+
return { design: withFontStylesheets(writeParts(body)), report };
|
|
7940
8529
|
}
|
|
7941
8530
|
|
|
7942
8531
|
// src/builder/index.ts
|
|
@@ -7944,10 +8533,10 @@ function toDesign(element2) {
|
|
|
7944
8533
|
return writeDesign(element2);
|
|
7945
8534
|
}
|
|
7946
8535
|
function fromDesign(design, options = {}) {
|
|
7947
|
-
const
|
|
7948
|
-
const root = checkTemplate(uniqueIds(readDesign(design, options.displayMode,
|
|
7949
|
-
const code = printTemplate(root, options.name,
|
|
7950
|
-
return { element: toElement(root), code, report
|
|
8536
|
+
const report = [];
|
|
8537
|
+
const root = checkTemplate(uniqueIds(readDesign(design, options.displayMode, report, options.toSafeHtml)), report);
|
|
8538
|
+
const code = printTemplate(root, options.name, report);
|
|
8539
|
+
return { element: toElement(root), code, report };
|
|
7951
8540
|
}
|
|
7952
8541
|
|
|
7953
8542
|
exports.fromDesign = fromDesign;
|