@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,24 +1,16 @@
|
|
|
1
1
|
import { n as isObject } from "./guards-eV1qLbf2.js";
|
|
2
2
|
import { n as inputSchema, r as jsonSchema } from "./input-schema-zjkgrXn2.js";
|
|
3
3
|
import { n as TEMPLATE_NAME_PATTERN, t as TEMPLATE_ID_PATTERN } from "./template-zLUjcwfF.js";
|
|
4
|
-
import {
|
|
5
|
-
import { M as EMAIL_ELEMENTS, P as emailElement, a as Email, d as equalColumnWidth, i as Divider, l as Section, n as Column, o as Image, p as checkAttribute, r as Columns, s as Link, t as Button, u as Spacer, y as serializeStyle } from "./components-BZCqegS8.js";
|
|
4
|
+
import { D as samvaBrandPlugin, E as reservedBrandAttributes, O as BRAND_FOOTER_MARKER, S as emailElement, T as parseBrandSpecifier, b as EMAIL_ELEMENTS, s as serializeStyle, t as checkAttribute } from "./html-DjPxwjLi.js";
|
|
6
5
|
import { FORMATTER_NAMES } from "./fmt.js";
|
|
7
6
|
import { i as drainEmailDiagnostics, t as EmailNode } from "./jsx-runtime-CBlexoeR.js";
|
|
8
|
-
import { n as
|
|
7
|
+
import { a as Email, d as equalColumnWidth, i as Divider, l as Section, n as Column, o as Image, r as Columns, s as Link, t as Button, u as Spacer } from "./components-Bl6DT21n.js";
|
|
8
|
+
import { n as applyStylesheet } from "./ir-De6pTH51.js";
|
|
9
9
|
import { i as lineColumn, t as buildLineMap } from "./diagnostic-model-DJtqbETd.js";
|
|
10
|
-
import {
|
|
10
|
+
import { l as isAssetPath, n as brandComponents } from "./brand-DTctcDmf.js";
|
|
11
11
|
import { parse } from "css-tree";
|
|
12
12
|
import { parse as parse$1 } from "@babel/parser";
|
|
13
13
|
//#region src/email/theme-imports.ts
|
|
14
|
-
/** The CSS and TSX specifier a template uses to reach its organization's brand. */
|
|
15
|
-
const BRAND_SPECIFIER = "samva:brand";
|
|
16
|
-
const BRAND_IMPORT = /^samva:brand(?:\/([a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?))?$/;
|
|
17
|
-
/** The brand a `samva:brand` specifier names: `undefined` slug means the default brand. */
|
|
18
|
-
const parseBrandSpecifier = (specifier) => {
|
|
19
|
-
const match = BRAND_IMPORT.exec(specifier);
|
|
20
|
-
return match === null ? void 0 : { slug: match[1] };
|
|
21
|
-
};
|
|
22
14
|
/** The directory part of a project path, with no trailing slash (`""` at the root). */
|
|
23
15
|
const projectDirectory = (path) => path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : "";
|
|
24
16
|
/** A relative or root-absolute stylesheet reference, resolved from a project directory. */
|
|
@@ -45,10 +37,12 @@ const importTarget = (node) => {
|
|
|
45
37
|
};
|
|
46
38
|
/**
|
|
47
39
|
* Walk the theme's `@import` graph. Project `.css` files resolve from the
|
|
48
|
-
* importing file's directory;
|
|
49
|
-
* recorded for the brand the build supplies. Anything else is an
|
|
40
|
+
* importing file's directory; the plugin's brand specifier, bare or with a
|
|
41
|
+
* slug, is recorded for the brand the build supplies. Anything else is an
|
|
42
|
+
* error.
|
|
50
43
|
*/
|
|
51
|
-
const resolveThemeImports = (themePath, themeCss, files) => {
|
|
44
|
+
const resolveThemeImports = (themePath, themeCss, files, brandPlugin) => {
|
|
45
|
+
const brand = JSON.stringify(brandPlugin.specifier);
|
|
52
46
|
const stylesheets = /* @__PURE__ */ new Map([[themePath, themeCss]]);
|
|
53
47
|
const brandSpecifiers = /* @__PURE__ */ new Set();
|
|
54
48
|
const diagnostics = [];
|
|
@@ -76,19 +70,19 @@ const resolveThemeImports = (themePath, themeCss, files) => {
|
|
|
76
70
|
});
|
|
77
71
|
const { target, extra } = importTarget(node);
|
|
78
72
|
if (target === void 0) {
|
|
79
|
-
refuse(`${path}: @import needs a quoted stylesheet path or
|
|
73
|
+
refuse(`${path}: @import needs a quoted stylesheet path or ${brand}.`);
|
|
80
74
|
continue;
|
|
81
75
|
}
|
|
82
76
|
if (extra) {
|
|
83
77
|
refuse(`${path}: @import ${JSON.stringify(target)} takes no layer, supports or media conditions; the import order is the layering.`);
|
|
84
78
|
continue;
|
|
85
79
|
}
|
|
86
|
-
if (parseBrandSpecifier(target) !== void 0) {
|
|
80
|
+
if (parseBrandSpecifier(target, brandPlugin) !== void 0) {
|
|
87
81
|
brandSpecifiers.add(target);
|
|
88
82
|
continue;
|
|
89
83
|
}
|
|
90
84
|
if (!/^\.{0,2}\//.test(target) || !/\.css$/i.test(target.replace(/[?#].*$/, ""))) {
|
|
91
|
-
refuse(`${path}: @import ${JSON.stringify(target)} is not a project stylesheet or
|
|
85
|
+
refuse(`${path}: @import ${JSON.stringify(target)} is not a project stylesheet or ${brand}. The compiler supplies Tailwind itself; import project .css files by relative path.`);
|
|
92
86
|
continue;
|
|
93
87
|
}
|
|
94
88
|
const resolved = resolveFromDirectory(projectDirectory(path), target.replace(/[?#].*$/, ""));
|
|
@@ -1093,7 +1087,7 @@ const columnsEach = (call, bindings) => {
|
|
|
1093
1087
|
/** Run a compiler primitive at compile time and lower what it returns. */
|
|
1094
1088
|
const runBuiltinComponent = (call) => {
|
|
1095
1089
|
const { lowerer, ctx, node, binding } = call;
|
|
1096
|
-
const specifier = binding.
|
|
1090
|
+
const specifier = binding.specifier;
|
|
1097
1091
|
if (binding.kind === "markup" && (specifier === "@samva/markup/sms" || specifier === "@samva/markup/whatsapp")) {
|
|
1098
1092
|
const lowered = call.channel?.(call);
|
|
1099
1093
|
if (lowered !== void 0) return lowered;
|
|
@@ -1104,17 +1098,17 @@ const runBuiltinComponent = (call) => {
|
|
|
1104
1098
|
const isEmail = binding.kind === "markup" && specifier === "@samva/markup/email";
|
|
1105
1099
|
const isBrand = binding.kind === "brand";
|
|
1106
1100
|
if (!(isEmail || isBrand)) {
|
|
1107
|
-
lowerer.error(ctx, node, "unknown-component", `<${call.name}> is not a component the profile can read.`,
|
|
1101
|
+
lowerer.error(ctx, node, "unknown-component", `<${call.name}> is not a component the profile can read.`, `Import components from @samva/markup/email, ${call.brandPlugin.specifier}, or the project.`);
|
|
1108
1102
|
return [];
|
|
1109
1103
|
}
|
|
1110
1104
|
let fn;
|
|
1111
1105
|
if (isEmail) fn = COMPONENTS[exported];
|
|
1112
1106
|
else {
|
|
1113
1107
|
if (call.brand === void 0) {
|
|
1114
|
-
lowerer.error(ctx, node, "brand-unavailable", `<${call.name}> comes from
|
|
1108
|
+
lowerer.error(ctx, node, "brand-unavailable", `<${call.name}> comes from ${binding.specifier}, and this build has no brand to resolve it.`, "Compile with a brand, or remove the brand components.");
|
|
1115
1109
|
return [];
|
|
1116
1110
|
}
|
|
1117
|
-
const brand = brandComponents(call.brand);
|
|
1111
|
+
const brand = brandComponents(call.brand, call.brandPlugin);
|
|
1118
1112
|
fn = exported === "BrandLogo" ? brand.BrandLogo : exported === "BrandFooter" ? brand.BrandFooter : void 0;
|
|
1119
1113
|
}
|
|
1120
1114
|
if (fn === void 0 || !Object.hasOwn(COMPONENTS, exported) && isEmail) {
|
|
@@ -1406,8 +1400,10 @@ var Lowerer = class {
|
|
|
1406
1400
|
options;
|
|
1407
1401
|
sources = [];
|
|
1408
1402
|
uniqueId = 0;
|
|
1403
|
+
reservedAttributes;
|
|
1409
1404
|
constructor(options) {
|
|
1410
1405
|
this.options = options;
|
|
1406
|
+
this.reservedAttributes = reservedBrandAttributes(options.brandPlugin);
|
|
1411
1407
|
}
|
|
1412
1408
|
get findings() {
|
|
1413
1409
|
return this.options.findings;
|
|
@@ -2129,8 +2125,8 @@ var Lowerer = class {
|
|
|
2129
2125
|
this.error(ctx, attribute, isHtml ? "dangerous-html" : name === "ref" ? "unsupported-attribute" : "event-handler", isHtml ? "`dangerouslySetInnerHTML` injects unescaped markup." : name === "ref" ? "A `ref` needs a running component." : `\`${name}\` is an event handler; email clients strip scripting and Samva refuses it.`, isHtml ? "Write the markup as elements." : "Remove it.");
|
|
2130
2126
|
continue;
|
|
2131
2127
|
}
|
|
2132
|
-
if (
|
|
2133
|
-
this.error(ctx, attribute, "reserved-attribute", `\`${name}\` is written only by BrandFooter from
|
|
2128
|
+
if (this.reservedAttributes.has(name)) {
|
|
2129
|
+
this.error(ctx, attribute, "reserved-attribute", `\`${name}\` is written only by BrandFooter from ${this.options.brandPlugin.specifier}.`, "Render <BrandFooter /> instead of marking an element as the footer.");
|
|
2134
2130
|
continue;
|
|
2135
2131
|
}
|
|
2136
2132
|
if (name === "className" || name === "class") {
|
|
@@ -2154,7 +2150,7 @@ var Lowerer = class {
|
|
|
2154
2150
|
}
|
|
2155
2151
|
if (value === void 0) continue;
|
|
2156
2152
|
if (value === false || value === null) continue;
|
|
2157
|
-
if (value ===
|
|
2153
|
+
if (value === this.options.brandPlugin.unsubscribeUrlPlaceholder) {
|
|
2158
2154
|
this.error(ctx, attribute, "reserved-url", "That URL is the unsubscribe placeholder only BrandFooter can write.", "Render <BrandFooter /> instead.");
|
|
2159
2155
|
continue;
|
|
2160
2156
|
}
|
|
@@ -2301,6 +2297,7 @@ var Lowerer = class {
|
|
|
2301
2297
|
name,
|
|
2302
2298
|
binding: imported,
|
|
2303
2299
|
brand: this.options.brand,
|
|
2300
|
+
brandPlugin: this.options.brandPlugin,
|
|
2304
2301
|
channel: this.options.channel
|
|
2305
2302
|
});
|
|
2306
2303
|
let fn;
|
|
@@ -3342,7 +3339,7 @@ const lowerProject = (options, project, findings, inputs) => {
|
|
|
3342
3339
|
staticContext,
|
|
3343
3340
|
assetUrl: urlFor,
|
|
3344
3341
|
brand: options.brand,
|
|
3345
|
-
|
|
3342
|
+
brandPlugin: options.brandPlugin ?? samvaBrandPlugin,
|
|
3346
3343
|
channel: channelLowering
|
|
3347
3344
|
});
|
|
3348
3345
|
const rootContext = (target) => ({
|
|
@@ -3546,7 +3543,7 @@ const resolveSpecifier = (from, specifier, files) => {
|
|
|
3546
3543
|
const isScriptPath = (path) => /\.[cm]?[jt]sx?$/.test(path);
|
|
3547
3544
|
const declaredNames = (pattern) => pattern.type === "Identifier" ? [text(pattern, "name") ?? ""] : [];
|
|
3548
3545
|
/** Load the entry and everything it imports from the project. */
|
|
3549
|
-
const loadProject = (files, entryPath, findings, loadOptions
|
|
3546
|
+
const loadProject = (files, entryPath, findings, loadOptions) => {
|
|
3550
3547
|
const modules = /* @__PURE__ */ new Map();
|
|
3551
3548
|
const unresolved = /* @__PURE__ */ new Set();
|
|
3552
3549
|
const loading = /* @__PURE__ */ new Set();
|
|
@@ -3631,7 +3628,7 @@ const loadProject = (files, entryPath, findings, loadOptions = {}) => {
|
|
|
3631
3628
|
}
|
|
3632
3629
|
break;
|
|
3633
3630
|
}
|
|
3634
|
-
if (
|
|
3631
|
+
if (parseBrandSpecifier(specifier, loadOptions.brandPlugin) !== void 0) {
|
|
3635
3632
|
for (const item of specifiers) if (text(item, "importKind") !== "type") imports.set(local(item), {
|
|
3636
3633
|
kind: "brand",
|
|
3637
3634
|
specifier,
|
|
@@ -3764,13 +3761,17 @@ const asFiles = (files, entry) => typeof files === "string" ? { [entry]: files }
|
|
|
3764
3761
|
* and the entry's path. Imports of files that are absent are trusted to exist, and the brand,
|
|
3765
3762
|
* Tailwind and theme are not resolved, so the result holds only findings the source itself owns.
|
|
3766
3763
|
*/
|
|
3767
|
-
const checkStaticProfile = (files, entry = DEFAULT_ENTRY) => {
|
|
3764
|
+
const checkStaticProfile = (files, entry = DEFAULT_ENTRY, brandPlugin = samvaBrandPlugin) => {
|
|
3768
3765
|
const findings = new Findings();
|
|
3769
|
-
const project = loadProject(asFiles(files, entry), entry, findings, {
|
|
3766
|
+
const project = loadProject(asFiles(files, entry), entry, findings, {
|
|
3767
|
+
brandPlugin,
|
|
3768
|
+
lenient: true
|
|
3769
|
+
});
|
|
3770
3770
|
if (project === void 0) return findings.items;
|
|
3771
3771
|
lowerProject({
|
|
3772
3772
|
entry,
|
|
3773
|
-
brand: void 0
|
|
3773
|
+
brand: void 0,
|
|
3774
|
+
brandPlugin
|
|
3774
3775
|
}, project, findings, {
|
|
3775
3776
|
assets: [],
|
|
3776
3777
|
stylesheets: /* @__PURE__ */ new Map(),
|
|
@@ -3798,6 +3799,6 @@ const introducedProfileErrors = (before, after) => {
|
|
|
3798
3799
|
return added;
|
|
3799
3800
|
};
|
|
3800
3801
|
//#endregion
|
|
3801
|
-
export { lowerProject as a, WHATSAPP_HEADER_TYPES as c,
|
|
3802
|
+
export { lowerProject as a, WHATSAPP_HEADER_TYPES as c, resolveFromDirectory as d, resolveThemeImports as f, completed as i, Findings as l, introducedProfileErrors as n, SMS_CATEGORIES as o, loadProject as r, WHATSAPP_CATEGORIES as s, checkStaticProfile as t, projectDirectory as u };
|
|
3802
3803
|
|
|
3803
|
-
//# sourceMappingURL=profile-
|
|
3804
|
+
//# sourceMappingURL=profile-PUfi3GPH.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as emailElement, o as serializeEmailHtml } from "./html-DjPxwjLi.js";
|
|
2
2
|
import { i as drainEmailDiagnostics, t as EmailNode } from "./jsx-runtime-CBlexoeR.js";
|
|
3
|
-
import {
|
|
3
|
+
import { f as preheaderNode } from "./components-Bl6DT21n.js";
|
|
4
|
+
import { n as applyStylesheet } from "./ir-De6pTH51.js";
|
|
4
5
|
import { r as hasBlockingDiagnostic, t as EmailCompileError } from "./diagnostics-CnKFefKz.js";
|
|
5
6
|
//#region src/email/text.ts
|
|
6
7
|
const PARAGRAPH = "\n\n";
|
|
@@ -254,4 +255,4 @@ const renderEmailContent = (stylesheet, render, options = {}) => {
|
|
|
254
255
|
//#endregion
|
|
255
256
|
export { deriveEmailText as i, compileEmail as n, renderEmailContent as r, EMAIL_DOCTYPE as t };
|
|
256
257
|
|
|
257
|
-
//# sourceMappingURL=render-
|
|
258
|
+
//# sourceMappingURL=render-D2ht57ZZ.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as BrandPlugin } from "./brand-plugin-CHhcei-j.js";
|
|
1
2
|
import { r as JsxSourceLocation } from "./source-locations-C8wCsiAy.js";
|
|
2
3
|
import { r as EmailDiagnostic } from "./diagnostics-B-Gym34R.js";
|
|
3
4
|
import { r as EmailNode } from "./jsx-runtime-ylIOnaSf.js";
|
|
@@ -207,6 +208,12 @@ interface RenderOptions {
|
|
|
207
208
|
readonly timeZone?: string;
|
|
208
209
|
/** Also return where each element landed in the HTML, for mapping findings back to source. */
|
|
209
210
|
readonly positions?: boolean;
|
|
211
|
+
/**
|
|
212
|
+
* The brand plugin the template compiled against: its unsubscribe placeholder gets the
|
|
213
|
+
* no-track marker, and only the elements `BrandFooter` wrote may carry its footer markers.
|
|
214
|
+
* `samvaBrandPlugin` when omitted.
|
|
215
|
+
*/
|
|
216
|
+
readonly brandPlugin?: BrandPlugin;
|
|
210
217
|
}
|
|
211
218
|
/** One rendered element's offset range in the HTML, and the source it was lowered from. */
|
|
212
219
|
interface RenderedPosition {
|
|
@@ -486,6 +493,8 @@ interface SerializeEmailOptions {
|
|
|
486
493
|
* A delivered message never carries it.
|
|
487
494
|
*/
|
|
488
495
|
readonly instancePaths?: boolean | undefined;
|
|
496
|
+
/** The brand plugin whose footer markers and unsubscribe link `BrandFooter` writes. */
|
|
497
|
+
readonly brandPlugin?: BrandPlugin | undefined;
|
|
489
498
|
}
|
|
490
499
|
/** Lower the derived tree to email HTML, reporting every unsupported construct with its origin. */
|
|
491
500
|
declare const serializeEmailHtml: (node: EmailNode, options?: SerializeEmailOptions) => EmailHtmlOutput;
|
|
@@ -536,4 +545,4 @@ interface RenderedEmail {
|
|
|
536
545
|
declare const renderEmailContent: (stylesheet: Stylesheet | undefined, render: () => EmailContent, options?: SerializeEmailOptions) => RenderedEmail;
|
|
537
546
|
//#endregion
|
|
538
547
|
export { IrWhatsApp as $, emailAuthoringTarget as A, IrElement as B, Compound as C, EmittedPosition as D, EmailElementSelection as E, emailSelections as F, IrPredicate as G, IrFormatter as H, renderIrPreview as I, IrRawPart as J, IrPreheader as K, IrArithmetic as L, emailSelectionAncestry as M, emailSelectionIndex as N, INSTANCE_PATH_ATTRIBUTE as O, emailSelectionSiblings as P, IrValue as Q, IrComparison as R, CompiledSelector as S, matchesSelector as T, IrIf as U, IrEmail as V, IrNode as W, IrSource as X, IrSms as Y, IrStyle as Z, Specificity as _, renderEmailContent as a, RenderedPosition as at, parseStylesheet as b, checkAttribute as c, TemplateIr as ct, escapeText as d, IrWhatsAppButton as et, kebab as f, CssRule as g, CssDeclaration as h, compileEmail as i, RenderedIrWhatsApp as it, emailDefinitionInstances as j, IrPreview as k, declarationValue as l, serializeStyle as m, EmailCompileOutput as n, RenderOptions as nt, EmailHtmlOutput as o, RenderedWhatsAppButton as ot, serializeEmailHtml as p, IrRaw as q, RenderedEmail as r, RenderedIrEmail as rt, SerializeEmailOptions as s, SML_IR_VERSION as st, EMAIL_DOCTYPE as t, IrWhatsAppButtonNode as tt, escapeAttribute as u, Stylesheet as v, classListOf as w, Ancestry as x, compileSelector as y, IrEach as z };
|
|
539
|
-
//# sourceMappingURL=render-
|
|
548
|
+
//# sourceMappingURL=render-DQKz4Aco.d.ts.map
|
package/dist/render.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { C as isUrlAttribute, S as emailElement, _ as EmailGlobalProps, b as EmailStyle, g as EmailElementSpec, h as EmailAttributeSpec, m as EMAIL_GLOBAL_ATTRIBUTES, p as EMAIL_ELEMENTS, r as EmailNode, v as EmailIntrinsicElements, x as EmailTag, y as EmailLayout } from "./jsx-runtime-ylIOnaSf.js";
|
|
2
|
-
import { $ as IrWhatsApp, A as emailAuthoringTarget, B as IrElement, D as EmittedPosition, E as EmailElementSelection, F as emailSelections, G as IrPredicate, H as IrFormatter, I as renderIrPreview, J as IrRawPart, K as IrPreheader, L as IrArithmetic, M as emailSelectionAncestry, N as emailSelectionIndex, O as INSTANCE_PATH_ATTRIBUTE, P as emailSelectionSiblings, Q as IrValue, R as IrComparison, U as IrIf, V as IrEmail, W as IrNode, X as IrSource, Y as IrSms, Z as IrStyle, a as renderEmailContent, at as RenderedPosition, c as checkAttribute, ct as TemplateIr, d as escapeText, et as IrWhatsAppButton, f as kebab, i as compileEmail, it as RenderedIrWhatsApp, j as emailDefinitionInstances, k as IrPreview, l as declarationValue, m as serializeStyle, n as EmailCompileOutput, nt as RenderOptions, o as EmailHtmlOutput, ot as RenderedWhatsAppButton, p as serializeEmailHtml, q as IrRaw, r as RenderedEmail, rt as RenderedIrEmail, s as SerializeEmailOptions, st as SML_IR_VERSION, t as EMAIL_DOCTYPE, tt as IrWhatsAppButtonNode, u as escapeAttribute, z as IrEach } from "./render-
|
|
2
|
+
import { $ as IrWhatsApp, A as emailAuthoringTarget, B as IrElement, D as EmittedPosition, E as EmailElementSelection, F as emailSelections, G as IrPredicate, H as IrFormatter, I as renderIrPreview, J as IrRawPart, K as IrPreheader, L as IrArithmetic, M as emailSelectionAncestry, N as emailSelectionIndex, O as INSTANCE_PATH_ATTRIBUTE, P as emailSelectionSiblings, Q as IrValue, R as IrComparison, U as IrIf, V as IrEmail, W as IrNode, X as IrSource, Y as IrSms, Z as IrStyle, a as renderEmailContent, at as RenderedPosition, c as checkAttribute, ct as TemplateIr, d as escapeText, et as IrWhatsAppButton, f as kebab, i as compileEmail, it as RenderedIrWhatsApp, j as emailDefinitionInstances, k as IrPreview, l as declarationValue, m as serializeStyle, n as EmailCompileOutput, nt as RenderOptions, o as EmailHtmlOutput, ot as RenderedWhatsAppButton, p as serializeEmailHtml, q as IrRaw, r as RenderedEmail, rt as RenderedIrEmail, s as SerializeEmailOptions, st as SML_IR_VERSION, t as EMAIL_DOCTYPE, tt as IrWhatsAppButtonNode, u as escapeAttribute, z as IrEach } from "./render-DQKz4Aco.js";
|
|
3
3
|
//#region src/render-ir.d.ts
|
|
4
4
|
interface IrRenderDiagnostic {
|
|
5
5
|
readonly code: string;
|
package/dist/render.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as SML_IR_VERSION } from "./ir-
|
|
3
|
-
import { i as deriveEmailText, n as compileEmail, r as renderEmailContent, t as EMAIL_DOCTYPE } from "./render-
|
|
1
|
+
import { C as isUrlAttribute, S as emailElement, _ as renderIr, a as kebab, b as EMAIL_ELEMENTS, c as INSTANCE_PATH_ATTRIBUTE, d as emailSelectionAncestry, f as emailSelectionIndex, g as IrRenderError, h as renderIrPreview, i as escapeText, l as emailAuthoringTarget, m as emailSelections, n as declarationValue, o as serializeEmailHtml, p as emailSelectionSiblings, r as escapeAttribute, s as serializeStyle, t as checkAttribute, u as emailDefinitionInstances, v as renderIrSms, x as EMAIL_GLOBAL_ATTRIBUTES, y as renderIrWhatsApp } from "./html-DjPxwjLi.js";
|
|
2
|
+
import { t as SML_IR_VERSION } from "./ir-De6pTH51.js";
|
|
3
|
+
import { i as deriveEmailText, n as compileEmail, r as renderEmailContent, t as EMAIL_DOCTYPE } from "./render-D2ht57ZZ.js";
|
|
4
4
|
//#region src/sms-segments.ts
|
|
5
5
|
const GSM7_EXTENDED = /* @__PURE__ */ new Set([
|
|
6
6
|
"[",
|
|
@@ -575,7 +575,7 @@ const brandThemeVariables = (brand) => {
|
|
|
575
575
|
...defaults.dark === void 0 ? {} : { [darkVariableName(BRAND_FOREGROUND_VARIABLE)]: defaults.dark }
|
|
576
576
|
};
|
|
577
577
|
};
|
|
578
|
-
/** The stylesheet Tailwind compiles for
|
|
578
|
+
/** The stylesheet Tailwind compiles for the theme's brand import: `brandThemeVariables` and the font faces. */
|
|
579
579
|
const brandTailwindCss = (brand) => printBrandCss({
|
|
580
580
|
fontFaces: brand.fontFaces,
|
|
581
581
|
variables: brandThemeVariables(brand),
|
|
@@ -584,4 +584,4 @@ const brandTailwindCss = (brand) => printBrandCss({
|
|
|
584
584
|
//#endregion
|
|
585
585
|
export { brandThemeVariables as a, parseBrandCss as c, formatColor as d, parseColor as f, brandTailwindCss as i, printBrandCss as l, BRAND_FOREGROUND_VARIABLE as n, contrastForeground as o, brandForegroundDefaults as r, darkVariableName as s, BRAND_COLOR_VARIABLE as t, downlevelColors as u };
|
|
586
586
|
|
|
587
|
-
//# sourceMappingURL=theme-css-
|
|
587
|
+
//# sourceMappingURL=theme-css-CEUrDpqq.js.map
|
package/generated/contract.json
CHANGED
|
@@ -1304,7 +1304,7 @@
|
|
|
1304
1304
|
},
|
|
1305
1305
|
{
|
|
1306
1306
|
"code": "brand-unavailable",
|
|
1307
|
-
"summary": "The build has no brand to resolve a
|
|
1307
|
+
"summary": "The build has no brand to resolve a brand import.",
|
|
1308
1308
|
"fix": "Create or select the brand the import names."
|
|
1309
1309
|
},
|
|
1310
1310
|
{
|
|
@@ -1598,7 +1598,7 @@
|
|
|
1598
1598
|
{
|
|
1599
1599
|
"code": "reserved-attribute",
|
|
1600
1600
|
"summary": "An attribute is written only by a brand component.",
|
|
1601
|
-
"fix": "Render BrandFooter from
|
|
1601
|
+
"fix": "Render BrandFooter from the brand import instead of marking an element as the footer."
|
|
1602
1602
|
},
|
|
1603
1603
|
{
|
|
1604
1604
|
"code": "reserved-character",
|
|
@@ -1655,7 +1655,7 @@
|
|
|
1655
1655
|
{
|
|
1656
1656
|
"code": "unknown-component",
|
|
1657
1657
|
"summary": "A component the profile cannot read.",
|
|
1658
|
-
"fix": "Import components from @samva/markup/email,
|
|
1658
|
+
"fix": "Import components from @samva/markup/email, the brand import, or the project."
|
|
1659
1659
|
},
|
|
1660
1660
|
{
|
|
1661
1661
|
"code": "unknown-field",
|
package/generated/reference.md
CHANGED
|
@@ -249,7 +249,7 @@ Global attributes: `id`, `title`, `lang`, `dir`, `role`.
|
|
|
249
249
|
| Code | Meaning | Fix |
|
|
250
250
|
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
251
251
|
| `brand-import-conflict` | The project imports two different brands. | Import one brand specifier everywhere. |
|
|
252
|
-
| `brand-unavailable` | The build has no brand to resolve a
|
|
252
|
+
| `brand-unavailable` | The build has no brand to resolve a brand import. | Create or select the brand the import names. |
|
|
253
253
|
| `button-dynamic-label` | A Button label is read at render time, so classic Outlook cannot size it. | Give the button a width: <Button width={200}>. |
|
|
254
254
|
| `button-outlook-fallback` | Classic Outlook needs a height and a plain string label to draw the button. | Give <Button> a height and a string label. |
|
|
255
255
|
| `caniemail/unknown-coverage` | A feature has no client-support data. | Verify it in the target clients. |
|
|
@@ -306,7 +306,7 @@ Global attributes: `id`, `title`, `lang`, `dir`, `role`.
|
|
|
306
306
|
| `not-a-function` | A channel field is not a function of the input. | Write (input) => ... in place or declare the function in the project. |
|
|
307
307
|
| `preheader-without-shell` | A preheader is set but the body has no <Email> shell to hold it. | Wrap the body in <Email>. |
|
|
308
308
|
| `recursive-partial` | A partial renders itself; partials are inlined at compile time. | Flatten the recursion, or repeat the structure with .map. |
|
|
309
|
-
| `reserved-attribute` | An attribute is written only by a brand component. | Render BrandFooter from
|
|
309
|
+
| `reserved-attribute` | An attribute is written only by a brand component. | Render BrandFooter from the brand import instead of marking an element as the footer. |
|
|
310
310
|
| `reserved-character` | Text contains a private-use character the compiler reserves. | Remove the characters U+E000 to U+E003. |
|
|
311
311
|
| `reserved-url` | A URL is the unsubscribe placeholder only BrandFooter can write. | Render <BrandFooter /> instead. |
|
|
312
312
|
| `schema-not-static` | The schema cannot be read without running code. | Write the schema as a literal jsonSchema<Input>({ type: 'object', ... }). |
|
|
@@ -315,7 +315,7 @@ Global attributes: `id`, `title`, `lang`, `dir`, `role`.
|
|
|
315
315
|
| `tailwind-dynamic-class` | A class list is assembled at run time. | Write the classes literally. |
|
|
316
316
|
| `too-many-class-variants` | A class list chooses between more literal lists than the compiler resolves. | Split the choice into fewer conditions, or move part of it to a partial. |
|
|
317
317
|
| `unguarded-optional` | An optional field is read outside a guard, so a send without it would render a blank. | Guard it with {input.field && ...}, or make the field required. |
|
|
318
|
-
| `unknown-component` | A component the profile cannot read. | Import components from @samva/markup/email,
|
|
318
|
+
| `unknown-component` | A component the profile cannot read. | Import components from @samva/markup/email, the brand import, or the project. |
|
|
319
319
|
| `unknown-field` | A binding names a field the schema does not declare. | Add the field to the schema, or bind a field it declares (the message suggests the nearest). |
|
|
320
320
|
| `unknown-formatter` | fmt.<name> is not a formatter. | Use fmt.money, number, date, time, plural or list. |
|
|
321
321
|
| `unmatched-class` | A class produced no styles. | Use a class the theme or Tailwind defines, or write the style inline. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@samva/markup",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Type-safe authoring and compilation primitives for Samva templates",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -121,12 +121,12 @@
|
|
|
121
121
|
},
|
|
122
122
|
"devDependencies": {
|
|
123
123
|
"@css-inline/css-inline-wasm": "0.15.0",
|
|
124
|
-
"@effect/vitest": "4.0.0
|
|
124
|
+
"@effect/vitest": "4.0.0",
|
|
125
125
|
"@types/css-tree": "3.2.0",
|
|
126
|
-
"effect": "4.0.0
|
|
126
|
+
"effect": "4.0.0",
|
|
127
127
|
"tsdown": "^0.22.13",
|
|
128
128
|
"typescript": "^7.0.2",
|
|
129
|
-
"vitest": "^
|
|
129
|
+
"vitest": "^5.0.3",
|
|
130
130
|
"zod": "4.6.5"
|
|
131
131
|
}
|
|
132
132
|
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
//#region src/internal/unsubscribe-link.ts
|
|
2
|
-
/**
|
|
3
|
-
* Marks the one link the serializer lets carry the unsubscribe placeholder as
|
|
4
|
-
* its href. Only `BrandFooter` sets it; nothing public exports it, so no
|
|
5
|
-
* author-written link can claim the exemption.
|
|
6
|
-
*/
|
|
7
|
-
const UNSUBSCRIBE_LINK = Symbol("samva.unsubscribe-link");
|
|
8
|
-
/**
|
|
9
|
-
* The per-recipient unsubscribe URL is not known at render: the send path
|
|
10
|
-
* substitutes each delivery's link for this placeholder, or removes the
|
|
11
|
-
* unsubscribe row when the send resolved no unsubscribe group.
|
|
12
|
-
*/
|
|
13
|
-
const UNSUBSCRIBE_URL_PLACEHOLDER = "%samva:unsubscribe-url%";
|
|
14
|
-
/**
|
|
15
|
-
* Marks the elements `BrandFooter` writes the send path's footer markers on.
|
|
16
|
-
* The serializer emits `data-samva-footer` and `data-samva-unsubscribe` only
|
|
17
|
-
* for an element carrying it, so an author cannot claim a footer the send
|
|
18
|
-
* path would then not append.
|
|
19
|
-
*/
|
|
20
|
-
const BRAND_FOOTER_MARKER = Symbol("samva.brand-footer-marker");
|
|
21
|
-
/** The attributes only `BrandFooter` writes. */
|
|
22
|
-
const RESERVED_FOOTER_ATTRIBUTES = /* @__PURE__ */ new Set(["data-samva-footer", "data-samva-unsubscribe"]);
|
|
23
|
-
//#endregion
|
|
24
|
-
export { UNSUBSCRIBE_URL_PLACEHOLDER as i, RESERVED_FOOTER_ATTRIBUTES as n, UNSUBSCRIBE_LINK as r, BRAND_FOOTER_MARKER as t };
|
|
25
|
-
|
|
26
|
-
//# sourceMappingURL=unsubscribe-link-CoesnQDU.js.map
|