@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.
Files changed (172) hide show
  1. package/AGENTS.md +107 -46
  2. package/CHANGELOG.md +345 -0
  3. package/README.md +188 -52
  4. package/dist/{brand-DcoVkh_S.d.ts → assets-BIsyMQ92.d.ts} +41 -11
  5. package/dist/{email/brand.js → brand-C0MKTwmX.js} +64 -4
  6. package/dist/{theme-css.d.ts → brand.d.ts} +12 -3
  7. package/dist/brand.js +4 -0
  8. package/dist/channel-specs-C2feJwuP.d.ts +13 -0
  9. package/dist/compiler.d.ts +88 -115
  10. package/dist/compiler.js +750 -171
  11. package/dist/components-BZCqegS8.js +1864 -0
  12. package/dist/diagnostic-model-DJtqbETd.js +38 -0
  13. package/dist/{diagnostics-OcQKQYSY.d.ts → diagnostic-model-DxthUZBT.d.ts} +3 -21
  14. package/dist/{diagnostics-BMYtBCQk.d.ts → diagnostics-B-Gym34R.d.ts} +8 -4
  15. package/dist/{email/diagnostics.js → diagnostics-CnKFefKz.js} +4 -3
  16. package/dist/diagnostics.d.ts +410 -2
  17. package/dist/diagnostics.js +402 -52
  18. package/dist/edit.d.ts +234 -0
  19. package/dist/edit.js +947 -0
  20. package/dist/email/jsx-dev-runtime.d.ts +2 -2
  21. package/dist/email/jsx-dev-runtime.js +1 -2
  22. package/dist/email/jsx-runtime.d.ts +1 -1
  23. package/dist/email/jsx-runtime.js +1 -85
  24. package/dist/{email/components.d.ts → email.d.ts} +22 -11
  25. package/dist/email.js +2 -0
  26. package/dist/fmt.d.ts +32 -0
  27. package/dist/fmt.js +174 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +2 -0
  30. package/dist/{render-vAv_4r9v.js → ir-CLhlklKV.js} +96 -279
  31. package/dist/jsx-runtime-CBlexoeR.js +98 -0
  32. package/dist/{elements-BzjjnoCS.d.ts → jsx-runtime-ylIOnaSf.d.ts} +76 -76
  33. package/dist/profile-pWGtWZXF.js +3803 -0
  34. package/dist/render-BPonDpCl.d.ts +539 -0
  35. package/dist/render-BQmfh94k.js +257 -0
  36. package/dist/render.d.ts +35 -0
  37. package/dist/render.js +49 -0
  38. package/dist/sms.d.ts +16 -0
  39. package/dist/sms.js +12 -0
  40. package/dist/source-locations-C8wCsiAy.d.ts +23 -0
  41. package/dist/template-DMPwxMSq.d.ts +65 -0
  42. package/dist/template-zLUjcwfF.js +42 -0
  43. package/dist/{theme-css-hFqcPSar.js → theme-css-JtRCm1fb.js} +2 -2
  44. package/dist/whatsapp.d.ts +57 -0
  45. package/dist/whatsapp.js +22 -0
  46. package/generated/contract.json +1788 -0
  47. package/generated/reference.md +360 -0
  48. package/package.json +46 -239
  49. package/samva-brand.d.ts +4 -4
  50. package/skills/sml/SKILL.md +123 -0
  51. package/dist/ast-BrDtApwS.d.ts +0 -107
  52. package/dist/ast.d.ts +0 -2
  53. package/dist/ast.js +0 -124
  54. package/dist/candidate.d.ts +0 -29
  55. package/dist/candidate.js +0 -88
  56. package/dist/catalog-CKuOooB8.d.ts +0 -354
  57. package/dist/checks-D73Xizbw.js +0 -59366
  58. package/dist/checks.d.ts +0 -33
  59. package/dist/checks.js +0 -2
  60. package/dist/classes.d.ts +0 -26
  61. package/dist/classes.js +0 -184
  62. package/dist/compile-sms.d.ts +0 -28
  63. package/dist/compile-sms.js +0 -44
  64. package/dist/compile-whatsapp.d.ts +0 -56
  65. package/dist/compile-whatsapp.js +0 -186
  66. package/dist/compile.d.ts +0 -18
  67. package/dist/compile.js +0 -29
  68. package/dist/components.d.ts +0 -289
  69. package/dist/components.js +0 -106
  70. package/dist/conditionals.d.ts +0 -10
  71. package/dist/conditionals.js +0 -74
  72. package/dist/corpus-CPPfgAHN.d.ts +0 -106
  73. package/dist/css-CS2jLID6.d.ts +0 -109
  74. package/dist/email/assets.d.ts +0 -40
  75. package/dist/email/assets.js +0 -63
  76. package/dist/email/brand-footer.d.ts +0 -34
  77. package/dist/email/brand-footer.js +0 -62
  78. package/dist/email/brand.d.ts +0 -2
  79. package/dist/email/components.js +0 -240
  80. package/dist/email/diagnostics.d.ts +0 -2
  81. package/dist/email/elements.d.ts +0 -2
  82. package/dist/email/elements.js +0 -299
  83. package/dist/email/html.d.ts +0 -2
  84. package/dist/email/html.js +0 -278
  85. package/dist/email/render.d.ts +0 -50
  86. package/dist/email/render.js +0 -3
  87. package/dist/email/selection.d.ts +0 -2
  88. package/dist/email/selection.js +0 -91
  89. package/dist/email/source-edit.d.ts +0 -67
  90. package/dist/email/source-edit.js +0 -185
  91. package/dist/email/stylesheets.d.ts +0 -15
  92. package/dist/email/stylesheets.js +0 -48
  93. package/dist/email/text.d.ts +0 -7
  94. package/dist/email/text.js +0 -100
  95. package/dist/emit.d.ts +0 -51
  96. package/dist/emit.js +0 -151
  97. package/dist/html-Bl9BAdqc.d.ts +0 -42
  98. package/dist/interpolate.d.ts +0 -24
  99. package/dist/interpolate.js +0 -43
  100. package/dist/jsx-dev-runtime.d.ts +0 -11
  101. package/dist/jsx-dev-runtime.js +0 -15
  102. package/dist/jsx-runtime-I-1nCtl2.d.ts +0 -57
  103. package/dist/jsx-runtime.d.ts +0 -2
  104. package/dist/jsx-runtime.js +0 -121
  105. package/dist/lexer.d.ts +0 -60
  106. package/dist/lexer.js +0 -281
  107. package/dist/model.d.ts +0 -24
  108. package/dist/model.js +0 -77
  109. package/dist/ops-BvwqHl7V.js +0 -630
  110. package/dist/ops.d.ts +0 -114
  111. package/dist/ops.js +0 -2
  112. package/dist/parser-t29cHLVL.d.ts +0 -396
  113. package/dist/parser.d.ts +0 -2
  114. package/dist/parser.js +0 -859
  115. package/dist/predicate-BI6uRRM-.js +0 -549
  116. package/dist/predicate-M-05Nd7R.d.ts +0 -56
  117. package/dist/predicate.d.ts +0 -2
  118. package/dist/predicate.js +0 -2
  119. package/dist/printer.d.ts +0 -6
  120. package/dist/printer.js +0 -122
  121. package/dist/registry-Dcp6QEK_.d.ts +0 -73
  122. package/dist/registry.d.ts +0 -2
  123. package/dist/registry.js +0 -646
  124. package/dist/render-template.d.ts +0 -45
  125. package/dist/render-template.js +0 -76
  126. package/dist/selection-CCArx9lU.d.ts +0 -91
  127. package/dist/showcase-egkusQBf.d.ts +0 -415
  128. package/dist/sms-segments-y4FpPilJ.d.ts +0 -13
  129. package/dist/sms-segments.d.ts +0 -2
  130. package/dist/sms-segments.js +0 -46
  131. package/dist/source-locations-DrCM82Ym.d.ts +0 -67
  132. package/dist/source-locations.d.ts +0 -2
  133. package/dist/source-locations.js +0 -102
  134. package/dist/starters/category-mapping.d.ts +0 -13
  135. package/dist/starters/category-mapping.js +0 -49
  136. package/dist/starters/corpus.d.ts +0 -2
  137. package/dist/starters/corpus.js +0 -194
  138. package/dist/starters/library.d.ts +0 -18
  139. package/dist/starters/library.js +0 -692
  140. package/dist/starters/public-gallery.d.ts +0 -266
  141. package/dist/starters/public-gallery.js +0 -916
  142. package/dist/starters/public-handoff.d.ts +0 -164
  143. package/dist/starters/public-handoff.js +0 -361
  144. package/dist/starters/public-preview-artifacts.d.ts +0 -43
  145. package/dist/starters/public-preview-artifacts.js +0 -104
  146. package/dist/starters/public-preview.d.ts +0 -61
  147. package/dist/starters/public-preview.js +0 -83
  148. package/dist/starters/showcase.d.ts +0 -2
  149. package/dist/starters/showcase.js +0 -335
  150. package/dist/starters/sources.d.ts +0 -45
  151. package/dist/starters/sources.js +0 -7762
  152. package/dist/starters/theme.d.ts +0 -45
  153. package/dist/starters/theme.js +0 -77
  154. package/dist/starters.d.ts +0 -2
  155. package/dist/starters.js +0 -874
  156. package/dist/tailwind-BYq7Lo6u.js +0 -727
  157. package/dist/template-DWftqh4x.d.ts +0 -60
  158. package/dist/template.d.ts +0 -2
  159. package/dist/template.js +0 -37
  160. package/dist/theme-css.js +0 -2
  161. package/dist/tsx-source-instrumentation.d.ts +0 -30
  162. package/dist/tsx-source-instrumentation.js +0 -115
  163. package/dist/tsx-tracer.d.ts +0 -112
  164. package/dist/tsx-tracer.js +0 -985
  165. package/dist/variables-J_L3qxRa.d.ts +0 -77
  166. package/dist/variables.d.ts +0 -2
  167. package/dist/variables.js +0 -141
  168. package/dist/version-BITr58kk.js +0 -96
  169. package/generated/sml-agent-contract.json +0 -709
  170. package/generated/sml-email-agent-contract.json +0 -615
  171. package/generated/sml-email-language-reference.md +0 -94
  172. package/generated/sml-language-reference.md +0 -195
@@ -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
@@ -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 };
@@ -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
- "&": "&amp;",
9
- "<": "&lt;",
10
- ">": "&gt;"
11
- })[character] ?? character);
12
- /** Escape a value for a double-quoted attribute. */
13
- const escapeAttribute = (value) => value.replace(/[&<>"']/g, (character) => ({
14
- "&": "&amp;",
15
- "<": "&lt;",
16
- ">": "&gt;",
17
- "\"": "&quot;",
18
- "'": "&#39;"
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
@@ -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
@@ -1,3 +0,0 @@
1
- import { a as renderEmailContent, i as renderEmail, n as bindStylesheets, r as compileEmail, t as EMAIL_DOCTYPE } from "../render-vAv_4r9v.js";
2
- import "./diagnostics.js";
3
- export { EMAIL_DOCTYPE, bindStylesheets, compileEmail, renderEmail, renderEmailContent };
@@ -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 };