@samva/markup 0.10.0 → 0.11.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 +5 -1
- package/CHANGELOG.md +34 -0
- package/dist/{assets-BIsyMQ92.d.ts → assets-B0yseju_.d.ts} +9 -12
- package/dist/{brand-C0MKTwmX.js → brand-DTctcDmf.js} +19 -25
- package/dist/brand-plugin-CHhcei-j.d.ts +32 -0
- package/dist/brand.d.ts +10 -8
- package/dist/brand.js +4 -4
- package/dist/compiler.d.ts +22 -24
- package/dist/compiler.js +43 -40
- package/dist/components-Bl6DT21n.js +275 -0
- package/dist/diagnostics.d.ts +3 -3
- package/dist/diagnostics.js +3 -3
- package/dist/edit.js +1 -1
- package/dist/email.js +1 -1
- package/dist/{components-BZCqegS8.js → html-DjPxwjLi.js} +89 -316
- package/dist/{ir-CLhlklKV.js → ir-De6pTH51.js} +2 -2
- package/dist/{profile-pWGtWZXF.js → profile-PUfi3GPH.js} +34 -33
- package/dist/{render-BQmfh94k.js → render-D2ht57ZZ.js} +4 -3
- package/dist/{render-BPonDpCl.d.ts → render-DQKz4Aco.d.ts} +10 -1
- package/dist/render.d.ts +1 -1
- package/dist/render.js +3 -3
- package/dist/{theme-css-JtRCm1fb.js → theme-css-CEUrDpqq.js} +2 -2
- package/generated/contract.json +3 -3
- package/generated/reference.md +3 -3
- package/package.json +4 -4
- package/dist/unsubscribe-link-CoesnQDU.js +0 -26
|
@@ -1,7 +1,52 @@
|
|
|
1
1
|
import { n as isObject } from "./guards-eV1qLbf2.js";
|
|
2
|
-
import { i as UNSUBSCRIBE_URL_PLACEHOLDER$1, n as RESERVED_FOOTER_ATTRIBUTES, r as UNSUBSCRIBE_LINK, t as BRAND_FOOTER_MARKER } from "./unsubscribe-link-CoesnQDU.js";
|
|
3
2
|
import { callFormatter, createFmt } from "./fmt.js";
|
|
4
|
-
|
|
3
|
+
//#region src/internal/unsubscribe-link.ts
|
|
4
|
+
/**
|
|
5
|
+
* Marks the one link the serializer lets carry the brand plugin's unsubscribe
|
|
6
|
+
* placeholder as its href. Only `BrandFooter` sets it; nothing public exports
|
|
7
|
+
* it, so no author-written link can claim the exemption.
|
|
8
|
+
*/
|
|
9
|
+
const UNSUBSCRIBE_LINK = Symbol("samva.unsubscribe-link");
|
|
10
|
+
/**
|
|
11
|
+
* Marks the elements `BrandFooter` writes the brand plugin's footer markers
|
|
12
|
+
* on. The serializer emits those reserved attributes only for an element
|
|
13
|
+
* carrying it, so an author cannot claim a footer the send path would then
|
|
14
|
+
* not append.
|
|
15
|
+
*/
|
|
16
|
+
const BRAND_FOOTER_MARKER = Symbol("samva.brand-footer-marker");
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/email/samva-brand-plugin.ts
|
|
19
|
+
/**
|
|
20
|
+
* Samva's brand plugin, the compiler's default. A template imports its
|
|
21
|
+
* organization's brand from `samva:brand`, and Samva's send path fills
|
|
22
|
+
* `%samva:unsubscribe-url%` per delivery and turns `data-samva-no-track` into
|
|
23
|
+
* its email provider's own no-track marker.
|
|
24
|
+
*/
|
|
25
|
+
const samvaBrandPlugin = {
|
|
26
|
+
specifier: "samva:brand",
|
|
27
|
+
footerAttribute: "data-samva-footer",
|
|
28
|
+
unsubscribeRowAttribute: "data-samva-unsubscribe",
|
|
29
|
+
unsubscribeUrlPlaceholder: "%samva:unsubscribe-url%",
|
|
30
|
+
noTrackAttribute: "data-samva-no-track"
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/email/brand-plugin.ts
|
|
34
|
+
const SLUG = "[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?";
|
|
35
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
36
|
+
/** `<specifier>` or `<specifier>/<slug>`, as a regular expression source with no groups. */
|
|
37
|
+
const brandSpecifierPattern = (plugin) => `${escapeRegExp(plugin.specifier)}(?:\\/${SLUG})?`;
|
|
38
|
+
/** The brand a specifier names: `undefined` slug means the default brand; `undefined` when it names none. */
|
|
39
|
+
const parseBrandSpecifier = (specifier, plugin = samvaBrandPlugin) => {
|
|
40
|
+
const match = new RegExp(`^${escapeRegExp(plugin.specifier)}(?:\\/(${SLUG}))?$`).exec(specifier);
|
|
41
|
+
return match === null ? void 0 : { slug: match[1] };
|
|
42
|
+
};
|
|
43
|
+
/** The attributes only `BrandFooter` writes; an authored element carrying one is refused. */
|
|
44
|
+
const reservedBrandAttributes = (plugin) => /* @__PURE__ */ new Set([
|
|
45
|
+
plugin.footerAttribute,
|
|
46
|
+
plugin.unsubscribeRowAttribute,
|
|
47
|
+
plugin.noTrackAttribute
|
|
48
|
+
]);
|
|
49
|
+
//#endregion
|
|
5
50
|
//#region src/email/elements.ts
|
|
6
51
|
/** Attributes every supported element accepts. `class`/`className`/`style` are handled separately. */
|
|
7
52
|
const EMAIL_GLOBAL_ATTRIBUTES = {
|
|
@@ -317,10 +362,8 @@ const MAX_ITERATIONS = 1e4;
|
|
|
317
362
|
const MAX_DEPTH = 64;
|
|
318
363
|
/** Classic Outlook lays a document out correctly only under a transitional doctype. */
|
|
319
364
|
const EMAIL_DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
|
|
320
|
-
|
|
321
|
-
const
|
|
322
|
-
const PREHEADER_TARGET_LENGTH$1 = 200;
|
|
323
|
-
const PREHEADER_PADDING$1 = "\xA0";
|
|
365
|
+
const PREHEADER_TARGET_LENGTH = 200;
|
|
366
|
+
const PREHEADER_PADDING = "\xA0";
|
|
324
367
|
const escapeText$1 = (value) => value.replace(/[&<>]/g, (character) => ({
|
|
325
368
|
"&": "&",
|
|
326
369
|
"<": "<",
|
|
@@ -589,9 +632,9 @@ const buildAttributes = (ctx, scope, tag, spec, node, props, depth) => {
|
|
|
589
632
|
const name = key === "className" ? "class" : key;
|
|
590
633
|
const value = evalValue(ctx, scope, raw, depth + 1);
|
|
591
634
|
props.set(name, value);
|
|
592
|
-
if (tag === "a" && name === "href" && raw ===
|
|
635
|
+
if (tag === "a" && name === "href" && raw === ctx.brandPlugin.unsubscribeUrlPlaceholder) {
|
|
593
636
|
present.add(name);
|
|
594
|
-
emitted.push(` href="${
|
|
637
|
+
emitted.push(` href="${escapeAttribute$1(ctx.brandPlugin.unsubscribeUrlPlaceholder)}" ${ctx.brandPlugin.noTrackAttribute}`);
|
|
595
638
|
continue;
|
|
596
639
|
}
|
|
597
640
|
if (name === "class") {
|
|
@@ -620,7 +663,7 @@ const buildAttributes = (ctx, scope, tag, spec, node, props, depth) => {
|
|
|
620
663
|
report$1(ctx, "event-handler", `<${tag} ${name}> is an event handler; email clients strip scripting and Samva refuses it.`);
|
|
621
664
|
continue;
|
|
622
665
|
}
|
|
623
|
-
if ((name
|
|
666
|
+
if (ctx.reservedAttributes.has(name) && node.reserved !== true) {
|
|
624
667
|
report$1(ctx, "reserved-attribute", `<${tag} ${name}> is reserved for the brand footer.`);
|
|
625
668
|
continue;
|
|
626
669
|
}
|
|
@@ -800,10 +843,10 @@ const PREHEADER_STYLE = [
|
|
|
800
843
|
/** The hidden inbox-preview block: the text, then padding so the client cannot borrow body copy. */
|
|
801
844
|
const preheaderBlock = (ctx, text) => {
|
|
802
845
|
const children = [text];
|
|
803
|
-
if (text.length < PREHEADER_TARGET_LENGTH
|
|
846
|
+
if (text.length < PREHEADER_TARGET_LENGTH) children.push({
|
|
804
847
|
el: "div",
|
|
805
848
|
attrs: { "data-samva-preheader-padding": true },
|
|
806
|
-
children: [PREHEADER_PADDING
|
|
849
|
+
children: [PREHEADER_PADDING.repeat(Math.ceil((PREHEADER_TARGET_LENGTH - text.length) / 7))]
|
|
807
850
|
});
|
|
808
851
|
const out = [];
|
|
809
852
|
evalElement(ctx, /* @__PURE__ */ new Map(), {
|
|
@@ -976,17 +1019,22 @@ const derive = (node, context) => {
|
|
|
976
1019
|
}
|
|
977
1020
|
};
|
|
978
1021
|
const normalize = (value) => value.replace(/\r\n?/g, "\n").split("\n").map((line) => line.replace(/[^\S\n]+$/, "")).join("\n").replace(/\n{3,}/g, PARAGRAPH).trim();
|
|
979
|
-
const createContext = (ir, input, options, plain) =>
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1022
|
+
const createContext = (ir, input, options, plain) => {
|
|
1023
|
+
const brandPlugin = options.brandPlugin ?? samvaBrandPlugin;
|
|
1024
|
+
return {
|
|
1025
|
+
input,
|
|
1026
|
+
locale: options.locale ?? ir.locale,
|
|
1027
|
+
timeZone: options.timeZone,
|
|
1028
|
+
plain,
|
|
1029
|
+
brandPlugin,
|
|
1030
|
+
reservedAttributes: reservedBrandAttributes(brandPlugin),
|
|
1031
|
+
diagnostics: [],
|
|
1032
|
+
iterations: 0,
|
|
1033
|
+
fmt: void 0,
|
|
1034
|
+
preheader: void 0,
|
|
1035
|
+
markerUsed: false
|
|
1036
|
+
};
|
|
1037
|
+
};
|
|
990
1038
|
const settle = (ctx) => {
|
|
991
1039
|
if (ctx.diagnostics.length > 0) throw new IrRenderError(ctx.diagnostics);
|
|
992
1040
|
};
|
|
@@ -1392,12 +1440,13 @@ const attributes = (sink, tag, spec, props, origins) => {
|
|
|
1392
1440
|
const present = /* @__PURE__ */ new Set();
|
|
1393
1441
|
let classes;
|
|
1394
1442
|
const unsubscribeLink = tag === "a" && props[UNSUBSCRIBE_LINK] === true;
|
|
1443
|
+
const { unsubscribeUrlPlaceholder, noTrackAttribute } = sink.brandPlugin;
|
|
1395
1444
|
for (const [key, value] of Object.entries(props)) {
|
|
1396
1445
|
if (key === "key") continue;
|
|
1397
1446
|
const name = key === "className" ? "class" : key;
|
|
1398
|
-
if (unsubscribeLink && name === "href" && value ===
|
|
1447
|
+
if (unsubscribeLink && name === "href" && value === unsubscribeUrlPlaceholder) {
|
|
1399
1448
|
present.add(name);
|
|
1400
|
-
emitted.push(` href="${
|
|
1449
|
+
emitted.push(` href="${escapeAttribute(unsubscribeUrlPlaceholder)}" ${noTrackAttribute}`);
|
|
1401
1450
|
continue;
|
|
1402
1451
|
}
|
|
1403
1452
|
if (name === "class") {
|
|
@@ -1428,8 +1477,8 @@ const attributes = (sink, tag, spec, props, origins) => {
|
|
|
1428
1477
|
report(sink, "event-handler", `<${tag} ${name}> is an event handler; email clients strip scripting and Samva refuses it.`, origins);
|
|
1429
1478
|
continue;
|
|
1430
1479
|
}
|
|
1431
|
-
if (
|
|
1432
|
-
report(sink, "reserved-attribute", `<${tag} ${name}> is written only by BrandFooter from
|
|
1480
|
+
if (sink.reservedAttributes.has(name) && props[BRAND_FOOTER_MARKER] !== true) {
|
|
1481
|
+
report(sink, "reserved-attribute", `<${tag} ${name}> is written only by BrandFooter from ${sink.brandPlugin.specifier}; render BrandFooter instead of marking an element as the footer.`, origins);
|
|
1433
1482
|
continue;
|
|
1434
1483
|
}
|
|
1435
1484
|
if (/^(data|aria)-[a-z0-9-]+$/.test(name)) {
|
|
@@ -1530,15 +1579,21 @@ const record = (sink, start, node, instancePath, text) => {
|
|
|
1530
1579
|
});
|
|
1531
1580
|
return text;
|
|
1532
1581
|
};
|
|
1533
|
-
|
|
1534
|
-
const
|
|
1535
|
-
|
|
1582
|
+
const serializationSink = (options) => {
|
|
1583
|
+
const brandPlugin = options.brandPlugin ?? samvaBrandPlugin;
|
|
1584
|
+
return {
|
|
1536
1585
|
diagnostics: [],
|
|
1537
1586
|
positions: [],
|
|
1538
1587
|
written: 0,
|
|
1539
1588
|
relativeUrls: options.relativeUrls === true,
|
|
1540
|
-
instancePaths: options.instancePaths === true
|
|
1589
|
+
instancePaths: options.instancePaths === true,
|
|
1590
|
+
brandPlugin,
|
|
1591
|
+
reservedAttributes: reservedBrandAttributes(brandPlugin)
|
|
1541
1592
|
};
|
|
1593
|
+
};
|
|
1594
|
+
/** Lower the derived tree to email HTML, reporting every unsupported construct with its origin. */
|
|
1595
|
+
const serializeEmailHtml = (node, options = {}) => {
|
|
1596
|
+
const sink = serializationSink(options);
|
|
1542
1597
|
return {
|
|
1543
1598
|
html: serialize(sink, node, "", { next: 0 }),
|
|
1544
1599
|
diagnostics: sink.diagnostics,
|
|
@@ -1547,13 +1602,7 @@ const serializeEmailHtml = (node, options = {}) => {
|
|
|
1547
1602
|
};
|
|
1548
1603
|
/** An authored style object as a declaration list, checked by the same rules as a serialized tree. */
|
|
1549
1604
|
const serializeStyle = (style, origins) => {
|
|
1550
|
-
const sink = {
|
|
1551
|
-
diagnostics: [],
|
|
1552
|
-
positions: [],
|
|
1553
|
-
written: 0,
|
|
1554
|
-
relativeUrls: false,
|
|
1555
|
-
instancePaths: false
|
|
1556
|
-
};
|
|
1605
|
+
const sink = serializationSink({});
|
|
1557
1606
|
return {
|
|
1558
1607
|
css: declarations(sink, style, origins),
|
|
1559
1608
|
diagnostics: sink.diagnostics
|
|
@@ -1564,13 +1613,7 @@ const serializeStyle = (style, origins) => {
|
|
|
1564
1613
|
* the text that would be emitted, or `undefined` when the attribute is dropped or invalid.
|
|
1565
1614
|
*/
|
|
1566
1615
|
const checkAttribute = (tag, name, value, origins) => {
|
|
1567
|
-
const sink = {
|
|
1568
|
-
diagnostics: [],
|
|
1569
|
-
positions: [],
|
|
1570
|
-
written: 0,
|
|
1571
|
-
relativeUrls: false,
|
|
1572
|
-
instancePaths: false
|
|
1573
|
-
};
|
|
1616
|
+
const sink = serializationSink({});
|
|
1574
1617
|
const element = emailElement(tag);
|
|
1575
1618
|
const globals = EMAIL_GLOBAL_ATTRIBUTES;
|
|
1576
1619
|
const attributeSpec = () => {
|
|
@@ -1589,276 +1632,6 @@ const checkAttribute = (tag, name, value, origins) => {
|
|
|
1589
1632
|
};
|
|
1590
1633
|
};
|
|
1591
1634
|
//#endregion
|
|
1592
|
-
|
|
1593
|
-
/** Marks the hidden preview block so the renderer can read it back and the text lane can keep it. */
|
|
1594
|
-
const PREHEADER_ATTRIBUTE = "data-samva-preheader";
|
|
1595
|
-
const PREHEADER_PADDING_ATTRIBUTE = "data-samva-preheader-padding";
|
|
1596
|
-
const PREHEADER_TARGET_LENGTH = 200;
|
|
1597
|
-
const PREHEADER_PADDING = "\xA0";
|
|
1598
|
-
const presentation = (table, row, rowProps = {}) => jsx("table", {
|
|
1599
|
-
role: "presentation",
|
|
1600
|
-
border: 0,
|
|
1601
|
-
cellpadding: 0,
|
|
1602
|
-
cellspacing: 0,
|
|
1603
|
-
...table,
|
|
1604
|
-
children: jsx("tbody", { children: jsx("tr", {
|
|
1605
|
-
...rowProps,
|
|
1606
|
-
children: row
|
|
1607
|
-
}) })
|
|
1608
|
-
});
|
|
1609
|
-
/** The hidden inbox preview block. Padding characters stop the client borrowing body copy. */
|
|
1610
|
-
const preheaderNode = (text) => jsx("div", {
|
|
1611
|
-
[PREHEADER_ATTRIBUTE]: text,
|
|
1612
|
-
style: {
|
|
1613
|
-
display: "none",
|
|
1614
|
-
overflow: "hidden",
|
|
1615
|
-
lineHeight: "1px",
|
|
1616
|
-
opacity: 0,
|
|
1617
|
-
maxHeight: 0,
|
|
1618
|
-
maxWidth: 0
|
|
1619
|
-
},
|
|
1620
|
-
children: [text, text.length >= PREHEADER_TARGET_LENGTH ? null : jsx("div", {
|
|
1621
|
-
[PREHEADER_PADDING_ATTRIBUTE]: true,
|
|
1622
|
-
children: PREHEADER_PADDING.repeat(Math.ceil((PREHEADER_TARGET_LENGTH - text.length) / 7))
|
|
1623
|
-
})]
|
|
1624
|
-
});
|
|
1625
|
-
/** Document shell: the head every target client needs, plus the body content cell. */
|
|
1626
|
-
const Email = ({ className, lang = "en", dir = "ltr", title, backgroundColor, style, children }) => jsx("html", {
|
|
1627
|
-
lang,
|
|
1628
|
-
dir,
|
|
1629
|
-
xmlns: "http://www.w3.org/1999/xhtml",
|
|
1630
|
-
"xmlns:o": "urn:schemas-microsoft-com:office:office",
|
|
1631
|
-
children: [jsx("head", { children: [
|
|
1632
|
-
jsx("meta", { charset: "utf-8" }),
|
|
1633
|
-
jsx("meta", {
|
|
1634
|
-
"http-equiv": "Content-Type",
|
|
1635
|
-
content: "text/html; charset=UTF-8"
|
|
1636
|
-
}),
|
|
1637
|
-
jsx("meta", {
|
|
1638
|
-
name: "viewport",
|
|
1639
|
-
content: "width=device-width, initial-scale=1"
|
|
1640
|
-
}),
|
|
1641
|
-
jsx("meta", { name: "x-apple-disable-message-reformatting" }),
|
|
1642
|
-
jsx("meta", {
|
|
1643
|
-
name: "color-scheme",
|
|
1644
|
-
content: "light dark"
|
|
1645
|
-
}),
|
|
1646
|
-
jsx("meta", {
|
|
1647
|
-
name: "supported-color-schemes",
|
|
1648
|
-
content: "light dark"
|
|
1649
|
-
}),
|
|
1650
|
-
title === void 0 ? null : jsx("title", { children: title }),
|
|
1651
|
-
rawMarkup("<!--[if mso]><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->")
|
|
1652
|
-
] }), jsx("body", {
|
|
1653
|
-
style: {
|
|
1654
|
-
margin: 0,
|
|
1655
|
-
padding: 0,
|
|
1656
|
-
...backgroundColor === void 0 ? {} : { backgroundColor }
|
|
1657
|
-
},
|
|
1658
|
-
children: [presentation({
|
|
1659
|
-
align: "center",
|
|
1660
|
-
width: "100%",
|
|
1661
|
-
...backgroundColor === void 0 ? {} : { bgcolor: backgroundColor }
|
|
1662
|
-
}, jsx("td", {
|
|
1663
|
-
className,
|
|
1664
|
-
...style === void 0 ? {} : { style },
|
|
1665
|
-
children
|
|
1666
|
-
}))]
|
|
1667
|
-
})]
|
|
1668
|
-
});
|
|
1669
|
-
/** A full-width band of content, as the single-cell table every client agrees on. */
|
|
1670
|
-
const Section = ({ className, align = "center", width = "100%", bgcolor, style, tableStyle, children }) => presentation({
|
|
1671
|
-
align,
|
|
1672
|
-
width,
|
|
1673
|
-
...bgcolor === void 0 ? {} : { bgcolor },
|
|
1674
|
-
...tableStyle === void 0 ? {} : { style: tableStyle }
|
|
1675
|
-
}, jsx("td", {
|
|
1676
|
-
className,
|
|
1677
|
-
...style === void 0 ? {} : { style },
|
|
1678
|
-
children
|
|
1679
|
-
}));
|
|
1680
|
-
/** The equal share of a row that each of `count` cells takes, whole percent rounded down. */
|
|
1681
|
-
const equalColumnWidth = (count) => `${Math.floor(100 / count)}%`;
|
|
1682
|
-
/** Give a `Column` that states no width its equal share, keeping the primitive's attribute order. */
|
|
1683
|
-
const withColumnWidth = (node, width) => {
|
|
1684
|
-
if (node.type === "Fragment") return EmailNode.Fragment({ children: node.children.map((child) => withColumnWidth(child, width)) });
|
|
1685
|
-
if (node.type !== "Element" || node.tag !== "td" || node.props.width !== void 0) return node;
|
|
1686
|
-
const props = {};
|
|
1687
|
-
for (const [key, value] of Object.entries(node.props)) {
|
|
1688
|
-
if (key === "style") props.width = width;
|
|
1689
|
-
props[key] = value;
|
|
1690
|
-
}
|
|
1691
|
-
if (props.width === void 0) props.width = width;
|
|
1692
|
-
return EmailNode.Element({
|
|
1693
|
-
...node,
|
|
1694
|
-
props
|
|
1695
|
-
});
|
|
1696
|
-
};
|
|
1697
|
-
/**
|
|
1698
|
-
* A row of side-by-side `Column`s. No flex or grid conversion happens here. With `each` it repeats
|
|
1699
|
-
* its cells over a list, `per` to a row, and splits the row equally between cells that state no
|
|
1700
|
-
* width.
|
|
1701
|
-
*/
|
|
1702
|
-
const Columns = ({ className, align = "center", width = "100%", style, each, per, children }) => {
|
|
1703
|
-
const table = {
|
|
1704
|
-
className,
|
|
1705
|
-
align,
|
|
1706
|
-
width,
|
|
1707
|
-
...style === void 0 ? {} : { style }
|
|
1708
|
-
};
|
|
1709
|
-
if (each === void 0 || typeof children !== "function") return presentation(table, children, { style: { width: "100%" } });
|
|
1710
|
-
if (per !== void 0 && (!Number.isInteger(per) || per < 1)) reportEmailDiagnostic({
|
|
1711
|
-
code: "invalid-columns-per",
|
|
1712
|
-
severity: "error",
|
|
1713
|
-
message: "<Columns per> must be a positive integer."
|
|
1714
|
-
});
|
|
1715
|
-
const size = per !== void 0 && Number.isInteger(per) && per >= 1 ? per : void 0;
|
|
1716
|
-
const chunks = [];
|
|
1717
|
-
if (size === void 0) chunks.push(each);
|
|
1718
|
-
else for (let at = 0; at < each.length; at += size) chunks.push(each.slice(at, at + size));
|
|
1719
|
-
const share = equalColumnWidth(size ?? Math.max(each.length, 1));
|
|
1720
|
-
return jsx(Fragment, { children: chunks.map((chunk) => presentation(table, chunk.flatMap((item, index) => normalizeChildren(children(item, index)).map((cell) => withColumnWidth(cell, share))), { style: { width: "100%" } })) });
|
|
1721
|
-
};
|
|
1722
|
-
/** One cell of a `Columns` row. */
|
|
1723
|
-
const Column = ({ className, align, valign, width, style, children }) => jsx("td", {
|
|
1724
|
-
className,
|
|
1725
|
-
...align === void 0 ? {} : { align },
|
|
1726
|
-
...valign === void 0 ? {} : { valign },
|
|
1727
|
-
...width === void 0 ? {} : { width },
|
|
1728
|
-
...style === void 0 ? {} : { style },
|
|
1729
|
-
children
|
|
1730
|
-
});
|
|
1731
|
-
const DEFAULT_BUTTON_FONT = "Helvetica, Arial, sans-serif";
|
|
1732
|
-
/** Average advance of a bold sans-serif glyph, as a fraction of the font size. */
|
|
1733
|
-
const BOLD_GLYPH_ADVANCE = .6;
|
|
1734
|
-
const invalidButtonNumber = (name) => reportEmailDiagnostic({
|
|
1735
|
-
code: "invalid-button-number",
|
|
1736
|
-
severity: "error",
|
|
1737
|
-
message: `<Button ${name}> must be a finite number.`
|
|
1738
|
-
});
|
|
1739
|
-
const buttonNumber = (name, value, fallback) => {
|
|
1740
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1741
|
-
invalidButtonNumber(name);
|
|
1742
|
-
return fallback;
|
|
1743
|
-
};
|
|
1744
|
-
const optionalButtonNumber = (name, value) => {
|
|
1745
|
-
if (value === void 0) return void 0;
|
|
1746
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1747
|
-
invalidButtonNumber(name);
|
|
1748
|
-
};
|
|
1749
|
-
const optionalPositiveButtonNumber = (name, value) => {
|
|
1750
|
-
if (value === void 0) return void 0;
|
|
1751
|
-
if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
|
|
1752
|
-
reportEmailDiagnostic({
|
|
1753
|
-
code: "invalid-button-number",
|
|
1754
|
-
severity: "error",
|
|
1755
|
-
message: `<Button ${name}> must be a positive finite number.`
|
|
1756
|
-
});
|
|
1757
|
-
};
|
|
1758
|
-
/** A link styled as a button, with a VML fallback so classic Outlook draws the shape. */
|
|
1759
|
-
const Button = ({ className, href, width, height, paddingX = 16, backgroundColor = "#000000", color = "#ffffff", borderRadius = 0, borderColor, borderWidth = 0, fontSize = 16, fontFamily = DEFAULT_BUTTON_FONT, align = "left", style, children }) => {
|
|
1760
|
-
const safeWidth = optionalButtonNumber("width", width);
|
|
1761
|
-
const safeHeight = optionalPositiveButtonNumber("height", height);
|
|
1762
|
-
const safePaddingX = buttonNumber("paddingX", paddingX, 16);
|
|
1763
|
-
const safeBorderRadius = buttonNumber("borderRadius", borderRadius, 0);
|
|
1764
|
-
const safeBorderWidth = buttonNumber("borderWidth", borderWidth, 0);
|
|
1765
|
-
const safeFontSize = buttonNumber("fontSize", fontSize, 16);
|
|
1766
|
-
const label = typeof children === "string" ? children : void 0;
|
|
1767
|
-
const fallback = safeHeight !== void 0 && label !== void 0;
|
|
1768
|
-
if (!fallback) reportEmailDiagnostic({
|
|
1769
|
-
code: "button-outlook-fallback",
|
|
1770
|
-
severity: "warning",
|
|
1771
|
-
message: "<Button> draws its classic Outlook shape from VML, which needs a height and a plain string label. Without both, classic Windows Outlook shows an unstyled link.",
|
|
1772
|
-
clients: ["outlook-windows"]
|
|
1773
|
-
});
|
|
1774
|
-
const arcsize = safeHeight === void 0 ? 0 : Math.round(safeBorderRadius / safeHeight * 100);
|
|
1775
|
-
const fallbackWidth = safeWidth ?? Math.ceil((label?.length ?? 0) * safeFontSize * BOLD_GLYPH_ADVANCE) + 2 * safePaddingX;
|
|
1776
|
-
return presentation({
|
|
1777
|
-
width: "100%",
|
|
1778
|
-
style: { borderCollapse: "collapse" }
|
|
1779
|
-
}, jsx("td", {
|
|
1780
|
-
align,
|
|
1781
|
-
children: [fallback ? rawMarkup(`<!--[if mso]><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${escapeAttribute(href)}" style="height:${safeHeight}px;v-text-anchor:middle;width:${fallbackWidth}px;" arcsize="${arcsize}%" ${borderColor === void 0 ? "stroke=\"false\"" : `strokecolor="${escapeAttribute(borderColor)}" strokeweight="${safeBorderWidth}px"`} fillcolor="${escapeAttribute(backgroundColor)}"><w:anchorlock/><center style="color:${escapeAttribute(color)};font-family:${escapeAttribute(fontFamily)};font-size:${safeFontSize}px;font-weight:bold;">${escapeText(label)}</center></v:roundrect><![endif]-->`) : null, jsx("a", {
|
|
1782
|
-
className,
|
|
1783
|
-
href,
|
|
1784
|
-
target: "_blank",
|
|
1785
|
-
rel: "noopener noreferrer",
|
|
1786
|
-
style: {
|
|
1787
|
-
backgroundColor,
|
|
1788
|
-
color,
|
|
1789
|
-
borderRadius: `${safeBorderRadius}px`,
|
|
1790
|
-
display: "inline-block",
|
|
1791
|
-
fontFamily,
|
|
1792
|
-
fontSize: `${safeFontSize}px`,
|
|
1793
|
-
fontWeight: "bold",
|
|
1794
|
-
lineHeight: safeHeight === void 0 ? "120%" : `${safeHeight - 2 * safeBorderWidth}px`,
|
|
1795
|
-
textAlign: "center",
|
|
1796
|
-
textDecoration: "none",
|
|
1797
|
-
...safeWidth === void 0 ? { padding: `0 ${safePaddingX}px` } : {
|
|
1798
|
-
width: `${safeWidth}px`,
|
|
1799
|
-
maxWidth: "100%"
|
|
1800
|
-
},
|
|
1801
|
-
...borderColor === void 0 ? {} : { border: `${safeBorderWidth}px solid ${borderColor}` },
|
|
1802
|
-
...fallback ? { msoHide: "all" } : {},
|
|
1803
|
-
...style
|
|
1804
|
-
},
|
|
1805
|
-
children
|
|
1806
|
-
})]
|
|
1807
|
-
}));
|
|
1808
|
-
};
|
|
1809
|
-
/** Vertical space that survives clients which collapse margins. */
|
|
1810
|
-
const Spacer = ({ height }) => presentation({ width: "100%" }, jsx("td", {
|
|
1811
|
-
height,
|
|
1812
|
-
style: {
|
|
1813
|
-
height: `${height}px`,
|
|
1814
|
-
lineHeight: `${height}px`,
|
|
1815
|
-
fontSize: "1px"
|
|
1816
|
-
},
|
|
1817
|
-
children: ""
|
|
1818
|
-
}));
|
|
1819
|
-
/** A horizontal rule drawn as a cell border, which every target client renders. */
|
|
1820
|
-
const Divider = ({ className, color = "#e5e7eb", thickness = 1, style }) => presentation({ width: "100%" }, jsx("td", {
|
|
1821
|
-
className,
|
|
1822
|
-
style: {
|
|
1823
|
-
borderTop: `${thickness}px solid ${color}`,
|
|
1824
|
-
fontSize: "1px",
|
|
1825
|
-
lineHeight: "1px",
|
|
1826
|
-
...style
|
|
1827
|
-
},
|
|
1828
|
-
children: ""
|
|
1829
|
-
}));
|
|
1830
|
-
/** An image with the width/height attributes Outlook sizes from before CSS applies. */
|
|
1831
|
-
const Image = ({ className, src, alt, width, height, align, style }) => jsx("img", {
|
|
1832
|
-
className,
|
|
1833
|
-
src,
|
|
1834
|
-
alt,
|
|
1835
|
-
border: 0,
|
|
1836
|
-
...width === void 0 ? {} : { width },
|
|
1837
|
-
...height === void 0 ? {} : { height },
|
|
1838
|
-
...align === void 0 ? {} : { align },
|
|
1839
|
-
style: {
|
|
1840
|
-
display: "block",
|
|
1841
|
-
outline: "none",
|
|
1842
|
-
border: "none",
|
|
1843
|
-
textDecoration: "none",
|
|
1844
|
-
maxWidth: "100%",
|
|
1845
|
-
...style
|
|
1846
|
-
}
|
|
1847
|
-
});
|
|
1848
|
-
/** A text link that opens outside the client and cannot reach back at the opener. */
|
|
1849
|
-
const Link = ({ className, href, target = "_blank", rel = "noopener noreferrer", style, children }) => jsx("a", {
|
|
1850
|
-
className,
|
|
1851
|
-
href,
|
|
1852
|
-
target,
|
|
1853
|
-
rel,
|
|
1854
|
-
style: {
|
|
1855
|
-
color: "#067df7",
|
|
1856
|
-
textDecoration: "none",
|
|
1857
|
-
...style
|
|
1858
|
-
},
|
|
1859
|
-
children
|
|
1860
|
-
});
|
|
1861
|
-
//#endregion
|
|
1862
|
-
export { renderIrSms as A, emailSelectionAncestry as C, renderIrPreview as D, emailSelections as E, isUrlAttribute as F, EMAIL_ELEMENTS as M, EMAIL_GLOBAL_ATTRIBUTES as N, IrRenderError as O, emailElement as P, emailDefinitionInstances as S, emailSelectionSiblings as T, kebab as _, Email as a, INSTANCE_PATH_ATTRIBUTE as b, PREHEADER_ATTRIBUTE as c, equalColumnWidth as d, preheaderNode as f, escapeText as g, escapeAttribute as h, Divider as i, renderIrWhatsApp as j, renderIr as k, Section as l, declarationValue as m, Column as n, Image as o, checkAttribute as p, Columns as r, Link as s, Button as t, Spacer as u, serializeEmailHtml as v, emailSelectionIndex as w, emailAuthoringTarget as x, serializeStyle as y };
|
|
1635
|
+
export { isUrlAttribute as C, samvaBrandPlugin as D, reservedBrandAttributes as E, BRAND_FOOTER_MARKER as O, emailElement as S, parseBrandSpecifier as T, renderIr as _, kebab as a, EMAIL_ELEMENTS as b, INSTANCE_PATH_ATTRIBUTE as c, emailSelectionAncestry as d, emailSelectionIndex as f, IrRenderError as g, renderIrPreview as h, escapeText as i, UNSUBSCRIBE_LINK as k, emailAuthoringTarget as l, emailSelections as m, declarationValue as n, serializeEmailHtml as o, emailSelectionSiblings as p, escapeAttribute as r, serializeStyle as s, checkAttribute as t, emailDefinitionInstances as u, renderIrSms as v, brandSpecifierPattern as w, EMAIL_GLOBAL_ATTRIBUTES as x, renderIrWhatsApp as y };
|
|
1863
1636
|
|
|
1864
|
-
//# sourceMappingURL=
|
|
1637
|
+
//# sourceMappingURL=html-DjPxwjLi.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as isObject } from "./guards-eV1qLbf2.js";
|
|
2
|
-
import {
|
|
2
|
+
import { a as kebab, n as declarationValue, r as escapeAttribute } from "./html-DjPxwjLi.js";
|
|
3
3
|
import { t as EmailNode } from "./jsx-runtime-CBlexoeR.js";
|
|
4
4
|
//#region src/email/font-stacks.ts
|
|
5
5
|
/** Generic families every email client understands as the last resort in a stack. */
|
|
@@ -580,4 +580,4 @@ const SML_IR_VERSION = 1;
|
|
|
580
580
|
//#endregion
|
|
581
581
|
export { downlevelMediaQuery as a, renderHeadCss as c, matchesSelector as i, resolveVariables as l, applyStylesheet as n, finalizeValue as o, classListOf as r, foldCalc as s, SML_IR_VERSION as t, unquote as u };
|
|
582
582
|
|
|
583
|
-
//# sourceMappingURL=ir-
|
|
583
|
+
//# sourceMappingURL=ir-De6pTH51.js.map
|