@samva/markup 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +107 -46
- package/CHANGELOG.md +345 -0
- package/README.md +188 -52
- package/dist/{brand-DcoVkh_S.d.ts → assets-BIsyMQ92.d.ts} +41 -11
- package/dist/{email/brand.js → brand-C0MKTwmX.js} +64 -4
- package/dist/{theme-css.d.ts → brand.d.ts} +12 -3
- package/dist/brand.js +4 -0
- package/dist/channel-specs-C2feJwuP.d.ts +13 -0
- package/dist/compiler.d.ts +88 -115
- package/dist/compiler.js +750 -171
- package/dist/components-BZCqegS8.js +1864 -0
- package/dist/diagnostic-model-DJtqbETd.js +38 -0
- package/dist/{diagnostics-OcQKQYSY.d.ts → diagnostic-model-DxthUZBT.d.ts} +3 -21
- package/dist/{diagnostics-BMYtBCQk.d.ts → diagnostics-B-Gym34R.d.ts} +8 -4
- package/dist/{email/diagnostics.js → diagnostics-CnKFefKz.js} +4 -3
- package/dist/diagnostics.d.ts +410 -2
- package/dist/diagnostics.js +402 -52
- package/dist/edit.d.ts +234 -0
- package/dist/edit.js +947 -0
- package/dist/email/jsx-dev-runtime.d.ts +2 -2
- package/dist/email/jsx-dev-runtime.js +1 -2
- package/dist/email/jsx-runtime.d.ts +1 -1
- package/dist/email/jsx-runtime.js +1 -85
- package/dist/{email/components.d.ts → email.d.ts} +22 -11
- package/dist/email.js +2 -0
- package/dist/fmt.d.ts +32 -0
- package/dist/fmt.js +174 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/{render-vAv_4r9v.js → ir-CLhlklKV.js} +96 -279
- package/dist/jsx-runtime-CBlexoeR.js +98 -0
- package/dist/{elements-BzjjnoCS.d.ts → jsx-runtime-ylIOnaSf.d.ts} +76 -76
- package/dist/profile-pWGtWZXF.js +3803 -0
- package/dist/render-BPonDpCl.d.ts +539 -0
- package/dist/render-BQmfh94k.js +257 -0
- package/dist/render.d.ts +35 -0
- package/dist/render.js +49 -0
- package/dist/sms.d.ts +16 -0
- package/dist/sms.js +12 -0
- package/dist/source-locations-C8wCsiAy.d.ts +23 -0
- package/dist/template-DMPwxMSq.d.ts +65 -0
- package/dist/template-zLUjcwfF.js +42 -0
- package/dist/{theme-css-hFqcPSar.js → theme-css-JtRCm1fb.js} +2 -2
- package/dist/whatsapp.d.ts +57 -0
- package/dist/whatsapp.js +22 -0
- package/generated/contract.json +1788 -0
- package/generated/reference.md +360 -0
- package/package.json +46 -239
- package/samva-brand.d.ts +4 -4
- package/skills/sml/SKILL.md +123 -0
- package/dist/ast-BrDtApwS.d.ts +0 -107
- package/dist/ast.d.ts +0 -2
- package/dist/ast.js +0 -124
- package/dist/candidate.d.ts +0 -29
- package/dist/candidate.js +0 -88
- package/dist/catalog-CKuOooB8.d.ts +0 -354
- package/dist/checks-D73Xizbw.js +0 -59366
- package/dist/checks.d.ts +0 -33
- package/dist/checks.js +0 -2
- package/dist/classes.d.ts +0 -26
- package/dist/classes.js +0 -184
- package/dist/compile-sms.d.ts +0 -28
- package/dist/compile-sms.js +0 -44
- package/dist/compile-whatsapp.d.ts +0 -56
- package/dist/compile-whatsapp.js +0 -186
- package/dist/compile.d.ts +0 -18
- package/dist/compile.js +0 -29
- package/dist/components.d.ts +0 -289
- package/dist/components.js +0 -106
- package/dist/conditionals.d.ts +0 -10
- package/dist/conditionals.js +0 -74
- package/dist/corpus-CPPfgAHN.d.ts +0 -106
- package/dist/css-CS2jLID6.d.ts +0 -109
- package/dist/email/assets.d.ts +0 -40
- package/dist/email/assets.js +0 -63
- package/dist/email/brand-footer.d.ts +0 -34
- package/dist/email/brand-footer.js +0 -62
- package/dist/email/brand.d.ts +0 -2
- package/dist/email/components.js +0 -240
- package/dist/email/diagnostics.d.ts +0 -2
- package/dist/email/elements.d.ts +0 -2
- package/dist/email/elements.js +0 -299
- package/dist/email/html.d.ts +0 -2
- package/dist/email/html.js +0 -278
- package/dist/email/render.d.ts +0 -50
- package/dist/email/render.js +0 -3
- package/dist/email/selection.d.ts +0 -2
- package/dist/email/selection.js +0 -91
- package/dist/email/source-edit.d.ts +0 -67
- package/dist/email/source-edit.js +0 -185
- package/dist/email/stylesheets.d.ts +0 -15
- package/dist/email/stylesheets.js +0 -48
- package/dist/email/text.d.ts +0 -7
- package/dist/email/text.js +0 -100
- package/dist/emit.d.ts +0 -51
- package/dist/emit.js +0 -151
- package/dist/html-Bl9BAdqc.d.ts +0 -42
- package/dist/interpolate.d.ts +0 -24
- package/dist/interpolate.js +0 -43
- package/dist/jsx-dev-runtime.d.ts +0 -11
- package/dist/jsx-dev-runtime.js +0 -15
- package/dist/jsx-runtime-I-1nCtl2.d.ts +0 -57
- package/dist/jsx-runtime.d.ts +0 -2
- package/dist/jsx-runtime.js +0 -121
- package/dist/lexer.d.ts +0 -60
- package/dist/lexer.js +0 -281
- package/dist/model.d.ts +0 -24
- package/dist/model.js +0 -77
- package/dist/ops-BvwqHl7V.js +0 -630
- package/dist/ops.d.ts +0 -114
- package/dist/ops.js +0 -2
- package/dist/parser-t29cHLVL.d.ts +0 -396
- package/dist/parser.d.ts +0 -2
- package/dist/parser.js +0 -859
- package/dist/predicate-BI6uRRM-.js +0 -549
- package/dist/predicate-M-05Nd7R.d.ts +0 -56
- package/dist/predicate.d.ts +0 -2
- package/dist/predicate.js +0 -2
- package/dist/printer.d.ts +0 -6
- package/dist/printer.js +0 -122
- package/dist/registry-Dcp6QEK_.d.ts +0 -73
- package/dist/registry.d.ts +0 -2
- package/dist/registry.js +0 -646
- package/dist/render-template.d.ts +0 -45
- package/dist/render-template.js +0 -76
- package/dist/selection-CCArx9lU.d.ts +0 -91
- package/dist/showcase-egkusQBf.d.ts +0 -415
- package/dist/sms-segments-y4FpPilJ.d.ts +0 -13
- package/dist/sms-segments.d.ts +0 -2
- package/dist/sms-segments.js +0 -46
- package/dist/source-locations-DrCM82Ym.d.ts +0 -67
- package/dist/source-locations.d.ts +0 -2
- package/dist/source-locations.js +0 -102
- package/dist/starters/category-mapping.d.ts +0 -13
- package/dist/starters/category-mapping.js +0 -49
- package/dist/starters/corpus.d.ts +0 -2
- package/dist/starters/corpus.js +0 -194
- package/dist/starters/library.d.ts +0 -18
- package/dist/starters/library.js +0 -692
- package/dist/starters/public-gallery.d.ts +0 -266
- package/dist/starters/public-gallery.js +0 -916
- package/dist/starters/public-handoff.d.ts +0 -164
- package/dist/starters/public-handoff.js +0 -361
- package/dist/starters/public-preview-artifacts.d.ts +0 -43
- package/dist/starters/public-preview-artifacts.js +0 -104
- package/dist/starters/public-preview.d.ts +0 -61
- package/dist/starters/public-preview.js +0 -83
- package/dist/starters/showcase.d.ts +0 -2
- package/dist/starters/showcase.js +0 -335
- package/dist/starters/sources.d.ts +0 -45
- package/dist/starters/sources.js +0 -7762
- package/dist/starters/theme.d.ts +0 -45
- package/dist/starters/theme.js +0 -77
- package/dist/starters.d.ts +0 -2
- package/dist/starters.js +0 -874
- package/dist/tailwind-BYq7Lo6u.js +0 -727
- package/dist/template-DWftqh4x.d.ts +0 -60
- package/dist/template.d.ts +0 -2
- package/dist/template.js +0 -37
- package/dist/theme-css.js +0 -2
- package/dist/tsx-source-instrumentation.d.ts +0 -30
- package/dist/tsx-source-instrumentation.js +0 -115
- package/dist/tsx-tracer.d.ts +0 -112
- package/dist/tsx-tracer.js +0 -985
- package/dist/variables-J_L3qxRa.d.ts +0 -77
- package/dist/variables.d.ts +0 -2
- package/dist/variables.js +0 -141
- package/dist/version-BITr58kk.js +0 -96
- package/generated/sml-agent-contract.json +0 -709
- package/generated/sml-email-agent-contract.json +0 -615
- package/generated/sml-email-language-reference.md +0 -94
- package/generated/sml-language-reference.md +0 -195
package/dist/email/elements.js
DELETED
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
//#region src/email/elements.ts
|
|
2
|
-
/** Attributes every supported element accepts. `class`/`className`/`style` are handled separately. */
|
|
3
|
-
const EMAIL_GLOBAL_ATTRIBUTES = {
|
|
4
|
-
id: { type: "string" },
|
|
5
|
-
title: { type: "string" },
|
|
6
|
-
lang: { type: "string" },
|
|
7
|
-
dir: {
|
|
8
|
-
type: "string",
|
|
9
|
-
values: [
|
|
10
|
-
"ltr",
|
|
11
|
-
"rtl",
|
|
12
|
-
"auto"
|
|
13
|
-
]
|
|
14
|
-
},
|
|
15
|
-
role: { type: "string" }
|
|
16
|
-
};
|
|
17
|
-
const alignment = {
|
|
18
|
-
type: "string",
|
|
19
|
-
values: [
|
|
20
|
-
"left",
|
|
21
|
-
"center",
|
|
22
|
-
"right"
|
|
23
|
-
]
|
|
24
|
-
};
|
|
25
|
-
const verticalAlignment = {
|
|
26
|
-
type: "string",
|
|
27
|
-
values: [
|
|
28
|
-
"top",
|
|
29
|
-
"middle",
|
|
30
|
-
"bottom"
|
|
31
|
-
]
|
|
32
|
-
};
|
|
33
|
-
const flowAttributes = { align: alignment };
|
|
34
|
-
const cellAttributes = {
|
|
35
|
-
align: alignment,
|
|
36
|
-
valign: verticalAlignment,
|
|
37
|
-
width: { type: "measure" },
|
|
38
|
-
height: { type: "measure" },
|
|
39
|
-
bgcolor: { type: "string" },
|
|
40
|
-
colspan: { type: "number" },
|
|
41
|
-
rowspan: { type: "number" }
|
|
42
|
-
};
|
|
43
|
-
const block = (attributes) => ({
|
|
44
|
-
content: "flow",
|
|
45
|
-
layout: "block",
|
|
46
|
-
attributes
|
|
47
|
-
});
|
|
48
|
-
const inline = () => ({
|
|
49
|
-
content: "flow",
|
|
50
|
-
layout: "inline",
|
|
51
|
-
attributes: {}
|
|
52
|
-
});
|
|
53
|
-
/** The complete supported vocabulary. Anything outside it is a compile error with a source location. */
|
|
54
|
-
const EMAIL_ELEMENTS = {
|
|
55
|
-
html: {
|
|
56
|
-
content: "flow",
|
|
57
|
-
layout: "document",
|
|
58
|
-
attributes: {
|
|
59
|
-
xmlns: { type: "string" },
|
|
60
|
-
"xmlns:o": { type: "string" },
|
|
61
|
-
"xmlns:v": { type: "string" },
|
|
62
|
-
"xmlns:w": { type: "string" }
|
|
63
|
-
},
|
|
64
|
-
childElements: ["head", "body"]
|
|
65
|
-
},
|
|
66
|
-
head: {
|
|
67
|
-
content: "flow",
|
|
68
|
-
layout: "metadata",
|
|
69
|
-
attributes: {},
|
|
70
|
-
childElements: [
|
|
71
|
-
"title",
|
|
72
|
-
"meta",
|
|
73
|
-
"style"
|
|
74
|
-
]
|
|
75
|
-
},
|
|
76
|
-
title: {
|
|
77
|
-
content: "text",
|
|
78
|
-
layout: "metadata",
|
|
79
|
-
attributes: {}
|
|
80
|
-
},
|
|
81
|
-
style: {
|
|
82
|
-
content: "raw",
|
|
83
|
-
layout: "metadata",
|
|
84
|
-
attributes: { type: { type: "string" } }
|
|
85
|
-
},
|
|
86
|
-
meta: {
|
|
87
|
-
content: "void",
|
|
88
|
-
layout: "metadata",
|
|
89
|
-
attributes: {
|
|
90
|
-
name: { type: "string" },
|
|
91
|
-
content: { type: "string" },
|
|
92
|
-
charset: { type: "string" },
|
|
93
|
-
"http-equiv": { type: "string" }
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
body: {
|
|
97
|
-
content: "flow",
|
|
98
|
-
layout: "block",
|
|
99
|
-
attributes: { bgcolor: { type: "string" } }
|
|
100
|
-
},
|
|
101
|
-
div: block(flowAttributes),
|
|
102
|
-
section: block(flowAttributes),
|
|
103
|
-
article: block(flowAttributes),
|
|
104
|
-
header: block(flowAttributes),
|
|
105
|
-
footer: block(flowAttributes),
|
|
106
|
-
main: block(flowAttributes),
|
|
107
|
-
center: block({}),
|
|
108
|
-
p: block(flowAttributes),
|
|
109
|
-
blockquote: block(flowAttributes),
|
|
110
|
-
pre: {
|
|
111
|
-
content: "flow",
|
|
112
|
-
layout: "preformatted",
|
|
113
|
-
attributes: {}
|
|
114
|
-
},
|
|
115
|
-
h1: block(flowAttributes),
|
|
116
|
-
h2: block(flowAttributes),
|
|
117
|
-
h3: block(flowAttributes),
|
|
118
|
-
h4: block(flowAttributes),
|
|
119
|
-
h5: block(flowAttributes),
|
|
120
|
-
h6: block(flowAttributes),
|
|
121
|
-
span: inline(),
|
|
122
|
-
strong: inline(),
|
|
123
|
-
b: inline(),
|
|
124
|
-
em: inline(),
|
|
125
|
-
i: inline(),
|
|
126
|
-
u: inline(),
|
|
127
|
-
s: inline(),
|
|
128
|
-
small: inline(),
|
|
129
|
-
code: inline(),
|
|
130
|
-
a: {
|
|
131
|
-
content: "flow",
|
|
132
|
-
layout: "link",
|
|
133
|
-
attributes: {
|
|
134
|
-
href: {
|
|
135
|
-
type: "url",
|
|
136
|
-
required: true
|
|
137
|
-
},
|
|
138
|
-
target: { type: "string" },
|
|
139
|
-
rel: { type: "string" },
|
|
140
|
-
name: { type: "string" }
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
img: {
|
|
144
|
-
content: "void",
|
|
145
|
-
layout: "image",
|
|
146
|
-
attributes: {
|
|
147
|
-
src: {
|
|
148
|
-
type: "url",
|
|
149
|
-
required: true
|
|
150
|
-
},
|
|
151
|
-
alt: {
|
|
152
|
-
type: "string",
|
|
153
|
-
required: true
|
|
154
|
-
},
|
|
155
|
-
width: { type: "measure" },
|
|
156
|
-
height: { type: "measure" },
|
|
157
|
-
border: { type: "measure" },
|
|
158
|
-
align: alignment
|
|
159
|
-
}
|
|
160
|
-
},
|
|
161
|
-
table: {
|
|
162
|
-
content: "flow",
|
|
163
|
-
layout: "table",
|
|
164
|
-
attributes: {
|
|
165
|
-
width: { type: "measure" },
|
|
166
|
-
height: { type: "measure" },
|
|
167
|
-
border: { type: "measure" },
|
|
168
|
-
cellpadding: { type: "measure" },
|
|
169
|
-
cellspacing: { type: "measure" },
|
|
170
|
-
align: alignment,
|
|
171
|
-
bgcolor: { type: "string" }
|
|
172
|
-
},
|
|
173
|
-
childElements: [
|
|
174
|
-
"caption",
|
|
175
|
-
"colgroup",
|
|
176
|
-
"thead",
|
|
177
|
-
"tbody",
|
|
178
|
-
"tfoot",
|
|
179
|
-
"tr"
|
|
180
|
-
]
|
|
181
|
-
},
|
|
182
|
-
caption: {
|
|
183
|
-
content: "flow",
|
|
184
|
-
layout: "block",
|
|
185
|
-
attributes: flowAttributes
|
|
186
|
-
},
|
|
187
|
-
colgroup: {
|
|
188
|
-
content: "flow",
|
|
189
|
-
layout: "table-section",
|
|
190
|
-
attributes: {
|
|
191
|
-
span: { type: "number" },
|
|
192
|
-
width: { type: "measure" }
|
|
193
|
-
},
|
|
194
|
-
childElements: ["col"]
|
|
195
|
-
},
|
|
196
|
-
col: {
|
|
197
|
-
content: "void",
|
|
198
|
-
layout: "table-section",
|
|
199
|
-
attributes: {
|
|
200
|
-
span: { type: "number" },
|
|
201
|
-
width: { type: "measure" }
|
|
202
|
-
}
|
|
203
|
-
},
|
|
204
|
-
thead: {
|
|
205
|
-
content: "flow",
|
|
206
|
-
layout: "table-section",
|
|
207
|
-
attributes: {
|
|
208
|
-
align: alignment,
|
|
209
|
-
valign: verticalAlignment,
|
|
210
|
-
bgcolor: { type: "string" }
|
|
211
|
-
},
|
|
212
|
-
childElements: ["tr"]
|
|
213
|
-
},
|
|
214
|
-
tbody: {
|
|
215
|
-
content: "flow",
|
|
216
|
-
layout: "table-section",
|
|
217
|
-
attributes: {
|
|
218
|
-
align: alignment,
|
|
219
|
-
valign: verticalAlignment,
|
|
220
|
-
bgcolor: { type: "string" }
|
|
221
|
-
},
|
|
222
|
-
childElements: ["tr"]
|
|
223
|
-
},
|
|
224
|
-
tfoot: {
|
|
225
|
-
content: "flow",
|
|
226
|
-
layout: "table-section",
|
|
227
|
-
attributes: {
|
|
228
|
-
align: alignment,
|
|
229
|
-
valign: verticalAlignment,
|
|
230
|
-
bgcolor: { type: "string" }
|
|
231
|
-
},
|
|
232
|
-
childElements: ["tr"]
|
|
233
|
-
},
|
|
234
|
-
tr: {
|
|
235
|
-
content: "flow",
|
|
236
|
-
layout: "row",
|
|
237
|
-
attributes: {
|
|
238
|
-
align: alignment,
|
|
239
|
-
valign: verticalAlignment,
|
|
240
|
-
bgcolor: { type: "string" },
|
|
241
|
-
height: { type: "measure" }
|
|
242
|
-
},
|
|
243
|
-
childElements: ["td", "th"]
|
|
244
|
-
},
|
|
245
|
-
td: {
|
|
246
|
-
content: "flow",
|
|
247
|
-
layout: "cell",
|
|
248
|
-
attributes: cellAttributes
|
|
249
|
-
},
|
|
250
|
-
th: {
|
|
251
|
-
content: "flow",
|
|
252
|
-
layout: "cell",
|
|
253
|
-
attributes: cellAttributes
|
|
254
|
-
},
|
|
255
|
-
ul: {
|
|
256
|
-
content: "flow",
|
|
257
|
-
layout: "list",
|
|
258
|
-
attributes: {},
|
|
259
|
-
childElements: ["li"]
|
|
260
|
-
},
|
|
261
|
-
ol: {
|
|
262
|
-
content: "flow",
|
|
263
|
-
layout: "list",
|
|
264
|
-
attributes: {
|
|
265
|
-
start: { type: "number" },
|
|
266
|
-
type: { type: "string" }
|
|
267
|
-
},
|
|
268
|
-
childElements: ["li"]
|
|
269
|
-
},
|
|
270
|
-
li: {
|
|
271
|
-
content: "flow",
|
|
272
|
-
layout: "list-item",
|
|
273
|
-
attributes: {}
|
|
274
|
-
},
|
|
275
|
-
br: {
|
|
276
|
-
content: "void",
|
|
277
|
-
layout: "break",
|
|
278
|
-
attributes: {}
|
|
279
|
-
},
|
|
280
|
-
hr: {
|
|
281
|
-
content: "void",
|
|
282
|
-
layout: "rule",
|
|
283
|
-
attributes: {
|
|
284
|
-
width: { type: "measure" },
|
|
285
|
-
size: { type: "number" },
|
|
286
|
-
align: alignment
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
/** Attributes that must carry a URL; these are checked against the safe-scheme rule. */
|
|
291
|
-
const isUrlAttribute = (tag, attribute) => {
|
|
292
|
-
return EMAIL_ELEMENTS[tag]?.attributes[attribute]?.type === "url";
|
|
293
|
-
};
|
|
294
|
-
/** Look up one supported element, or `undefined` when the tag is outside the vocabulary. */
|
|
295
|
-
const emailElement = (tag) => Object.hasOwn(EMAIL_ELEMENTS, tag) ? EMAIL_ELEMENTS[tag] : void 0;
|
|
296
|
-
//#endregion
|
|
297
|
-
export { EMAIL_ELEMENTS, EMAIL_GLOBAL_ATTRIBUTES, emailElement, isUrlAttribute };
|
|
298
|
-
|
|
299
|
-
//# sourceMappingURL=elements.js.map
|
package/dist/email/html.d.ts
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { a as escapeText, i as escapeAttribute, n as SerializeEmailOptions, o as kebab, r as declarationValue, s as serializeEmailHtml, t as EmailHtmlOutput } from "../html-Bl9BAdqc.js";
|
|
2
|
-
export { EmailHtmlOutput, SerializeEmailOptions, declarationValue, escapeAttribute, escapeText, kebab, serializeEmailHtml };
|
package/dist/email/html.js
DELETED
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
import { n as isObject } from "../guards-eV1qLbf2.js";
|
|
2
|
-
import { i as UNSUBSCRIBE_URL_PLACEHOLDER, n as RESERVED_FOOTER_ATTRIBUTES, r as UNSUBSCRIBE_LINK, t as BRAND_FOOTER_MARKER } from "../unsubscribe-link-CoesnQDU.js";
|
|
3
|
-
import { EMAIL_ELEMENTS, EMAIL_GLOBAL_ATTRIBUTES, emailElement } from "./elements.js";
|
|
4
|
-
import { INSTANCE_PATH_ATTRIBUTE } from "./selection.js";
|
|
5
|
-
//#region src/email/html.ts
|
|
6
|
-
/** Escape a value for element content. */
|
|
7
|
-
const escapeText = (value) => value.replace(/[&<>]/g, (character) => ({
|
|
8
|
-
"&": "&",
|
|
9
|
-
"<": "<",
|
|
10
|
-
">": ">"
|
|
11
|
-
})[character] ?? character);
|
|
12
|
-
/** Escape a value for a double-quoted attribute. */
|
|
13
|
-
const escapeAttribute = (value) => value.replace(/[&<>"']/g, (character) => ({
|
|
14
|
-
"&": "&",
|
|
15
|
-
"<": "<",
|
|
16
|
-
">": ">",
|
|
17
|
-
"\"": """,
|
|
18
|
-
"'": "'"
|
|
19
|
-
})[character] ?? character);
|
|
20
|
-
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
|
|
21
|
-
const SAFE_URL_SCHEME = /^(https:\/\/|mailto:|tel:|cid:|#)/i;
|
|
22
|
-
const RELATIVE_URL = /^(\.{1,2}\/|\/(?!\/))/;
|
|
23
|
-
/**
|
|
24
|
-
* Only references an email client will follow safely; no `javascript:`, no
|
|
25
|
-
* `data:`. A relative reference is legal only for an export: a file on disk can
|
|
26
|
-
* resolve one against its own directory, and a delivered message cannot resolve
|
|
27
|
-
* anything but an absolute URL.
|
|
28
|
-
*/
|
|
29
|
-
const isSafeUrl = (value, relative) => !/^[\u0000-\u0020]/.test(value) && !CONTROL_CHARACTERS.test(value) && !value.includes("\\") && (SAFE_URL_SCHEME.test(value) || relative && RELATIVE_URL.test(value));
|
|
30
|
-
const CSS_PROPERTY = /^(--[a-zA-Z0-9-]+|[a-zA-Z][a-zA-Z0-9-]*)$/;
|
|
31
|
-
const CSS_UNSAFE = /[;{}<>\\]|expression\s*\(|@import|\/\*/i;
|
|
32
|
-
const CSS_URL = /url\(\s*(['"]?)([^'")]*)\1\s*\)/gi;
|
|
33
|
-
/** camelCase to kebab-case; already-kebab names pass through unchanged. */
|
|
34
|
-
const kebab = (property) => property.startsWith("--") ? property : property.replace(/[A-Z]/g, (l) => `-${l.toLowerCase()}`);
|
|
35
|
-
const UNITLESS = /* @__PURE__ */ new Set([
|
|
36
|
-
"opacity",
|
|
37
|
-
"zIndex",
|
|
38
|
-
"fontWeight",
|
|
39
|
-
"lineHeight",
|
|
40
|
-
"order",
|
|
41
|
-
"flexGrow",
|
|
42
|
-
"flexShrink",
|
|
43
|
-
"columnCount",
|
|
44
|
-
"orphans",
|
|
45
|
-
"widows",
|
|
46
|
-
"zoom",
|
|
47
|
-
"fillOpacity",
|
|
48
|
-
"strokeOpacity"
|
|
49
|
-
]);
|
|
50
|
-
/** A bare number is pixels for every property that takes a length. */
|
|
51
|
-
const declarationValue = (property, value) => typeof value === "number" && value !== 0 && !UNITLESS.has(property) && !property.startsWith("--") ? `${value}px` : String(value);
|
|
52
|
-
const report = (sink, code, message, origins, severity = "error") => {
|
|
53
|
-
sink.diagnostics.push({
|
|
54
|
-
code,
|
|
55
|
-
severity,
|
|
56
|
-
message,
|
|
57
|
-
origins
|
|
58
|
-
});
|
|
59
|
-
};
|
|
60
|
-
const declarations = (sink, style, origins) => {
|
|
61
|
-
const parts = [];
|
|
62
|
-
for (const [property, value] of Object.entries(style)) {
|
|
63
|
-
if (value === void 0 || value === null) continue;
|
|
64
|
-
if (!CSS_PROPERTY.test(property)) {
|
|
65
|
-
report(sink, "unsupported-style-property", `Style property ${JSON.stringify(property)} is not a CSS property name; write it in camelCase (backgroundColor) or as a custom property (--brand).`, origins);
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (typeof value !== "string" && typeof value !== "number") {
|
|
69
|
-
report(sink, "invalid-style-value", `Style ${property} must be a string or number, not ${typeof value}.`, origins);
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
const text = declarationValue(property, value);
|
|
73
|
-
if (CSS_UNSAFE.test(text) || CONTROL_CHARACTERS.test(text)) {
|
|
74
|
-
report(sink, "unsafe-style-value", `Style ${property} contains CSS that could break out of the declaration; remove ${JSON.stringify(text)}.`, origins);
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
let unsafeUrl;
|
|
78
|
-
for (const match of text.matchAll(CSS_URL)) if (!isSafeUrl(match[2] ?? "", sink.relativeUrls)) unsafeUrl = match[2] ?? "";
|
|
79
|
-
if (unsafeUrl !== void 0) {
|
|
80
|
-
report(sink, "unsafe-url", `Style ${property} points at ${JSON.stringify(unsafeUrl)}; email URLs must use https:, mailto:, tel:, cid: or a fragment${sink.relativeUrls ? ", or a relative path" : ""}.`, origins);
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
parts.push(`${kebab(property)}:${text}`);
|
|
84
|
-
}
|
|
85
|
-
return parts.join(";");
|
|
86
|
-
};
|
|
87
|
-
const attributeValue = (sink, tag, name, spec, value, origins) => {
|
|
88
|
-
if (value === void 0 || value === null || value === false) return void 0;
|
|
89
|
-
if (value === true) return "";
|
|
90
|
-
if (typeof value !== "string" && typeof value !== "number") {
|
|
91
|
-
report(sink, "invalid-attribute-value", `<${tag} ${name}> must be a string or number, not ${typeof value}.`, origins);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const text = String(value);
|
|
95
|
-
if (CONTROL_CHARACTERS.test(text)) {
|
|
96
|
-
report(sink, "invalid-attribute-value", `<${tag} ${name}> contains control characters.`, origins);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
if (spec.values !== void 0 && !spec.values.includes(text)) {
|
|
100
|
-
report(sink, "invalid-attribute-value", `<${tag} ${name}> accepts ${spec.values.join(", ")}; received ${JSON.stringify(text)}.`, origins);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (spec.type === "number" && !/^-?\d+$/.test(text)) {
|
|
104
|
-
report(sink, "invalid-attribute-value", `<${tag} ${name}> must be an integer.`, origins);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (spec.type === "measure" && !/^\d+(\.\d+)?%?$/.test(text)) {
|
|
108
|
-
report(sink, "invalid-attribute-value", `<${tag} ${name}> must be a pixel count or a percentage.`, origins);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
if (spec.type === "url" && !isSafeUrl(text, sink.relativeUrls)) {
|
|
112
|
-
report(sink, "unsafe-url", sink.relativeUrls ? `<${tag} ${name}> must use https:, mailto:, tel:, cid:, a fragment, or a relative path; received ${JSON.stringify(text)}.` : `<${tag} ${name}> must use https:, mailto:, tel:, cid: or a fragment; received ${JSON.stringify(text)}. A relative path resolves only in an export, never in a delivered message.`, origins);
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
return text;
|
|
116
|
-
};
|
|
117
|
-
const attributes = (sink, tag, spec, props, origins) => {
|
|
118
|
-
const emitted = [];
|
|
119
|
-
const present = /* @__PURE__ */ new Set();
|
|
120
|
-
let classes;
|
|
121
|
-
const unsubscribeLink = tag === "a" && props[UNSUBSCRIBE_LINK] === true;
|
|
122
|
-
for (const [key, value] of Object.entries(props)) {
|
|
123
|
-
if (key === "key") continue;
|
|
124
|
-
const name = key === "className" ? "class" : key;
|
|
125
|
-
if (unsubscribeLink && name === "href" && value === "%samva:unsubscribe-url%") {
|
|
126
|
-
present.add(name);
|
|
127
|
-
emitted.push(` href="${UNSUBSCRIBE_URL_PLACEHOLDER}" ses:no-track`);
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
if (name === "class") {
|
|
131
|
-
if (value === void 0 || value === null) continue;
|
|
132
|
-
if (typeof value !== "string") {
|
|
133
|
-
report(sink, "invalid-attribute-value", `<${tag} class> must be a string.`, origins);
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
if (classes !== void 0) {
|
|
137
|
-
report(sink, "duplicate-attribute", `<${tag}> sets both class and className; keep one.`, origins);
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
classes = value;
|
|
141
|
-
emitted.push(` class="${escapeAttribute(value)}"`);
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
if (name === "style") {
|
|
145
|
-
if (value === void 0 || value === null) continue;
|
|
146
|
-
if (!isObject(value) || Array.isArray(value)) {
|
|
147
|
-
report(sink, "invalid-style", `<${tag} style> must be a property object, for example style={{ backgroundColor: "#fff" }}.`, origins);
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
const css = declarations(sink, value, origins);
|
|
151
|
-
if (css !== "") emitted.push(` style="${escapeAttribute(css)}"`);
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
if (/^on/i.test(name)) {
|
|
155
|
-
report(sink, "event-handler", `<${tag} ${name}> is an event handler; email clients strip scripting and Samva refuses it.`, origins);
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
if (RESERVED_FOOTER_ATTRIBUTES.has(name) && props[BRAND_FOOTER_MARKER] !== true) {
|
|
159
|
-
report(sink, "reserved-attribute", `<${tag} ${name}> is written only by BrandFooter from samva:brand; render BrandFooter instead of marking an element as the footer.`, origins);
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
if (/^(data|aria)-[a-z0-9-]+$/.test(name)) {
|
|
163
|
-
const text = attributeValue(sink, tag, name, { type: "string" }, value, origins);
|
|
164
|
-
if (text !== void 0) emitted.push(` ${name}="${escapeAttribute(text)}"`);
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
const globalAttributes = EMAIL_GLOBAL_ATTRIBUTES;
|
|
168
|
-
const attribute = Object.hasOwn(globalAttributes, name) ? globalAttributes[name] : Object.hasOwn(spec.attributes, name) ? spec.attributes[name] : void 0;
|
|
169
|
-
if (attribute === void 0) {
|
|
170
|
-
const supported = [
|
|
171
|
-
...Object.keys(EMAIL_GLOBAL_ATTRIBUTES),
|
|
172
|
-
...Object.keys(spec.attributes),
|
|
173
|
-
"class",
|
|
174
|
-
"style"
|
|
175
|
-
].sort().join(", ");
|
|
176
|
-
report(sink, "unsupported-attribute", `<${tag}> does not support the ${name} attribute. Supported: ${supported}, plus data-* and aria-*.`, origins);
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
present.add(name);
|
|
180
|
-
const text = attributeValue(sink, tag, name, attribute, value, origins);
|
|
181
|
-
if (text !== void 0) emitted.push(` ${name}="${escapeAttribute(text)}"`);
|
|
182
|
-
}
|
|
183
|
-
for (const [name, attribute] of Object.entries(spec.attributes)) if (attribute.required === true && !present.has(name)) report(sink, "missing-attribute", `<${tag}> requires the ${name} attribute.`, origins);
|
|
184
|
-
return emitted.join("");
|
|
185
|
-
};
|
|
186
|
-
/** Fragments are transparent to a content model; raw compatibility markup is not content. */
|
|
187
|
-
const contentChildren = (children) => children.flatMap((child) => child.type === "Fragment" ? contentChildren(child.children) : [child]);
|
|
188
|
-
const checkContentModel = (sink, tag, spec, children, origins) => {
|
|
189
|
-
if (spec.content === "void") {
|
|
190
|
-
if (contentChildren(children).length > 0) report(sink, "void-element-children", `<${tag}> cannot have children.`, origins);
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
if (spec.content === "text" || spec.content === "raw") {
|
|
194
|
-
for (const child of contentChildren(children)) if (child.type !== "Text") report(sink, "invalid-content", `<${tag}> accepts text only.`, child.type === "Element" ? child.origins : origins);
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
if (spec.childElements === void 0) return;
|
|
198
|
-
for (const child of contentChildren(children)) {
|
|
199
|
-
if (child.type === "Raw") continue;
|
|
200
|
-
if (child.type === "Text") {
|
|
201
|
-
if (child.value.trim() !== "") report(sink, "invalid-content", `<${tag}> cannot contain text directly; put it inside <${spec.childElements[0]}>.`, origins);
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
if (child.type === "Element" && !spec.childElements.includes(child.tag)) report(sink, "invalid-content", `<${tag}> accepts only ${spec.childElements.map((name) => `<${name}>`).join(", ")}; found <${child.tag}>.`, child.origins);
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
/** Every write goes through here so `sink.written` stays the emitted offset. */
|
|
208
|
-
const emit = (sink, text) => {
|
|
209
|
-
sink.written += text.length;
|
|
210
|
-
return text;
|
|
211
|
-
};
|
|
212
|
-
const serialize = (sink, node, parentPath, counter) => {
|
|
213
|
-
switch (node.type) {
|
|
214
|
-
case "Text": return emit(sink, escapeText(node.value));
|
|
215
|
-
case "Raw": return emit(sink, node.html);
|
|
216
|
-
case "Fragment": return node.children.map((child) => serialize(sink, child, parentPath, counter)).join("");
|
|
217
|
-
case "Element": {
|
|
218
|
-
const start = sink.written;
|
|
219
|
-
const index = counter.next++;
|
|
220
|
-
const path = parentPath === "" ? String(index) : `${parentPath}.${index}`;
|
|
221
|
-
const own = { next: 0 };
|
|
222
|
-
const stamp = sink.instancePaths ? ` ${INSTANCE_PATH_ATTRIBUTE}="${path}"` : "";
|
|
223
|
-
const spec = emailElement(node.tag);
|
|
224
|
-
if (spec === void 0) {
|
|
225
|
-
report(sink, "unsupported-element", `<${node.tag}> is not part of the supported email vocabulary. Supported elements: ${Object.keys(EMAIL_ELEMENTS).join(", ")}.`, node.origins);
|
|
226
|
-
return "";
|
|
227
|
-
}
|
|
228
|
-
checkContentModel(sink, node.tag, spec, node.children, node.origins);
|
|
229
|
-
if (spec.content === "raw") {
|
|
230
|
-
const css = node.children.map((child) => child.type === "Text" ? child.value : "").join("");
|
|
231
|
-
if (/<\/|<!--/i.test(css)) {
|
|
232
|
-
report(sink, "unsafe-raw-text", `<${node.tag}> content cannot contain markup.`, node.origins);
|
|
233
|
-
return "";
|
|
234
|
-
}
|
|
235
|
-
const rawAttrs = attributes(sink, node.tag, spec, node.props, node.origins);
|
|
236
|
-
const raw = css === "" ? "" : `<${node.tag}${rawAttrs}${stamp}>${css}</${node.tag}>`;
|
|
237
|
-
return record(sink, start, node, path, emit(sink, raw));
|
|
238
|
-
}
|
|
239
|
-
const attrs = attributes(sink, node.tag, spec, node.props, node.origins);
|
|
240
|
-
if (spec.content === "void") return record(sink, start, node, path, emit(sink, `<${node.tag}${attrs}${stamp} />`));
|
|
241
|
-
const open = emit(sink, `<${node.tag}${attrs}${stamp}>`);
|
|
242
|
-
const children = node.children.map((child) => serialize(sink, child, path, own)).join("");
|
|
243
|
-
const close = emit(sink, `</${node.tag}>`);
|
|
244
|
-
return record(sink, start, node, path, `${open}${children}${close}`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
/** Record an element's emitted range once its content is written. */
|
|
249
|
-
const record = (sink, start, node, instancePath, text) => {
|
|
250
|
-
sink.positions.push({
|
|
251
|
-
start,
|
|
252
|
-
end: sink.written,
|
|
253
|
-
origins: node.origins,
|
|
254
|
-
instancePath,
|
|
255
|
-
tag: node.tag,
|
|
256
|
-
authored: node.authored
|
|
257
|
-
});
|
|
258
|
-
return text;
|
|
259
|
-
};
|
|
260
|
-
/** Lower the derived tree to email HTML, reporting every unsupported construct with its origin. */
|
|
261
|
-
const serializeEmailHtml = (node, options = {}) => {
|
|
262
|
-
const sink = {
|
|
263
|
-
diagnostics: [],
|
|
264
|
-
positions: [],
|
|
265
|
-
written: 0,
|
|
266
|
-
relativeUrls: options.relativeUrls === true,
|
|
267
|
-
instancePaths: options.instancePaths === true
|
|
268
|
-
};
|
|
269
|
-
return {
|
|
270
|
-
html: serialize(sink, node, "", { next: 0 }),
|
|
271
|
-
diagnostics: sink.diagnostics,
|
|
272
|
-
positions: sink.positions
|
|
273
|
-
};
|
|
274
|
-
};
|
|
275
|
-
//#endregion
|
|
276
|
-
export { declarationValue, escapeAttribute, escapeText, kebab, serializeEmailHtml };
|
|
277
|
-
|
|
278
|
-
//# sourceMappingURL=html.js.map
|
package/dist/email/render.d.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { r as EmailDiagnostic } from "../diagnostics-BMYtBCQk.js";
|
|
2
|
-
import { m as EmailNode } from "../elements-BzjjnoCS.js";
|
|
3
|
-
import { n as EmittedPosition } from "../selection-CCArx9lU.js";
|
|
4
|
-
import { n as SerializeEmailOptions } from "../html-Bl9BAdqc.js";
|
|
5
|
-
import { r as TemplateDefinition, t as EmailContent } from "../template-DWftqh4x.js";
|
|
6
|
-
import { i as Stylesheet } from "../css-CS2jLID6.js";
|
|
7
|
-
//#region src/email/render.d.ts
|
|
8
|
-
/** Classic Outlook lays a document out correctly only under a transitional doctype. */
|
|
9
|
-
declare const EMAIL_DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
|
|
10
|
-
interface EmailCompileOutput {
|
|
11
|
-
readonly html: string;
|
|
12
|
-
readonly text: string;
|
|
13
|
-
/** Findings that did not block the compile; blocking findings throw. */
|
|
14
|
-
readonly diagnostics: readonly EmailDiagnostic[];
|
|
15
|
-
/**
|
|
16
|
-
* Each emitted element's offset range and origins. Compatibility checks run
|
|
17
|
-
* in the host over the emitted document, and this is what lets a finding name
|
|
18
|
-
* the authoring element rather than a character offset.
|
|
19
|
-
*/
|
|
20
|
-
readonly positions: readonly EmittedPosition[];
|
|
21
|
-
}
|
|
22
|
-
/** Lower the derived tree to HTML and its plain-text alternative. */
|
|
23
|
-
declare const compileEmail: (node: EmailNode, options?: SerializeEmailOptions) => EmailCompileOutput;
|
|
24
|
-
interface RenderedEmail {
|
|
25
|
-
readonly subject: string;
|
|
26
|
-
readonly preheader: string | undefined;
|
|
27
|
-
readonly html: string;
|
|
28
|
-
readonly text: string;
|
|
29
|
-
readonly tree: EmailNode;
|
|
30
|
-
readonly diagnostics: readonly EmailDiagnostic[];
|
|
31
|
-
readonly positions: readonly EmittedPosition[];
|
|
32
|
-
}
|
|
33
|
-
/** Render one validated input through the template's only content function. Host isolation is required for untrusted source. */
|
|
34
|
-
declare const renderEmail: <Input>(template: TemplateDefinition<Input>, data: unknown, options?: SerializeEmailOptions) => RenderedEmail;
|
|
35
|
-
/**
|
|
36
|
-
* A render bound to the sheets registered so far. A compiled project's wrapper
|
|
37
|
-
* calls this once its own stylesheet imports have run, so a later project
|
|
38
|
-
* loaded into the same process cannot restyle an entry that is rendered again.
|
|
39
|
-
*/
|
|
40
|
-
declare const bindStylesheets: (stylesheet?: Stylesheet | undefined) => typeof renderEmail;
|
|
41
|
-
/**
|
|
42
|
-
* Render authored content against a stylesheet: inline what a client honors,
|
|
43
|
-
* keep the rest for `<head>`, and lower to HTML and text. `renderEmail` runs a
|
|
44
|
-
* validated template through it; a host that composes content without a
|
|
45
|
-
* template definition calls it directly.
|
|
46
|
-
*/
|
|
47
|
-
declare const renderEmailContent: (stylesheet: Stylesheet | undefined, render: () => EmailContent, options?: SerializeEmailOptions) => RenderedEmail;
|
|
48
|
-
//#endregion
|
|
49
|
-
export { EMAIL_DOCTYPE, EmailCompileOutput, RenderedEmail, bindStylesheets, compileEmail, renderEmail, renderEmailContent };
|
|
50
|
-
//# sourceMappingURL=render.d.ts.map
|
package/dist/email/render.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { a as emailDefinitionInstances, c as emailSelectionSiblings, i as emailAuthoringTarget, l as emailSelections, n as EmittedPosition, o as emailSelectionAncestry, r as INSTANCE_PATH_ATTRIBUTE, s as emailSelectionIndex, t as EmailElementSelection } from "../selection-CCArx9lU.js";
|
|
2
|
-
export { EmailElementSelection, EmittedPosition, INSTANCE_PATH_ATTRIBUTE, emailAuthoringTarget, emailDefinitionInstances, emailSelectionAncestry, emailSelectionIndex, emailSelectionSiblings, emailSelections };
|