@samva/markup 0.16.0 → 0.17.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 +42 -40
- package/CHANGELOG.md +72 -0
- package/README.md +10 -9
- package/dist/{brand-jFV6YtSV.d.ts → assets-CS5Xa_mr.d.ts} +41 -3
- package/dist/{brand-Ci05p37c.js → brand-CIiEqkU7.js} +64 -3
- package/dist/{brand-plugin-CHhcei-j.d.ts → brand-plugin-Dj9SMpfS.d.ts} +3 -1
- package/dist/brand.d.ts +2 -3
- package/dist/brand.js +3 -4
- package/dist/compatibility.d.ts +76 -0
- package/dist/compatibility.js +807 -0
- package/dist/compiled-template-Bc4Dq9zy.d.ts +268 -0
- package/dist/compiler.d.ts +59 -193
- package/dist/compiler.js +473 -775
- package/dist/{components-3XHjYD3i.js → components-C5850-Qg.js} +37 -29
- package/dist/css-B_aVKrDo.js +436 -0
- package/dist/css-color-DLM12Osu.js +315 -0
- package/dist/{diagnostic-codes-8XDLR9ef.js → diagnostic-codes-DZKb0Xcg.js} +40 -1
- package/dist/{diagnostics-CUYlQ_6x.d.ts → diagnostics-D4vSLQrO.d.ts} +1 -10
- package/dist/diagnostics.d.ts +6 -2
- package/dist/diagnostics.js +3 -4
- package/dist/edit.d.ts +10 -9
- package/dist/edit.js +12 -11
- package/dist/email/jsx-dev-runtime.d.ts +1 -1
- package/dist/email/jsx-dev-runtime.js +1 -1
- package/dist/email/jsx-runtime.d.ts +2 -2
- package/dist/email/jsx-runtime.js +2 -2
- package/dist/email.d.ts +1 -1
- package/dist/email.js +1 -1
- package/dist/html-DJUK2YJF.js +483 -0
- package/dist/index.d.ts +1 -1
- package/dist/{jsx-runtime-DC7xG0aA.d.ts → jsx-runtime-DL8NVF1f.d.ts} +12 -6
- package/dist/{jsx-runtime-CBlexoeR.js → jsx-runtime-qL19u3Fy.js} +17 -10
- package/dist/preview-rNoJBEIY.d.ts +104 -0
- package/dist/{profile-BsY4dkan.js → profile-JTvgCD6X.js} +714 -29
- package/dist/{html-BvRFepeO.js → render-template-DwPDm3Hq.js} +155 -572
- package/dist/render.d.ts +40 -28
- package/dist/render.js +3 -47
- package/dist/selectors-CYWCoxZr.js +157 -0
- package/dist/sms.d.ts +1 -1
- package/dist/sms.js +1 -1
- package/dist/{template-B-UDXK8a.d.ts → template-DmNcoH4Q.d.ts} +2 -9
- package/dist/{theme-css-s9QwD5Ko.js → theme-css-P9F5S7Ic.js} +3 -316
- package/dist/whatsapp.d.ts +1 -1
- package/dist/whatsapp.js +1 -1
- package/generated/contract.json +7 -2
- package/generated/reference.md +3 -2
- package/package.json +5 -5
- package/skills/sml/SKILL.md +2 -2
- package/dist/assets-BSpgZOSR.js +0 -61
- package/dist/assets-BSuNT9gr.d.ts +0 -39
- package/dist/brand-footer-ClRtWnIc.d.ts +0 -40
- package/dist/brand-footer-DXVGsKE5.js +0 -746
- package/dist/brand-sample.d.ts +0 -4
- package/dist/brand-sample.js +0 -2
- package/dist/cascade-DoLWZ66g.js +0 -701
- package/dist/diagnostic-model-DJtqbETd.js +0 -36
- package/dist/ir-KJLFW08u.js +0 -5
- package/dist/render-CVnXZMyK.js +0 -258
- package/dist/render-CuZej25A.d.ts +0 -564
- package/dist/theme-imports-u_P0ygvQ.js +0 -102
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
//#region src/diagnostic-model.ts
|
|
2
|
-
/** Newline-offset line map. Build once per source, query with `lineColumn`. */
|
|
3
|
-
const buildLineMap = (source) => {
|
|
4
|
-
const lineStarts = [0];
|
|
5
|
-
for (let index = 0; index < source.length; index++) {
|
|
6
|
-
const code = source.charCodeAt(index);
|
|
7
|
-
if (code === 10 || code === 13 && source.charCodeAt(index + 1) !== 10 || code === 8232 || code === 8233) lineStarts.push(index + 1);
|
|
8
|
-
}
|
|
9
|
-
return lineStarts;
|
|
10
|
-
};
|
|
11
|
-
/** 1-based line/column for an absolute offset, via binary search. */
|
|
12
|
-
const lineColumn = (lineMap, offset) => {
|
|
13
|
-
let low = 0;
|
|
14
|
-
let high = lineMap.length - 1;
|
|
15
|
-
while (low < high) {
|
|
16
|
-
const middle = low + high + 1 >> 1;
|
|
17
|
-
if (lineMap[middle] <= offset) low = middle;
|
|
18
|
-
else high = middle - 1;
|
|
19
|
-
}
|
|
20
|
-
return {
|
|
21
|
-
line: low + 1,
|
|
22
|
-
column: offset - lineMap[low] + 1
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
/** Build one formatter for a batch of diagnostics against the same source. */
|
|
26
|
-
const createDiagnosticFormatter = (source) => {
|
|
27
|
-
const lineMap = buildLineMap(source);
|
|
28
|
-
return (diagnostic) => {
|
|
29
|
-
const { line, column } = lineColumn(lineMap, diagnostic.span.start);
|
|
30
|
-
return `line ${line}:${column} ${diagnostic.message}`;
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
/** "line 12:8 unknown class ..." — the shape agents iterate on. */
|
|
34
|
-
const formatDiagnostic = (source, diagnostic) => createDiagnosticFormatter(source)(diagnostic);
|
|
35
|
-
//#endregion
|
|
36
|
-
export { lineColumn as i, createDiagnosticFormatter as n, formatDiagnostic as r, buildLineMap as t };
|
package/dist/ir-KJLFW08u.js
DELETED
package/dist/render-CVnXZMyK.js
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import { S as emailElement, o as serializeEmailHtml } from "./html-BvRFepeO.js";
|
|
2
|
-
import { i as drainEmailDiagnostics, t as EmailNode } from "./jsx-runtime-CBlexoeR.js";
|
|
3
|
-
import { f as preheaderNode } from "./components-3XHjYD3i.js";
|
|
4
|
-
import { n as applyStylesheet, r as renderDarkRules } from "./cascade-DoLWZ66g.js";
|
|
5
|
-
import { r as hasBlockingDiagnostic, t as EmailCompileError } from "./diagnostics-C0Tr8-eQ.js";
|
|
6
|
-
//#region src/email/text.ts
|
|
7
|
-
const PARAGRAPH = "\n\n";
|
|
8
|
-
const LINE = "\n";
|
|
9
|
-
const INVISIBLE = /\u200b|\u200c|\u200d|\u200e|\u200f|\u2060|\ufeff/g;
|
|
10
|
-
const isLayoutTable = (props) => props.role === "presentation" || props.role === "none";
|
|
11
|
-
/**
|
|
12
|
-
* A data-table cell that reads as nothing and holds only imagery with empty alt text carries no
|
|
13
|
-
* column of its own; an empty cell without such an image is a positional placeholder.
|
|
14
|
-
*/
|
|
15
|
-
const isDecorativeCell = (cell, context) => {
|
|
16
|
-
const images = collect(cell.children, (tag) => tag === "img", () => true);
|
|
17
|
-
return images.length > 0 && images.every((image) => image.props.alt === "") && childText(cell, context).trim() === "";
|
|
18
|
-
};
|
|
19
|
-
/** Wrap a block's content in separators; empty blocks contribute nothing. */
|
|
20
|
-
const block = (inner, separator) => {
|
|
21
|
-
const trimmed = inner.replace(/^\s+/, "").replace(/\s+$/, "");
|
|
22
|
-
return trimmed === "" ? "" : `${separator}${trimmed}${separator}`;
|
|
23
|
-
};
|
|
24
|
-
const collect = (nodes, keep, descend) => nodes.flatMap((child) => {
|
|
25
|
-
if (child.type === "Fragment") return collect(child.children, keep, descend);
|
|
26
|
-
if (child.type !== "Element") return [];
|
|
27
|
-
if (keep(child.tag)) return [child];
|
|
28
|
-
return descend(child.tag) ? collect(child.children, keep, descend) : [];
|
|
29
|
-
});
|
|
30
|
-
const TABLE_SECTIONS = /* @__PURE__ */ new Set([
|
|
31
|
-
"thead",
|
|
32
|
-
"tbody",
|
|
33
|
-
"tfoot"
|
|
34
|
-
]);
|
|
35
|
-
const childText = (node, context) => node.children.map((child) => derive(child, context)).join("");
|
|
36
|
-
const derive = (node, context) => {
|
|
37
|
-
switch (node.type) {
|
|
38
|
-
case "Text": return context.preformatted ? node.value : node.value.replace(INVISIBLE, "").replace(/[^\S\n]+/g, " ");
|
|
39
|
-
case "Raw": return node.text;
|
|
40
|
-
case "Fragment": return node.children.map((child) => derive(child, context)).join("");
|
|
41
|
-
case "Element": {
|
|
42
|
-
const spec = emailElement(node.tag);
|
|
43
|
-
if (spec === void 0) return childText(node, context);
|
|
44
|
-
switch (spec.layout) {
|
|
45
|
-
case "metadata": return "";
|
|
46
|
-
case "document":
|
|
47
|
-
case "inline":
|
|
48
|
-
case "list-item": return childText(node, context);
|
|
49
|
-
case "break": return LINE;
|
|
50
|
-
case "rule": return `${PARAGRAPH}---${PARAGRAPH}`;
|
|
51
|
-
case "image": {
|
|
52
|
-
const alt = typeof node.props.alt === "string" ? node.props.alt.trim() : "";
|
|
53
|
-
return alt === "" ? "" : `[${alt}]`;
|
|
54
|
-
}
|
|
55
|
-
case "link": {
|
|
56
|
-
const label = childText(node, context).replace(/\s+/g, " ").trim();
|
|
57
|
-
const href = typeof node.props.href === "string" ? node.props.href : "";
|
|
58
|
-
if (href === "" || href === label) return label;
|
|
59
|
-
return label === "" ? href : `${label} (${href})`;
|
|
60
|
-
}
|
|
61
|
-
case "list": {
|
|
62
|
-
const indent = " ".repeat(context.listDepth);
|
|
63
|
-
const start = Number(node.props.start ?? 1);
|
|
64
|
-
const first = Number.isFinite(start) ? start : 1;
|
|
65
|
-
const nested = {
|
|
66
|
-
...context,
|
|
67
|
-
listDepth: context.listDepth + 1
|
|
68
|
-
};
|
|
69
|
-
const lines = collect(node.children, (tag) => tag === "li", () => false).map((item, index) => {
|
|
70
|
-
const marker = node.tag === "ol" ? `${first + index}.` : "-";
|
|
71
|
-
const continuation = `${indent}${" ".repeat(marker.length + 1)}`;
|
|
72
|
-
const [head = "", ...rest] = derive(item, nested).replace(/^\s+/, "").replace(/\s+$/, "").replace(/\n{2,}/g, LINE).split("\n");
|
|
73
|
-
return [`${indent}${marker} ${head}`, ...rest.map((line) => line.trim() === "" ? "" : /^\s/.test(line) ? line : `${continuation}${line}`)].join(LINE);
|
|
74
|
-
});
|
|
75
|
-
return block(lines.join(LINE), PARAGRAPH);
|
|
76
|
-
}
|
|
77
|
-
case "table": {
|
|
78
|
-
if (isLayoutTable(node.props)) return childText(node, context);
|
|
79
|
-
const rows = collect(node.children, (tag) => tag === "tr", (tag) => TABLE_SECTIONS.has(tag)).map((row) => collect(row.children, (tag) => tag === "td" || tag === "th", () => false).filter((cell) => !isDecorativeCell(cell, context)).map((cell) => childText(cell, context).replace(/\s+/g, " ").trim()).join(" | "));
|
|
80
|
-
return block(rows.join(LINE), PARAGRAPH);
|
|
81
|
-
}
|
|
82
|
-
case "row": return block(collect(node.children, (tag) => tag === "td" || tag === "th", () => false).map((cell) => childText(cell, context).replace(/^\s+|\s+$/g, "")).filter((cell) => cell !== "").join(LINE), LINE);
|
|
83
|
-
case "table-section":
|
|
84
|
-
case "cell": return block(childText(node, context), LINE);
|
|
85
|
-
case "preformatted": return block(childText(node, {
|
|
86
|
-
...context,
|
|
87
|
-
preformatted: true
|
|
88
|
-
}), PARAGRAPH);
|
|
89
|
-
case "block": return block(childText(node, context), PARAGRAPH);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
/** Collapse the separators the walk emits into readable plain text. */
|
|
95
|
-
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();
|
|
96
|
-
/** Derive the plain-text alternative from the semantic tree in reading order. */
|
|
97
|
-
const deriveEmailText = (node) => normalize(derive(node, {
|
|
98
|
-
preformatted: false,
|
|
99
|
-
listDepth: 0
|
|
100
|
-
}));
|
|
101
|
-
//#endregion
|
|
102
|
-
//#region src/email/render.ts
|
|
103
|
-
/** Classic Outlook lays a document out correctly only under a transitional doctype. */
|
|
104
|
-
const EMAIL_DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
|
|
105
|
-
const settle = (diagnostics) => {
|
|
106
|
-
if (hasBlockingDiagnostic(diagnostics)) throw new EmailCompileError({ diagnostics });
|
|
107
|
-
return diagnostics;
|
|
108
|
-
};
|
|
109
|
-
/** Lower the derived tree to HTML and its plain-text alternative. */
|
|
110
|
-
const compileEmail = (node, options = {}) => {
|
|
111
|
-
const serialized = serializeEmailHtml(node, options);
|
|
112
|
-
const diagnostics = settle([...drainEmailDiagnostics(), ...serialized.diagnostics]);
|
|
113
|
-
return {
|
|
114
|
-
html: serialized.html,
|
|
115
|
-
text: deriveEmailText(node),
|
|
116
|
-
diagnostics,
|
|
117
|
-
positions: serialized.positions
|
|
118
|
-
};
|
|
119
|
-
};
|
|
120
|
-
/** Put the hidden preview block first inside `<body>`, or first overall when there is no shell. */
|
|
121
|
-
const withPreheader = (node, text) => {
|
|
122
|
-
const insert = (current) => {
|
|
123
|
-
if (current.type === "Fragment") {
|
|
124
|
-
for (const [index, child] of current.children.entries()) {
|
|
125
|
-
const replaced = insert(child);
|
|
126
|
-
if (replaced !== void 0) return EmailNode.Fragment({ children: current.children.with(index, replaced) });
|
|
127
|
-
}
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
if (current.type !== "Element") return void 0;
|
|
131
|
-
if (current.tag === "body") return EmailNode.Element({
|
|
132
|
-
...current,
|
|
133
|
-
children: [preheaderNode(text), ...current.children]
|
|
134
|
-
});
|
|
135
|
-
for (const [index, child] of current.children.entries()) {
|
|
136
|
-
const replaced = insert(child);
|
|
137
|
-
if (replaced !== void 0) return EmailNode.Element({
|
|
138
|
-
...current,
|
|
139
|
-
children: current.children.with(index, replaced)
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
return insert(node) ?? EmailNode.Fragment({ children: [preheaderNode(text), node] });
|
|
144
|
-
};
|
|
145
|
-
const headStyle = (css, origins) => EmailNode.Element({
|
|
146
|
-
tag: "style",
|
|
147
|
-
props: {},
|
|
148
|
-
children: [EmailNode.Text({ value: css })],
|
|
149
|
-
origins,
|
|
150
|
-
authored: false
|
|
151
|
-
});
|
|
152
|
-
/**
|
|
153
|
-
* Put the retained rules in a `<style>` at the end of `<head>`, and project
|
|
154
|
-
* `@font-face` rules in a sibling classic Outlook cannot see. Outlook for
|
|
155
|
-
* Windows registers a face it cannot load and then renders the family in Times
|
|
156
|
-
* New Roman, ignoring the stack; hidden, the family is merely missing and the
|
|
157
|
-
* stack's fallback applies.
|
|
158
|
-
*/
|
|
159
|
-
const withHeadCss = (node, css, fontFaceCss) => {
|
|
160
|
-
const insert = (current) => {
|
|
161
|
-
if (current.type === "Fragment") {
|
|
162
|
-
for (const [index, child] of current.children.entries()) {
|
|
163
|
-
const replaced = insert(child);
|
|
164
|
-
if (replaced !== void 0) return EmailNode.Fragment({ children: current.children.with(index, replaced) });
|
|
165
|
-
}
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
if (current.type !== "Element") return void 0;
|
|
169
|
-
if (current.tag === "head") return EmailNode.Element({
|
|
170
|
-
...current,
|
|
171
|
-
children: [
|
|
172
|
-
...current.children,
|
|
173
|
-
...css === "" ? [] : [headStyle(css, current.origins)],
|
|
174
|
-
...fontFaceCss === "" ? [] : [
|
|
175
|
-
EmailNode.Raw({
|
|
176
|
-
html: "<!--[if !mso]><!-->",
|
|
177
|
-
text: "",
|
|
178
|
-
origins: []
|
|
179
|
-
}),
|
|
180
|
-
headStyle(fontFaceCss, current.origins),
|
|
181
|
-
EmailNode.Raw({
|
|
182
|
-
html: "<!--<![endif]-->",
|
|
183
|
-
text: "",
|
|
184
|
-
origins: []
|
|
185
|
-
})
|
|
186
|
-
]
|
|
187
|
-
]
|
|
188
|
-
});
|
|
189
|
-
for (const [index, child] of current.children.entries()) {
|
|
190
|
-
const replaced = insert(child);
|
|
191
|
-
if (replaced !== void 0) return EmailNode.Element({
|
|
192
|
-
...current,
|
|
193
|
-
children: current.children.with(index, replaced)
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
return insert(node);
|
|
198
|
-
};
|
|
199
|
-
/**
|
|
200
|
-
* Render authored content against a stylesheet: inline what a client honors,
|
|
201
|
-
* keep the rest for `<head>`, and lower to HTML and text.
|
|
202
|
-
*/
|
|
203
|
-
const renderEmailContent = (stylesheet, render, options = {}) => {
|
|
204
|
-
drainEmailDiagnostics();
|
|
205
|
-
const content = render();
|
|
206
|
-
const invalid = [];
|
|
207
|
-
const fail = (code, message) => invalid.push({
|
|
208
|
-
code,
|
|
209
|
-
severity: "error",
|
|
210
|
-
message,
|
|
211
|
-
origins: []
|
|
212
|
-
});
|
|
213
|
-
if (typeof content.subject !== "string" || /[\r\n\u0000]/.test(content.subject)) fail("invalid-subject", "Invalid email subject: it must be one line of text.");
|
|
214
|
-
if (content.preheader !== void 0 && typeof content.preheader !== "string") fail("invalid-preheader", "Invalid email preheader: it must be a string.");
|
|
215
|
-
if (content.text !== void 0 && typeof content.text !== "string") fail("invalid-text", "Invalid plain text: it must be a string.");
|
|
216
|
-
settle(invalid);
|
|
217
|
-
const base = content.preheader === void 0 ? content.body : withPreheader(content.body, content.preheader);
|
|
218
|
-
const styled = stylesheet === void 0 ? {
|
|
219
|
-
tree: base,
|
|
220
|
-
headCss: "",
|
|
221
|
-
fontFaceCss: "",
|
|
222
|
-
diagnostics: [],
|
|
223
|
-
darkRules: /* @__PURE__ */ new Map()
|
|
224
|
-
} : applyStylesheet(base, stylesheet);
|
|
225
|
-
const headCss = `${renderDarkRules(styled.darkRules)}${styled.headCss}`;
|
|
226
|
-
const style = [...styled.diagnostics];
|
|
227
|
-
let tree = styled.tree;
|
|
228
|
-
if (headCss !== "" || styled.fontFaceCss !== "") {
|
|
229
|
-
const placed = withHeadCss(tree, headCss, styled.fontFaceCss);
|
|
230
|
-
if (placed === void 0) style.push({
|
|
231
|
-
code: "missing-head",
|
|
232
|
-
severity: "error",
|
|
233
|
-
message: "Styles that cannot be inlined — media queries, dark-scheme rules and pseudo-selectors — need a <head> to live in. Wrap the message in <Email>, or write those declarations as inline styles.",
|
|
234
|
-
origins: []
|
|
235
|
-
});
|
|
236
|
-
else tree = placed;
|
|
237
|
-
}
|
|
238
|
-
settle(style);
|
|
239
|
-
const compiled = compileEmail(tree, options);
|
|
240
|
-
const prefix = compiled.html.startsWith("<html") ? EMAIL_DOCTYPE : "";
|
|
241
|
-
const html = `${prefix}${compiled.html}`;
|
|
242
|
-
const positions = prefix === "" ? compiled.positions : compiled.positions.map((position) => ({
|
|
243
|
-
...position,
|
|
244
|
-
start: position.start + prefix.length,
|
|
245
|
-
end: position.end + prefix.length
|
|
246
|
-
}));
|
|
247
|
-
return {
|
|
248
|
-
subject: content.subject,
|
|
249
|
-
preheader: content.preheader,
|
|
250
|
-
html,
|
|
251
|
-
text: content.text ?? compiled.text,
|
|
252
|
-
tree,
|
|
253
|
-
diagnostics: [...style, ...compiled.diagnostics],
|
|
254
|
-
positions
|
|
255
|
-
};
|
|
256
|
-
};
|
|
257
|
-
//#endregion
|
|
258
|
-
export { deriveEmailText as i, compileEmail as n, renderEmailContent as r, EMAIL_DOCTYPE as t };
|