@samva/markup 0.18.0 → 0.19.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 (54) hide show
  1. package/AGENTS.md +11 -4
  2. package/CHANGELOG.md +36 -0
  3. package/README.md +89 -5
  4. package/dist/assets-BSpgZOSR.js +61 -0
  5. package/dist/assets-k7_v3IOJ.d.ts +39 -0
  6. package/dist/{assets-CS5Xa_mr.d.ts → brand-Bxb6Sm7y.d.ts} +34 -41
  7. package/dist/brand.d.ts +2 -2
  8. package/dist/brand.js +3 -3
  9. package/dist/compatibility.d.ts +15 -5
  10. package/dist/compatibility.js +48 -5
  11. package/dist/compile-C1GWLQ5C.js +490 -0
  12. package/dist/compile-CIeuGmP-.d.ts +98 -0
  13. package/dist/{compiled-template-Bc4Dq9zy.d.ts → compiled-template-CPDHvFlt.d.ts} +157 -6
  14. package/dist/compiler.d.ts +4 -89
  15. package/dist/compiler.js +8 -486
  16. package/dist/components-DNBCz20_.js +3267 -0
  17. package/dist/{css-color-DLM12Osu.js → dark-scheme-BGDCCaUq.js} +12 -1
  18. package/dist/{diagnostic-codes-DZKb0Xcg.js → diagnostic-codes-BCwfUgnF.js} +33 -36
  19. package/dist/diagnostic-model-DJtqbETd.js +36 -0
  20. package/dist/{diagnostics-D4vSLQrO.d.ts → diagnostics-C3TSx6SU.d.ts} +22 -2
  21. package/dist/diagnostics.d.ts +33 -1
  22. package/dist/diagnostics.js +2 -1
  23. package/dist/edit.d.ts +15 -4
  24. package/dist/edit.js +67 -4
  25. package/dist/email/jsx-dev-runtime.d.ts +2 -2
  26. package/dist/email/jsx-runtime.d.ts +1 -1
  27. package/dist/email.d.ts +6 -2
  28. package/dist/email.js +6 -2
  29. package/dist/index.d.ts +1 -1
  30. package/dist/{jsx-runtime-DL8NVF1f.d.ts → jsx-runtime-HHfrVZEf.d.ts} +1 -2
  31. package/dist/markdown.d.ts +69 -0
  32. package/dist/markdown.js +95 -0
  33. package/dist/parse-Bex7QFqj.js +921 -0
  34. package/dist/{preview-rNoJBEIY.d.ts → preview-DRLWgmfg.d.ts} +3 -3
  35. package/dist/profile-DFk4gs7o.js +56 -0
  36. package/dist/{profile-JTvgCD6X.js → project-B6D64SZS.js} +497 -234
  37. package/dist/render.d.ts +8 -8
  38. package/dist/render.js +1 -2
  39. package/dist/{css-B_aVKrDo.js → selectors-CKGuc9MO.js} +158 -4
  40. package/dist/sms.d.ts +1 -1
  41. package/dist/{template-DmNcoH4Q.d.ts → template-4Dse7_OC.d.ts} +1 -1
  42. package/dist/{theme-css-P9F5S7Ic.js → theme-css-BujDuuEA.js} +1 -2
  43. package/dist/whatsapp.d.ts +1 -1
  44. package/generated/contract.json +41 -1
  45. package/generated/reference.md +97 -89
  46. package/package.json +15 -4
  47. package/dist/brand-CIiEqkU7.js +0 -193
  48. package/dist/brand-plugin-Dj9SMpfS.d.ts +0 -33
  49. package/dist/components-C5850-Qg.js +0 -409
  50. package/dist/dark-scheme-CbCwGDWH.js +0 -12
  51. package/dist/html-DJUK2YJF.js +0 -483
  52. package/dist/render-template-DwPDm3Hq.js +0 -1226
  53. package/dist/selectors-CYWCoxZr.js +0 -157
  54. package/dist/source-locations-C8wCsiAy.d.ts +0 -22
@@ -0,0 +1,921 @@
1
+ import { t as hasProperty } from "./guards-eV1qLbf2.js";
2
+ import { t as TEMPLATE_ID_PATTERN } from "./template-zLUjcwfF.js";
3
+ import { K as isValueNode, M as lowerEmailNodes, U as COMPONENT_DEGRADATIONS, p as checkAttribute, y as serializeStyle } from "./components-DNBCz20_.js";
4
+ import { i as lineColumn, t as buildLineMap } from "./diagnostic-model-DJtqbETd.js";
5
+ import { fromMarkdown } from "mdast-util-from-markdown";
6
+ import { mdxExpressionFromMarkdown } from "mdast-util-mdx-expression";
7
+ import { mdxJsxFromMarkdown } from "mdast-util-mdx-jsx";
8
+ import { mdxExpression } from "micromark-extension-mdx-expression";
9
+ import { mdxJsx } from "micromark-extension-mdx-jsx";
10
+ import { isMap, isScalar, parseDocument } from "yaml";
11
+ //#region generated/contract.json
12
+ var components = [
13
+ {
14
+ "name": "Email",
15
+ "import": "@samva/markup/email",
16
+ "props": [
17
+ {
18
+ "name": "className",
19
+ "type": "string",
20
+ "optional": true,
21
+ "description": "CSS classes applied to the primitive's content element."
22
+ },
23
+ {
24
+ "name": "lang",
25
+ "type": "string",
26
+ "optional": true
27
+ },
28
+ {
29
+ "name": "dir",
30
+ "type": "\"ltr\" | \"rtl\"",
31
+ "optional": true
32
+ },
33
+ {
34
+ "name": "title",
35
+ "type": "string",
36
+ "optional": true,
37
+ "description": "Document title; several clients use it as the fallback preview line."
38
+ },
39
+ {
40
+ "name": "backgroundColor",
41
+ "type": "string",
42
+ "optional": true,
43
+ "description": "The page color, painted across the whole body and, as `bgcolor`, on the wrapper table for clients that drop body styles. Defaults to the theme's `--color-background`, which switches in dark mode."
44
+ },
45
+ {
46
+ "name": "gutter",
47
+ "type": "number",
48
+ "optional": true,
49
+ "description": "Pixels of space each side of the content, so it never touches the screen edge on a narrow viewport. Defaults to 16; `0` lets content run edge to edge."
50
+ },
51
+ {
52
+ "name": "style",
53
+ "type": "EmailStyle",
54
+ "optional": true,
55
+ "description": "Applied to the content cell, where Yahoo and AOL keep body styling."
56
+ },
57
+ {
58
+ "name": "children",
59
+ "type": "EmailChild",
60
+ "optional": true
61
+ }
62
+ ]
63
+ },
64
+ {
65
+ "name": "Section",
66
+ "import": "@samva/markup/email",
67
+ "props": [
68
+ {
69
+ "name": "className",
70
+ "type": "string",
71
+ "optional": true,
72
+ "description": "CSS classes applied to the primitive's content element."
73
+ },
74
+ {
75
+ "name": "align",
76
+ "type": "\"left\" | \"center\" | \"right\"",
77
+ "optional": true
78
+ },
79
+ {
80
+ "name": "width",
81
+ "type": "number | string",
82
+ "optional": true,
83
+ "description": "The band's width. A pixel width (`600`) is a maximum: the band fills a narrower screen, and classic Outlook, which ignores `max-width`, draws it at that width."
84
+ },
85
+ {
86
+ "name": "bgcolor",
87
+ "type": "string",
88
+ "optional": true
89
+ },
90
+ {
91
+ "name": "style",
92
+ "type": "EmailStyle",
93
+ "optional": true,
94
+ "description": "Applied to the content cell so padding survives Outlook and Klaviyo."
95
+ },
96
+ {
97
+ "name": "tableStyle",
98
+ "type": "EmailStyle",
99
+ "optional": true
100
+ },
101
+ {
102
+ "name": "children",
103
+ "type": "EmailChild",
104
+ "optional": true
105
+ }
106
+ ]
107
+ },
108
+ {
109
+ "name": "Columns",
110
+ "import": "@samva/markup/email",
111
+ "props": [
112
+ {
113
+ "name": "className",
114
+ "type": "string",
115
+ "optional": true,
116
+ "description": "CSS classes applied to the primitive's content element."
117
+ },
118
+ {
119
+ "name": "align",
120
+ "type": "\"left\" | \"center\" | \"right\"",
121
+ "optional": true
122
+ },
123
+ {
124
+ "name": "width",
125
+ "type": "number | string",
126
+ "optional": true
127
+ },
128
+ {
129
+ "name": "style",
130
+ "type": "EmailStyle",
131
+ "optional": true
132
+ },
133
+ {
134
+ "name": "each",
135
+ "type": "readonly Item[]",
136
+ "optional": true,
137
+ "description": "Repeat the cells for each item: `children` is then a function from an item (and its index in the row) to a `Column`. Without `per` all items share one row."
138
+ },
139
+ {
140
+ "name": "per",
141
+ "type": "number",
142
+ "optional": true,
143
+ "description": "With `each`, the cells in a row; each chunk of items is its own `Columns` table."
144
+ },
145
+ {
146
+ "name": "stack",
147
+ "type": "boolean",
148
+ "optional": true,
149
+ "description": "Stack the cells into one column, each full width, on a viewport narrower than 600px. On by default; `stack={false}` keeps a row of small cells (icons, a price beside a label) side by side. Clients that ignore media queries keep the row."
150
+ },
151
+ {
152
+ "name": "gap",
153
+ "type": "number",
154
+ "optional": true,
155
+ "description": "Pixels between the cells of a row. A stacked row puts the same space between the stacked cells instead, so a gap needs no padding on the cells that would misalign them when stacked. The cells must be written out and always render."
156
+ },
157
+ {
158
+ "name": "children",
159
+ "type": "EmailChild | ((item: Item, index: number) => EmailChild)",
160
+ "optional": true,
161
+ "description": "`Column` elements, or with `each` a function that returns one."
162
+ }
163
+ ]
164
+ },
165
+ {
166
+ "name": "Column",
167
+ "import": "@samva/markup/email",
168
+ "props": [
169
+ {
170
+ "name": "className",
171
+ "type": "string",
172
+ "optional": true,
173
+ "description": "CSS classes applied to the primitive's content element."
174
+ },
175
+ {
176
+ "name": "align",
177
+ "type": "\"left\" | \"center\" | \"right\"",
178
+ "optional": true
179
+ },
180
+ {
181
+ "name": "valign",
182
+ "type": "\"top\" | \"middle\" | \"bottom\"",
183
+ "optional": true
184
+ },
185
+ {
186
+ "name": "width",
187
+ "type": "number | string",
188
+ "optional": true
189
+ },
190
+ {
191
+ "name": "style",
192
+ "type": "EmailStyle",
193
+ "optional": true
194
+ },
195
+ {
196
+ "name": "children",
197
+ "type": "EmailChild",
198
+ "optional": true
199
+ }
200
+ ]
201
+ },
202
+ {
203
+ "name": "Button",
204
+ "import": "@samva/markup/email",
205
+ "props": [
206
+ {
207
+ "name": "className",
208
+ "type": "string",
209
+ "optional": true,
210
+ "description": "CSS classes applied to the primitive's content element."
211
+ },
212
+ {
213
+ "name": "href",
214
+ "type": "string",
215
+ "optional": false
216
+ },
217
+ {
218
+ "name": "width",
219
+ "type": "number",
220
+ "optional": true,
221
+ "description": "Pixel width of the box. Classic Outlook draws the VML shape at a fixed width, so without this it is estimated from the label and `paddingX`, and the anchor sizes to its label."
222
+ },
223
+ {
224
+ "name": "height",
225
+ "type": "number",
226
+ "optional": true,
227
+ "description": "Pixel height; classic Outlook's VML fallback needs it."
228
+ },
229
+ {
230
+ "name": "paddingX",
231
+ "type": "number",
232
+ "optional": true,
233
+ "description": "Horizontal padding on each side of the label when no `width` fixes the box."
234
+ },
235
+ {
236
+ "name": "backgroundColor",
237
+ "type": "string",
238
+ "optional": true
239
+ },
240
+ {
241
+ "name": "color",
242
+ "type": "string",
243
+ "optional": true
244
+ },
245
+ {
246
+ "name": "borderRadius",
247
+ "type": "number",
248
+ "optional": true
249
+ },
250
+ {
251
+ "name": "borderColor",
252
+ "type": "string",
253
+ "optional": true
254
+ },
255
+ {
256
+ "name": "borderWidth",
257
+ "type": "number",
258
+ "optional": true
259
+ },
260
+ {
261
+ "name": "fontSize",
262
+ "type": "number",
263
+ "optional": true
264
+ },
265
+ {
266
+ "name": "fontFamily",
267
+ "type": "string",
268
+ "optional": true
269
+ },
270
+ {
271
+ "name": "align",
272
+ "type": "\"left\" | \"center\" | \"right\"",
273
+ "optional": true
274
+ },
275
+ {
276
+ "name": "style",
277
+ "type": "EmailStyle",
278
+ "optional": true
279
+ },
280
+ {
281
+ "name": "children",
282
+ "type": "EmailChild",
283
+ "optional": true,
284
+ "description": "The label. A string label is required for the classic Outlook fallback."
285
+ }
286
+ ]
287
+ },
288
+ {
289
+ "name": "Spacer",
290
+ "import": "@samva/markup/email",
291
+ "props": [{
292
+ "name": "height",
293
+ "type": "number",
294
+ "optional": false
295
+ }]
296
+ },
297
+ {
298
+ "name": "Divider",
299
+ "import": "@samva/markup/email",
300
+ "props": [
301
+ {
302
+ "name": "className",
303
+ "type": "string",
304
+ "optional": true,
305
+ "description": "CSS classes applied to the primitive's content element."
306
+ },
307
+ {
308
+ "name": "color",
309
+ "type": "string",
310
+ "optional": true
311
+ },
312
+ {
313
+ "name": "thickness",
314
+ "type": "number",
315
+ "optional": true
316
+ },
317
+ {
318
+ "name": "style",
319
+ "type": "EmailStyle",
320
+ "optional": true
321
+ }
322
+ ]
323
+ },
324
+ {
325
+ "name": "Image",
326
+ "import": "@samva/markup/email",
327
+ "props": [
328
+ {
329
+ "name": "className",
330
+ "type": "string",
331
+ "optional": true,
332
+ "description": "CSS classes applied to the primitive's content element."
333
+ },
334
+ {
335
+ "name": "src",
336
+ "type": "string",
337
+ "optional": false
338
+ },
339
+ {
340
+ "name": "alt",
341
+ "type": "string",
342
+ "optional": false,
343
+ "description": "Required. Use an empty string only for images that carry no meaning."
344
+ },
345
+ {
346
+ "name": "width",
347
+ "type": "number | string",
348
+ "optional": true
349
+ },
350
+ {
351
+ "name": "height",
352
+ "type": "number | string",
353
+ "optional": true
354
+ },
355
+ {
356
+ "name": "align",
357
+ "type": "\"left\" | \"center\" | \"right\"",
358
+ "optional": true
359
+ },
360
+ {
361
+ "name": "style",
362
+ "type": "EmailStyle",
363
+ "optional": true
364
+ }
365
+ ]
366
+ },
367
+ {
368
+ "name": "Link",
369
+ "import": "@samva/markup/email",
370
+ "props": [
371
+ {
372
+ "name": "className",
373
+ "type": "string",
374
+ "optional": true,
375
+ "description": "CSS classes applied to the primitive's content element."
376
+ },
377
+ {
378
+ "name": "href",
379
+ "type": "string",
380
+ "optional": false
381
+ },
382
+ {
383
+ "name": "target",
384
+ "type": "string",
385
+ "optional": true
386
+ },
387
+ {
388
+ "name": "rel",
389
+ "type": "string",
390
+ "optional": true
391
+ },
392
+ {
393
+ "name": "style",
394
+ "type": "EmailStyle",
395
+ "optional": true
396
+ },
397
+ {
398
+ "name": "children",
399
+ "type": "EmailChild",
400
+ "optional": true
401
+ }
402
+ ]
403
+ },
404
+ {
405
+ "name": "BrandLogo",
406
+ "import": "samva:brand",
407
+ "props": [
408
+ {
409
+ "name": "width",
410
+ "type": "number",
411
+ "optional": true,
412
+ "description": "Rendered width in pixels; the height follows the image."
413
+ },
414
+ {
415
+ "name": "alt",
416
+ "type": "string",
417
+ "optional": true,
418
+ "description": "Defaults to the brand footer's company name."
419
+ },
420
+ {
421
+ "name": "align",
422
+ "type": "\"left\" | \"center\" | \"right\"",
423
+ "optional": true
424
+ },
425
+ {
426
+ "name": "className",
427
+ "type": "string",
428
+ "optional": true
429
+ }
430
+ ]
431
+ },
432
+ {
433
+ "name": "BrandFooter",
434
+ "import": "samva:brand",
435
+ "props": [{
436
+ "name": "unsubscribeLabel",
437
+ "type": "string",
438
+ "optional": true,
439
+ "description": "Text of the unsubscribe link."
440
+ }, {
441
+ "name": "className",
442
+ "type": "string",
443
+ "optional": true
444
+ }]
445
+ }
446
+ ];
447
+ //#endregion
448
+ //#region src/markdown/frontmatter.ts
449
+ /** The YAML AST keeps duplicate keys, tags and aliases visible instead of coercing them. */
450
+ const readFrontmatter = (source, report) => {
451
+ const opening = /^(?:\uFEFF)?---[ \t]*(?:\r\n|\n|\r)/.exec(source);
452
+ if (opening === null) {
453
+ report("An issue requires YAML frontmatter with title, slug and topic.", 0);
454
+ return {
455
+ markdown: source,
456
+ frontmatter: void 0
457
+ };
458
+ }
459
+ const bodyStart = opening[0].length;
460
+ const closing = /^---[ \t]*(?:\r?$)/m.exec(source.slice(bodyStart));
461
+ if (closing === null) {
462
+ report("Close the YAML frontmatter with a line containing ---.", 0);
463
+ return {
464
+ markdown: "",
465
+ frontmatter: void 0
466
+ };
467
+ }
468
+ const bodyEnd = bodyStart + closing.index;
469
+ const end = bodyEnd + closing[0].length;
470
+ const markdown = source.slice(0, end).replace(/[^\r\n]/g, " ") + source.slice(end);
471
+ const document = parseDocument(source.slice(bodyStart, bodyEnd), { uniqueKeys: true });
472
+ let invalid = false;
473
+ const fail = (message, offset = 0) => {
474
+ invalid = true;
475
+ report(message, bodyStart + offset);
476
+ };
477
+ for (const error of document.errors) fail(error.message, error.pos[0]);
478
+ for (const warning of document.warnings) fail(warning.message, warning.pos[0]);
479
+ if (!isMap(document.contents)) {
480
+ fail("Frontmatter must be a YAML mapping with title, slug and topic.");
481
+ return {
482
+ markdown,
483
+ frontmatter: void 0
484
+ };
485
+ }
486
+ const keys = (map, allowed) => {
487
+ if (map.tag !== void 0 || map.anchor !== void 0) fail("YAML tags and anchors are not supported.", map.range?.[0]);
488
+ for (const pair of map.items) {
489
+ if (!isScalar(pair.key) || typeof pair.key.value !== "string") {
490
+ fail("Frontmatter field names must be strings.");
491
+ continue;
492
+ }
493
+ if (!allowed.includes(pair.key.value)) fail(`Unknown frontmatter field ${JSON.stringify(pair.key.value)}.`, pair.key.range?.[0]);
494
+ }
495
+ };
496
+ const string = (map, name, required = false) => {
497
+ const value = map.get(name, true);
498
+ if (value === void 0 && !required) return void 0;
499
+ if (!isScalar(value) || typeof value.value !== "string" || required && value.value.trim() === "" || value.tag !== void 0 || value.anchor !== void 0) {
500
+ fail(`${name} must be ${required ? "a nonempty" : "a"} string.`);
501
+ return;
502
+ }
503
+ return value.value;
504
+ };
505
+ const root = document.contents;
506
+ keys(root, [
507
+ "title",
508
+ "slug",
509
+ "topic",
510
+ "preheader",
511
+ "seo",
512
+ "gate"
513
+ ]);
514
+ const title = string(root, "title", true);
515
+ const slug = string(root, "slug", true);
516
+ const topic = string(root, "topic", true);
517
+ const preheader = string(root, "preheader");
518
+ if (slug !== void 0 && !TEMPLATE_ID_PATTERN.test(slug)) fail("slug must be a lowercase kebab-case identifier of at most 100 characters.");
519
+ let seo;
520
+ const seoNode = root.get("seo", true);
521
+ if (seoNode !== void 0) {
522
+ if (!isMap(seoNode)) fail("seo must be a mapping of title, description and image.");
523
+ else {
524
+ keys(seoNode, [
525
+ "title",
526
+ "description",
527
+ "image"
528
+ ]);
529
+ const seoTitle = string(seoNode, "title");
530
+ const description = string(seoNode, "description");
531
+ const image = string(seoNode, "image");
532
+ if (image !== void 0 && (!URL.canParse(image) || !/^https:\/\//i.test(image) || checkAttribute("img", "src", image, []).diagnostics.length > 0 || URL.canParse(image) && (new URL(image).username !== "" || new URL(image).password !== ""))) fail("seo.image must be a safe absolute https URL without credentials.");
533
+ seo = {
534
+ ...seoTitle === void 0 ? {} : { title: seoTitle },
535
+ ...description === void 0 ? {} : { description },
536
+ ...image === void 0 ? {} : { image }
537
+ };
538
+ }
539
+ }
540
+ let gate;
541
+ const gateNode = root.get("gate", true);
542
+ if (gateNode !== void 0) {
543
+ if (!isMap(gateNode)) fail("gate must be a mapping with leadIn.");
544
+ else {
545
+ keys(gateNode, ["leadIn"]);
546
+ const leadIn = gateNode.get("leadIn", true);
547
+ if (!isScalar(leadIn) || typeof leadIn.value !== "number" || !Number.isSafeInteger(leadIn.value) || leadIn.value < 0 || leadIn.tag !== void 0 || leadIn.anchor !== void 0) fail("gate.leadIn must be a nonnegative integer counting leading top-level blocks.");
548
+ else gate = { leadIn: leadIn.value };
549
+ }
550
+ }
551
+ return {
552
+ markdown,
553
+ frontmatter: invalid || title === void 0 || slug === void 0 || topic === void 0 ? void 0 : {
554
+ title,
555
+ slug,
556
+ topic,
557
+ ...preheader === void 0 ? {} : { preheader },
558
+ ...seo === void 0 ? {} : { seo },
559
+ ...gate === void 0 ? {} : { gate }
560
+ }
561
+ };
562
+ };
563
+ //#endregion
564
+ //#region src/markdown/parse.ts
565
+ const componentName = (name) => Object.hasOwn(COMPONENT_DEGRADATIONS, name);
566
+ /** Parse a static issue without importing, evaluating or passing through authored HTML. */
567
+ const parseIssue = (source, file) => {
568
+ const diagnostics = [];
569
+ const sourceNodes = [];
570
+ const lines = buildLineMap(source);
571
+ const location = (offset) => {
572
+ const at = lineColumn(lines, offset);
573
+ return {
574
+ fileName: file,
575
+ lineNumber: at.line,
576
+ columnNumber: at.column
577
+ };
578
+ };
579
+ const report = (code, message, offset) => {
580
+ diagnostics.push({
581
+ code,
582
+ message,
583
+ severity: "error",
584
+ origins: [location(offset)]
585
+ });
586
+ };
587
+ const { markdown, frontmatter } = readFrontmatter(source, (message, offset) => report("markdown-frontmatter", message, offset));
588
+ const result = (nodes) => ({
589
+ nodes: diagnostics.some((item) => item.severity === "error") ? [] : nodes,
590
+ frontmatter,
591
+ diagnostics,
592
+ sources: [file],
593
+ sourceNodes
594
+ });
595
+ let tree;
596
+ try {
597
+ tree = fromMarkdown(markdown, {
598
+ extensions: [
599
+ { disable: { null: ["htmlFlow", "htmlText"] } },
600
+ mdxJsx(),
601
+ mdxExpression()
602
+ ],
603
+ mdastExtensions: [mdxJsxFromMarkdown(), mdxExpressionFromMarkdown()]
604
+ });
605
+ } catch (cause) {
606
+ const line = hasProperty(cause, "line") && typeof cause.line === "number" ? cause.line : 1;
607
+ const column = hasProperty(cause, "column") && typeof cause.column === "number" ? cause.column : 1;
608
+ const offset = (lines[line - 1] ?? 0) + column - 1;
609
+ const html = /<!--|<![A-Z]|<\?/.exec(markdown);
610
+ report(html === null ? "markdown-syntax" : "markdown-html", html === null ? `Invalid static Markdown: ${String(cause)}` : "Raw HTML, comments and processing instructions are not supported; use Markdown or SML components.", html?.index ?? offset);
611
+ return result([]);
612
+ }
613
+ const start = (node) => node.position?.start.offset ?? 0;
614
+ const end = (node) => node.position?.end.offset ?? start(node);
615
+ const src = (node) => {
616
+ const at = lineColumn(lines, start(node));
617
+ return [
618
+ at.line,
619
+ at.column,
620
+ 0
621
+ ];
622
+ };
623
+ const record = (node, kind, tag, properties = [], content) => {
624
+ const children = "children" in node ? node.children : [];
625
+ const text = node.type === "text" ? node : children.length === 1 && children[0]?.type === "text" ? children[0] : void 0;
626
+ const textProperties = text === void 0 ? [] : [{
627
+ name: "children",
628
+ value: text.value,
629
+ literalType: "string",
630
+ start: start(text),
631
+ end: end(text),
632
+ syntax: "markdown"
633
+ }];
634
+ sourceNodes.push({
635
+ kind,
636
+ tag,
637
+ start: start(node),
638
+ end: end(node),
639
+ contentStart: content?.[0] ?? (children[0] === void 0 ? start(node) : start(children[0])),
640
+ contentEnd: content?.[1] ?? (children.length === 0 ? end(node) : end(children[children.length - 1])),
641
+ src: src(node),
642
+ properties: [...properties, ...textProperties]
643
+ });
644
+ };
645
+ const attach = (items, offset) => {
646
+ for (const diagnostic of items) diagnostics.push({
647
+ ...diagnostic,
648
+ origins: [location(offset)]
649
+ });
650
+ };
651
+ const attribute = (tag, name, value, offset) => {
652
+ attach(checkAttribute(tag, name, value, [location(offset)]).diagnostics, offset);
653
+ };
654
+ const definitions = /* @__PURE__ */ new Map();
655
+ const collectDefinitions = (node) => {
656
+ if (node.type === "definition" && !definitions.has(node.identifier)) definitions.set(node.identifier, node);
657
+ if ("children" in node) for (const child of node.children) collectDefinitions(child);
658
+ };
659
+ collectDefinitions(tree);
660
+ const linkProperties = (node, reference, image) => {
661
+ const properties = [];
662
+ const property = (name, value, from, to) => {
663
+ properties.push({
664
+ name,
665
+ value,
666
+ literalType: "string",
667
+ start: from,
668
+ end: to,
669
+ syntax: "markdown"
670
+ });
671
+ };
672
+ const labelEnd = (from, to) => {
673
+ let depth = 0;
674
+ for (let index = from; index < to; index++) {
675
+ if (source[index] === "\\") {
676
+ index++;
677
+ continue;
678
+ }
679
+ if (source[index] === "`") {
680
+ let count = 1;
681
+ while (source[index + count] === "`") count++;
682
+ const delimiter = "`".repeat(count);
683
+ let close = source.indexOf(delimiter, index + count);
684
+ while (close >= 0 && close < to && (source[close - 1] === "`" || source[close + count] === "`")) close = source.indexOf(delimiter, close + count);
685
+ if (close >= 0 && close < to) {
686
+ index = close + count - 1;
687
+ continue;
688
+ }
689
+ }
690
+ if (source[index] === "[") depth++;
691
+ if (source[index] === "]" && --depth === 0) return index;
692
+ }
693
+ return to;
694
+ };
695
+ const labelStart = start(node) + (image ? 2 : 1);
696
+ if (image && "alt" in node) property("alt", node.alt ?? "", labelStart, labelEnd(labelStart - 1, end(node)));
697
+ const from = start(reference);
698
+ const to = end(reference);
699
+ const autolink = source[from] === "<";
700
+ let cursor = autolink ? from + 1 : labelEnd(from, to) + 2;
701
+ while (/\s/.test(source[cursor] ?? "") && cursor < to) cursor++;
702
+ const angle = source[cursor] === "<";
703
+ if (angle) cursor++;
704
+ const urlStart = cursor;
705
+ let depth = 0;
706
+ for (; cursor < to; cursor++) {
707
+ const character = source[cursor];
708
+ if (character === "\\") {
709
+ cursor++;
710
+ continue;
711
+ }
712
+ if ((autolink || angle) && character === ">") break;
713
+ if (!autolink && !angle) {
714
+ if (character === "(") depth++;
715
+ else if (character === ")") {
716
+ if (depth === 0) break;
717
+ depth--;
718
+ } else if (/\s/.test(character ?? "")) break;
719
+ }
720
+ }
721
+ property(image ? "src" : "href", reference.url, urlStart, cursor);
722
+ if (angle) cursor++;
723
+ while (/\s/.test(source[cursor] ?? "") && cursor < to) cursor++;
724
+ if (reference.title !== null && reference.title !== void 0) {
725
+ const delimiter = source[cursor] === "(" ? ")" : source[cursor];
726
+ const titleStart = ++cursor;
727
+ for (; cursor < to; cursor++) {
728
+ if (source[cursor] === "\\") {
729
+ cursor++;
730
+ continue;
731
+ }
732
+ if (source[cursor] === delimiter) break;
733
+ }
734
+ property("title", reference.title, titleStart, cursor);
735
+ }
736
+ return properties;
737
+ };
738
+ const propValue = (spec, value, offset) => {
739
+ const types = spec.type.split(/\s*\|\s*/);
740
+ if (types.includes("boolean")) {
741
+ if (value === true || value === "true") return true;
742
+ if (value === "false") return false;
743
+ }
744
+ if (typeof value === "string") {
745
+ if (types.includes("string")) return value;
746
+ if (types.includes("number") && /^-?(?:\d+(?:\.\d*)?|\.\d+)$/.test(value)) {
747
+ const number = Number(value);
748
+ if (Number.isFinite(number)) return number;
749
+ }
750
+ if (types.some((type) => type === JSON.stringify(value))) return value;
751
+ }
752
+ report("markdown-prop", `${spec.name} requires a static ${spec.type} value.`, offset);
753
+ };
754
+ const component = (node, columnStack) => {
755
+ const name = node.name;
756
+ if (name === null || !componentName(name)) {
757
+ report(name !== null && /^[a-z]/.test(name) ? "markdown-html" : "unknown-component", `Unsupported tag <${name ?? ""}>; use Markdown or a component from the SML component registry.`, start(node));
758
+ return [];
759
+ }
760
+ const spec = components.find((candidate) => candidate.name === name);
761
+ if (spec === void 0) {
762
+ report("markdown-prop", `The generated prop contract is missing ${name}.`, start(node));
763
+ return [];
764
+ }
765
+ const props = {};
766
+ const properties = [];
767
+ const seen = /* @__PURE__ */ new Set();
768
+ for (const attr of node.attributes) {
769
+ const offset = attr.position?.start.offset ?? start(node);
770
+ if (attr.type !== "mdxJsxAttribute" || attr.value !== null && attr.value !== void 0 && typeof attr.value !== "string") {
771
+ report("markdown-expression", "Expressions and spread attributes are not supported in SML Markdown.", offset);
772
+ continue;
773
+ }
774
+ if (seen.has(attr.name)) {
775
+ report("markdown-prop", `Duplicate ${attr.name} attribute on <${name}>.`, offset);
776
+ continue;
777
+ }
778
+ seen.add(attr.name);
779
+ const field = spec.props.find((prop) => prop.name === attr.name && prop.name !== "children");
780
+ if (field === void 0) {
781
+ report("markdown-prop", `<${name}> does not support the ${attr.name} attribute.`, offset);
782
+ continue;
783
+ }
784
+ const value = propValue(field, attr.value ?? true, offset);
785
+ if (value === void 0) continue;
786
+ props[attr.name] = value;
787
+ if (name === "BrandLogo" && attr.name === "width" && typeof value === "number") attribute("img", "width", value, offset);
788
+ const attrEnd = attr.position?.end.offset ?? offset + attr.name.length;
789
+ const quote = source.slice(offset, attrEnd).search(/["']/);
790
+ properties.push({
791
+ name: attr.name,
792
+ value,
793
+ literalType: typeof value === "string" ? "string" : typeof value === "number" ? "number" : "boolean",
794
+ start: quote < 0 ? offset : offset + quote,
795
+ end: attrEnd,
796
+ syntax: quote < 0 ? "boolean" : "quoted"
797
+ });
798
+ if (typeof value === "string") {
799
+ if (/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f\uE000\uE001]/.test(value)) report("markdown-prop", `${attr.name} contains a control character or reserved token delimiter.`, offset);
800
+ if (attr.name === "href") attribute("a", "href", value, offset);
801
+ if (attr.name === "src") attribute("img", "src", value, offset);
802
+ }
803
+ }
804
+ for (const field of spec.props) if (!field.optional && !Object.hasOwn(props, field.name)) report("markdown-prop", `<${name}> requires the ${field.name} attribute.`, start(node));
805
+ const kids = node.children.flatMap((child) => walk(child, name === "Columns" && props.stack !== false));
806
+ const raw = source.slice(start(node), end(node));
807
+ const lastAttribute = node.attributes[node.attributes.length - 1];
808
+ const openEnd = source.indexOf(">", lastAttribute?.position?.end.offset ?? start(node));
809
+ const closeStart = source[openEnd - 1] === "/" ? openEnd + 1 : start(node) + raw.lastIndexOf("</");
810
+ record(node, "component", name, properties, [openEnd + 1, closeStart]);
811
+ if (kids.length > 0 && !spec.props.some((field) => field.name === "children")) report("markdown-prop", `<${name}> does not accept children.`, start(node));
812
+ return [{
813
+ type: "component",
814
+ name,
815
+ props,
816
+ children: kids,
817
+ ...name === "Column" ? { emailColumn: { stack: columnStack } } : {},
818
+ src: src(node)
819
+ }];
820
+ };
821
+ const intrinsic = (node, type, tag, kids, attrs, properties = []) => {
822
+ record(node, type, tag, properties);
823
+ return [{
824
+ type,
825
+ tag,
826
+ children: kids,
827
+ ...attrs === void 0 ? {} : { attrs },
828
+ src: src(node)
829
+ }];
830
+ };
831
+ const walk = (node, columnStack = false) => {
832
+ const walkChild = (child) => walk(child, columnStack);
833
+ switch (node.type) {
834
+ case "root": return node.children.flatMap(walkChild);
835
+ case "text":
836
+ record(node, "text", "#text");
837
+ if (/[\uE000\uE001]/.test(node.value)) report("markdown-syntax", "Text contains a reserved compiler token delimiter.", start(node));
838
+ return [node.value];
839
+ case "paragraph": return intrinsic(node, "block", "p", node.children.flatMap(walkChild));
840
+ case "heading": return intrinsic(node, "block", `h${node.depth}`, node.children.flatMap(walkChild));
841
+ case "blockquote": return intrinsic(node, "block", "blockquote", node.children.flatMap(walkChild));
842
+ case "list": return intrinsic(node, "block", node.ordered ? "ol" : "ul", node.children.flatMap(walkChild), node.ordered && node.start !== null && node.start !== void 0 && node.start !== 1 ? { start: node.start } : void 0);
843
+ case "listItem": return intrinsic(node, "block", "li", node.children.flatMap(walkChild));
844
+ case "strong": return intrinsic(node, "inline", "strong", node.children.flatMap(walkChild));
845
+ case "emphasis": return intrinsic(node, "inline", "em", node.children.flatMap(walkChild));
846
+ case "inlineCode": return intrinsic(node, "inline", "code", [node.value]);
847
+ case "code": return intrinsic(node, "block", "pre", [{
848
+ type: "inline",
849
+ tag: "code",
850
+ children: [node.value],
851
+ src: src(node)
852
+ }]);
853
+ case "break": return intrinsic(node, "inline", "br", []);
854
+ case "thematicBreak": return intrinsic(node, "block", "hr", []);
855
+ case "link":
856
+ case "image":
857
+ case "linkReference":
858
+ case "imageReference": {
859
+ const reference = node.type === "linkReference" || node.type === "imageReference" ? definitions.get(node.identifier) : node;
860
+ if (reference === void 0) {
861
+ report("markdown-syntax", "The Markdown reference has no definition.", start(node));
862
+ return [];
863
+ }
864
+ const image = node.type === "image" || node.type === "imageReference";
865
+ const attrs = image ? {
866
+ src: reference.url,
867
+ alt: "alt" in node ? node.alt ?? "" : ""
868
+ } : { href: reference.url };
869
+ if (reference.title !== null && reference.title !== void 0) attrs.title = reference.title;
870
+ attribute(image ? "img" : "a", image ? "src" : "href", reference.url, start(node));
871
+ return intrinsic(node, "inline", image ? "img" : "a", "children" in node ? node.children.flatMap(walkChild) : [], attrs, linkProperties(node, reference, image));
872
+ }
873
+ case "definition": return [];
874
+ case "mdxJsxFlowElement":
875
+ case "mdxJsxTextElement": return component(node, columnStack);
876
+ case "mdxFlowExpression":
877
+ case "mdxTextExpression":
878
+ report("markdown-expression", "Expressions are not supported in SML Markdown; write static content.", start(node));
879
+ return [];
880
+ case "html":
881
+ report("markdown-html", "Raw HTML is not supported; use Markdown or SML components.", start(node));
882
+ return [];
883
+ default:
884
+ report("markdown-syntax", `Unsupported Markdown construct ${node.type}.`, start(node));
885
+ return [];
886
+ }
887
+ };
888
+ const nodes = walk(tree);
889
+ if (!diagnostics.some((item) => item.severity === "error")) {
890
+ const lowerStatic = (list) => list.flatMap((node) => {
891
+ if (isValueNode(node)) return [node];
892
+ if ("type" in node && node.type === "component" && (node.name === "BrandLogo" || node.name === "BrandFooter")) return [];
893
+ const lowered = {
894
+ ...node,
895
+ ..."children" in node && node.children !== void 0 ? { children: lowerStatic(node.children) } : {}
896
+ };
897
+ const at = "src" in node && node.src !== void 0 ? (lines[node.src[0] - 1] ?? 0) + node.src[1] - 1 : 0;
898
+ return lowerEmailNodes([lowered], void 0, (diagnostic) => attach([{
899
+ ...diagnostic,
900
+ origins: []
901
+ }], at));
902
+ });
903
+ const lowered = lowerStatic(nodes);
904
+ const validate = (list) => {
905
+ for (const node of list) {
906
+ if (isValueNode(node)) continue;
907
+ if ("el" in node) {
908
+ const at = node.src === void 0 ? 0 : (lines[node.src[0] - 1] ?? 0) + node.src[1] - 1;
909
+ for (const [name, value] of Object.entries(node.attrs ?? {})) if (typeof value === "string" || typeof value === "number") attribute(node.el, name, value, at);
910
+ if (Array.isArray(node.style)) attach(serializeStyle(Object.fromEntries(node.style), [location(at)]).diagnostics, at);
911
+ }
912
+ if ("children" in node && node.children !== void 0) validate(node.children);
913
+ }
914
+ };
915
+ validate(lowered);
916
+ }
917
+ sourceNodes.sort((a, b) => a.start - b.start || b.end - a.end);
918
+ return result(nodes);
919
+ };
920
+ //#endregion
921
+ export { parseIssue as t };