@samva/markup 0.9.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 +53 -41
- package/CHANGELOG.md +379 -0
- package/README.md +6 -6
- package/dist/assets-B0yseju_.d.ts +115 -0
- package/dist/brand-DTctcDmf.js +202 -0
- package/dist/brand-plugin-CHhcei-j.d.ts +32 -0
- package/dist/{theme-css.d.ts → brand.d.ts} +15 -4
- package/dist/brand.js +4 -0
- package/dist/compiler.d.ts +236 -18
- package/dist/compiler.js +1315 -13
- package/dist/{email/components.js → components-Bl6DT21n.js} +4 -4
- package/dist/diagnostic-model-DJtqbETd.js +38 -0
- package/dist/diagnostic-model-DxthUZBT.d.ts +31 -0
- package/dist/{email/diagnostics.js → diagnostics-CnKFefKz.js} +2 -2
- package/dist/diagnostics.d.ts +406 -27
- package/dist/diagnostics.js +402 -34
- package/dist/edit.d.ts +234 -0
- package/dist/{channel-edit.js → edit.js} +341 -3
- package/dist/email/jsx-dev-runtime.d.ts +1 -1
- package/dist/email/jsx-dev-runtime.js +1 -2
- package/dist/email/jsx-runtime.d.ts +1 -1
- package/dist/email/jsx-runtime.js +1 -85
- package/dist/{email/components.d.ts → email.d.ts} +2 -2
- package/dist/email.js +2 -0
- package/dist/html-DjPxwjLi.js +1637 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{cascade-t6kxlgEI.js → ir-De6pTH51.js} +8 -4
- package/dist/jsx-runtime-CBlexoeR.js +98 -0
- package/dist/{elements-cqldpaJt.d.ts → jsx-runtime-ylIOnaSf.d.ts} +74 -74
- package/dist/{profile-BH-OtdDA.js → profile-PUfi3GPH.js} +166 -32
- package/dist/{email/render.js → render-D2ht57ZZ.js} +103 -8
- package/dist/render-DQKz4Aco.d.ts +548 -0
- package/dist/render.d.ts +35 -0
- package/dist/render.js +49 -0
- package/dist/sms.d.ts +1 -1
- package/dist/sms.js +1 -1
- package/dist/{template-_QE_rQND.d.ts → template-DMPwxMSq.d.ts} +2 -2
- package/dist/{template.js → template-zLUjcwfF.js} +2 -2
- package/dist/{theme-css-hFqcPSar.js → theme-css-CEUrDpqq.js} +3 -3
- package/dist/whatsapp.d.ts +1 -1
- package/dist/whatsapp.js +1 -1
- package/generated/contract.json +18 -12
- package/generated/reference.md +19 -12
- package/package.json +39 -122
- package/samva-brand.d.ts +4 -4
- package/skills/sml/SKILL.md +2 -2
- package/dist/assets-C5duwTya.d.ts +0 -40
- package/dist/brand-B-0bN_Ir.d.ts +0 -88
- package/dist/channel-edit.d.ts +0 -121
- package/dist/channel-specs-B6M_HlaV.js +0 -29
- package/dist/compatibility-DbISoFIq.js +0 -589
- package/dist/css-BVQKICKC.d.ts +0 -109
- package/dist/diagnostic-codes.d.ts +0 -403
- package/dist/diagnostic-codes.js +0 -399
- package/dist/email/assets.d.ts +0 -2
- package/dist/email/assets.js +0 -63
- package/dist/email/brand-footer.d.ts +0 -34
- package/dist/email/brand-footer.js +0 -63
- package/dist/email/brand.d.ts +0 -2
- package/dist/email/brand.js +0 -148
- package/dist/email/diagnostics.d.ts +0 -2
- package/dist/email/elements.d.ts +0 -2
- package/dist/email/elements.js +0 -299
- package/dist/email/html.d.ts +0 -2
- package/dist/email/html.js +0 -321
- package/dist/email/render.d.ts +0 -40
- package/dist/email/text.d.ts +0 -7
- package/dist/email/text.js +0 -100
- package/dist/html-BJepOwZ4.d.ts +0 -56
- package/dist/ir-aaqNE987.d.ts +0 -252
- package/dist/ir.d.ts +0 -2
- package/dist/ir.js +0 -7
- package/dist/preview-Ekdg1dXG.d.ts +0 -100
- package/dist/preview.d.ts +0 -2
- package/dist/preview.js +0 -160
- package/dist/render-ir.d.ts +0 -19
- package/dist/render-ir.js +0 -826
- package/dist/sms-segments.d.ts +0 -13
- package/dist/sms-segments.js +0 -46
- package/dist/source-edit.d.ts +0 -116
- package/dist/source-edit.js +0 -343
- package/dist/source-locations.d.ts +0 -2
- package/dist/source-locations.js +0 -16
- package/dist/static.d.ts +0 -57
- package/dist/static.js +0 -69
- package/dist/tailwind-CMYpZf1v.js +0 -614
- package/dist/template.d.ts +0 -2
- package/dist/theme-css.js +0 -2
- package/dist/theme-imports-lvBw3AD1.js +0 -109
- package/dist/transform-Ct2OMkFN.d.ts +0 -153
- package/dist/unsubscribe-link-CoesnQDU.js +0 -26
|
@@ -1,6 +1,344 @@
|
|
|
1
1
|
import { n as isObject } from "./guards-eV1qLbf2.js";
|
|
2
|
-
import {
|
|
2
|
+
import { d as completeJsxSourceLocation, u as JSX_SOURCE_LOCATION_PROP } from "./jsx-runtime-CBlexoeR.js";
|
|
3
|
+
import { t as buildLineMap } from "./diagnostic-model-DJtqbETd.js";
|
|
4
|
+
import { c as WHATSAPP_HEADER_TYPES, n as introducedProfileErrors, o as SMS_CATEGORIES, s as WHATSAPP_CATEGORIES, t as checkStaticProfile } from "./profile-PUfi3GPH.js";
|
|
3
5
|
import { parse } from "@babel/parser";
|
|
6
|
+
//#region src/source-edit.ts
|
|
7
|
+
const node = (value) => isObject(value) && "type" in value && typeof value.type === "string" && "start" in value && typeof value.start === "number" && "end" in value && typeof value.end === "number";
|
|
8
|
+
const children = (value) => Array.isArray(value) ? value.filter(node) : [];
|
|
9
|
+
const walk$1 = (value, visit, parent = null) => {
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
for (const item of value) walk$1(item, visit, parent);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (!node(value)) return;
|
|
15
|
+
visit(value, parent);
|
|
16
|
+
for (const [key, child] of Object.entries(value)) if (key !== "loc" && key !== "extra" && key !== "comments") walk$1(child, visit, value);
|
|
17
|
+
};
|
|
18
|
+
const parsed = (source) => {
|
|
19
|
+
try {
|
|
20
|
+
const result = parse(source, {
|
|
21
|
+
sourceType: "module",
|
|
22
|
+
plugins: ["typescript", "jsx"]
|
|
23
|
+
});
|
|
24
|
+
return node(result) ? result : null;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const binding = (value) => value.type === "Identifier" || value.type === "MemberExpression" && value.computed === false && node(value.object) && binding(value.object);
|
|
30
|
+
/** JSX trims each line of text and joins the non-empty lines with one space. */
|
|
31
|
+
const collapseJsxText = (value) => value.split(/\r\n|\n|\r|\u2028|\u2029/).map((line) => line.trim()).filter((line) => line !== "").join(" ");
|
|
32
|
+
const property = (name, value, source, syntax) => {
|
|
33
|
+
const expression = value.type === "JSXExpressionContainer" && node(value.expression) ? value.expression : value;
|
|
34
|
+
const literal = expression.type === "StringLiteral" || expression.type === "NumericLiteral" || expression.type === "BooleanLiteral" || expression.type === "JSXText";
|
|
35
|
+
const raw = source.slice(expression.start, expression.end);
|
|
36
|
+
const inset = expression.type === "JSXText" ? {
|
|
37
|
+
start: expression.start + (raw.length - raw.trimStart().length),
|
|
38
|
+
end: expression.end - (raw.length - raw.trimEnd().length)
|
|
39
|
+
} : {
|
|
40
|
+
start: value.start,
|
|
41
|
+
end: value.end
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
name,
|
|
45
|
+
literalType: expression.type === "NumericLiteral" ? "number" : expression.type === "BooleanLiteral" ? "boolean" : "string",
|
|
46
|
+
kind: literal ? "literal" : binding(expression) ? "binding" : "computed",
|
|
47
|
+
value: literal && expression.type === "JSXText" && typeof expression.value === "string" ? collapseJsxText(expression.value) : literal && (typeof expression.value === "string" || typeof expression.value === "number" || typeof expression.value === "boolean") ? String(expression.value) : source.slice(expression.start, expression.end),
|
|
48
|
+
start: inset.start,
|
|
49
|
+
end: inset.end,
|
|
50
|
+
syntax
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/** The JSX elements directly before and after `element` in its parent's child list. */
|
|
54
|
+
const siblingsOf = (element, parent, source) => {
|
|
55
|
+
if (parent?.type !== "JSXElement" && parent?.type !== "JSXFragment") return {
|
|
56
|
+
previous: null,
|
|
57
|
+
next: null
|
|
58
|
+
};
|
|
59
|
+
const list = children(parent.children).filter((child) => child.type !== "JSXText" || source.slice(child.start, child.end).trim() !== "");
|
|
60
|
+
const index = list.findIndex((child) => child.start === element.start);
|
|
61
|
+
const range = (child) => child?.type === "JSXElement" ? {
|
|
62
|
+
start: child.start,
|
|
63
|
+
end: child.end
|
|
64
|
+
} : null;
|
|
65
|
+
return {
|
|
66
|
+
previous: range(list[index - 1]),
|
|
67
|
+
next: range(list[index + 1])
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
/** Resolve a renderer origin against the unchanged authored entry, never against HTML offsets. */
|
|
71
|
+
const inspectSourceElement = (source, file, origin) => {
|
|
72
|
+
if (file !== origin.fileName) return null;
|
|
73
|
+
const tree = parsed(source);
|
|
74
|
+
if (tree === null) return null;
|
|
75
|
+
const lines = buildLineMap(source);
|
|
76
|
+
const lineStart = lines[origin.lineNumber - 1];
|
|
77
|
+
if (lineStart === void 0 || !Number.isInteger(origin.columnNumber) || origin.columnNumber < 1) return null;
|
|
78
|
+
const offset = lineStart + origin.columnNumber - 1;
|
|
79
|
+
if (offset >= (lines[origin.lineNumber] ?? source.length)) return null;
|
|
80
|
+
let found = null;
|
|
81
|
+
walk$1(tree, (element, parent) => {
|
|
82
|
+
if (element.type !== "JSXElement" || element.start !== offset || !node(element.openingElement)) return;
|
|
83
|
+
const opening = element.openingElement;
|
|
84
|
+
const properties = [];
|
|
85
|
+
for (const attribute of children(opening.attributes)) if (attribute.type === "JSXAttribute" && node(attribute.name) && typeof attribute.name.name === "string" && node(attribute.value)) {
|
|
86
|
+
const value = attribute.value;
|
|
87
|
+
const expression = value.type === "JSXExpressionContainer" && node(value.expression) ? value.expression : null;
|
|
88
|
+
if (attribute.name.name === "style" && expression?.type === "ObjectExpression" && !children(expression.properties).some((member) => member.type === "SpreadElement")) for (const member of children(expression.properties)) {
|
|
89
|
+
if (member.type !== "ObjectProperty" || member.computed === true || !node(member.key) || !node(member.value)) continue;
|
|
90
|
+
const key = member.key.type === "Identifier" ? member.key.name : member.key.value;
|
|
91
|
+
if (typeof key === "string") properties.push(property(`style.${key}`, member.value, source, "value"));
|
|
92
|
+
}
|
|
93
|
+
else properties.push(property(attribute.name.name, value, source, "attribute"));
|
|
94
|
+
}
|
|
95
|
+
const meaningful = children(element.children).filter((child) => child.type !== "JSXText" || source.slice(child.start, child.end).trim() !== "");
|
|
96
|
+
if (meaningful.length === 1 && meaningful[0] !== void 0 && meaningful[0].type !== "JSXElement" && meaningful[0].type !== "JSXFragment") properties.unshift(property("children", meaningful[0], source, "child"));
|
|
97
|
+
else if (meaningful.length > 1) {
|
|
98
|
+
const texts = meaningful.flatMap((child, index) => child.type === "JSXText" ? [property(`text.${index}`, child, source, "child")] : []);
|
|
99
|
+
properties.unshift(...texts);
|
|
100
|
+
}
|
|
101
|
+
found = {
|
|
102
|
+
start: element.start,
|
|
103
|
+
end: element.end,
|
|
104
|
+
contentStart: opening.selfClosing === true ? null : opening.end,
|
|
105
|
+
contentEnd: node(element.closingElement) ? element.closingElement.start : null,
|
|
106
|
+
tag: node(opening.name) ? source.slice(opening.name.start, opening.name.end) : "element",
|
|
107
|
+
properties,
|
|
108
|
+
attributeInsertion: (() => {
|
|
109
|
+
const last = children(opening.attributes).at(-1);
|
|
110
|
+
if (last !== void 0) return last.end;
|
|
111
|
+
return node(opening.name) ? opening.name.end : opening.end - 1;
|
|
112
|
+
})(),
|
|
113
|
+
hasSpread: children(opening.attributes).some((attribute) => attribute.type === "JSXSpreadAttribute"),
|
|
114
|
+
structural: parent?.type === "JSXElement" || parent?.type === "JSXFragment",
|
|
115
|
+
...siblingsOf(element, parent, source)
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
return found;
|
|
119
|
+
};
|
|
120
|
+
const PLAIN_JSX_TEXT = /^[^\s{}<>&](?:[^{}<>&\r\n\u2028\u2029]*[^\s{}<>&])?$/;
|
|
121
|
+
/** Literal controls cannot overwrite a binding or computed expression. */
|
|
122
|
+
const literalReplacement = (source, field, value) => {
|
|
123
|
+
if (field.kind !== "literal") return null;
|
|
124
|
+
if (field.literalType === "number" && (value.trim() === "" || !Number.isFinite(Number(value)))) return null;
|
|
125
|
+
if (field.literalType === "boolean" && value !== "true" && value !== "false") return null;
|
|
126
|
+
const literal = field.literalType === "string" ? JSON.stringify(value) : field.literalType === "number" ? String(Number(value)) : value;
|
|
127
|
+
const before = source.slice(field.start, field.end);
|
|
128
|
+
const plainText = field.syntax === "child" && !before.startsWith("{") && PLAIN_JSX_TEXT.test(value);
|
|
129
|
+
return {
|
|
130
|
+
start: field.start,
|
|
131
|
+
end: field.end,
|
|
132
|
+
before,
|
|
133
|
+
after: plainText ? value : field.syntax === "child" || field.syntax === "attribute" && field.literalType !== "string" ? `{${literal}}` : literal
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
/** Explicit expression changes are proposals: callers show before/after and require Apply. */
|
|
137
|
+
const expressionReplacement = (source, field, expression) => ({
|
|
138
|
+
start: field.start,
|
|
139
|
+
end: field.end,
|
|
140
|
+
before: source.slice(field.start, field.end),
|
|
141
|
+
after: field.syntax === "value" ? expression : `{${expression}}`
|
|
142
|
+
});
|
|
143
|
+
const profileRefusal = (source, result, guard) => {
|
|
144
|
+
const file = guard.file ?? guard.entry;
|
|
145
|
+
const project = (content) => ({
|
|
146
|
+
...guard.files,
|
|
147
|
+
[file]: content
|
|
148
|
+
});
|
|
149
|
+
const introduced = introducedProfileErrors(checkStaticProfile(project(source), guard.entry), checkStaticProfile(project(result), guard.entry));
|
|
150
|
+
const first = introduced[0];
|
|
151
|
+
if (first === void 0) return null;
|
|
152
|
+
const more = introduced.length > 1 ? ` (and ${introduced.length - 1} more)` : "";
|
|
153
|
+
return `This change leaves the static profile: ${first.message}${first.fix === void 0 ? "" : ` ${first.fix}`}${more}`;
|
|
154
|
+
};
|
|
155
|
+
/** Apply exact, nonoverlapping edits and refuse syntax damage without changing the original. */
|
|
156
|
+
const applySourceReplacements = (source, edits, options = {}) => {
|
|
157
|
+
const sorted = [...edits].sort((a, b) => b.start - a.start);
|
|
158
|
+
let boundary = source.length;
|
|
159
|
+
let result = source;
|
|
160
|
+
for (const edit of sorted) {
|
|
161
|
+
if (edit.start < 0 || edit.end < edit.start || edit.end > boundary || source.slice(edit.start, edit.end) !== edit.before) return {
|
|
162
|
+
ok: false,
|
|
163
|
+
reason: "Source changed at this target. Select it again before applying the edit."
|
|
164
|
+
};
|
|
165
|
+
result = result.slice(0, edit.start) + edit.after + result.slice(edit.end);
|
|
166
|
+
boundary = edit.start;
|
|
167
|
+
}
|
|
168
|
+
if (parsed(result) === null) return {
|
|
169
|
+
ok: false,
|
|
170
|
+
reason: "This change is not valid TSX. Review it in Source."
|
|
171
|
+
};
|
|
172
|
+
if (options.profile !== void 0 && result !== source) {
|
|
173
|
+
const refusal = profileRefusal(source, result, options.profile);
|
|
174
|
+
if (refusal !== null) return {
|
|
175
|
+
ok: false,
|
|
176
|
+
reason: refusal
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
ok: true,
|
|
181
|
+
source: result
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
const indentation = (source, offset) => {
|
|
185
|
+
const lineStart = Math.max(source.lastIndexOf("\n", offset - 1), source.lastIndexOf("\r", offset - 1));
|
|
186
|
+
const lead = source.slice(lineStart + 1, offset);
|
|
187
|
+
return /^[ \t]*$/.test(lead) ? lead : null;
|
|
188
|
+
};
|
|
189
|
+
/** The whole lines an element alone occupies, so removing it leaves no blank line behind. */
|
|
190
|
+
const ownLines = (source, element) => {
|
|
191
|
+
if (indentation(source, element.start) === null) return element;
|
|
192
|
+
const trailing = /^[ \t]*(\r\n|\n|\r)/.exec(source.slice(element.end));
|
|
193
|
+
if (trailing === null) return element;
|
|
194
|
+
return {
|
|
195
|
+
start: element.start - (indentation(source, element.start) ?? "").length,
|
|
196
|
+
end: element.end + trailing[0].length
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
/** Structural edits name a whole authored block; loop iterations are never source nodes. */
|
|
200
|
+
const structuralReplacements = (source, element, operation) => {
|
|
201
|
+
if (operation.kind === "insert") {
|
|
202
|
+
if (element.contentEnd === null) return null;
|
|
203
|
+
return [{
|
|
204
|
+
start: element.contentEnd,
|
|
205
|
+
end: element.contentEnd,
|
|
206
|
+
before: "",
|
|
207
|
+
after: operation.tsx
|
|
208
|
+
}];
|
|
209
|
+
}
|
|
210
|
+
if (!element.structural) return null;
|
|
211
|
+
const text = source.slice(element.start, element.end);
|
|
212
|
+
if (operation.kind === "duplicate") {
|
|
213
|
+
const indent = indentation(source, element.start);
|
|
214
|
+
return [{
|
|
215
|
+
start: element.end,
|
|
216
|
+
end: element.end,
|
|
217
|
+
before: "",
|
|
218
|
+
after: indent === null ? text : `\n${indent}${text}`
|
|
219
|
+
}];
|
|
220
|
+
}
|
|
221
|
+
if (operation.kind === "reorder") {
|
|
222
|
+
const neighbour = operation.direction === "before" ? element.previous : element.next;
|
|
223
|
+
if (neighbour === null) return null;
|
|
224
|
+
return [{
|
|
225
|
+
start: neighbour.start,
|
|
226
|
+
end: neighbour.end,
|
|
227
|
+
before: source.slice(neighbour.start, neighbour.end),
|
|
228
|
+
after: text
|
|
229
|
+
}, {
|
|
230
|
+
start: element.start,
|
|
231
|
+
end: element.end,
|
|
232
|
+
before: text,
|
|
233
|
+
after: source.slice(neighbour.start, neighbour.end)
|
|
234
|
+
}];
|
|
235
|
+
}
|
|
236
|
+
const removal = ownLines(source, element);
|
|
237
|
+
const removed = {
|
|
238
|
+
start: removal.start,
|
|
239
|
+
end: removal.end,
|
|
240
|
+
before: source.slice(removal.start, removal.end),
|
|
241
|
+
after: ""
|
|
242
|
+
};
|
|
243
|
+
if (operation.kind === "delete") return [removed];
|
|
244
|
+
const destination = operation.destination.contentEnd;
|
|
245
|
+
if (destination === null || destination >= element.start && destination <= element.end) return null;
|
|
246
|
+
return [removed, {
|
|
247
|
+
start: destination,
|
|
248
|
+
end: destination,
|
|
249
|
+
before: "",
|
|
250
|
+
after: text
|
|
251
|
+
}];
|
|
252
|
+
};
|
|
253
|
+
/** The object literal a top-level `const name = { ... }` holds. */
|
|
254
|
+
const topLevelObject = (tree, name) => {
|
|
255
|
+
const program = tree.program;
|
|
256
|
+
if (!node(program)) return null;
|
|
257
|
+
for (const item of children(program.body)) {
|
|
258
|
+
const declaration = item.type === "ExportNamedDeclaration" && node(item.declaration) ? item.declaration : item;
|
|
259
|
+
if (declaration.type !== "VariableDeclaration") continue;
|
|
260
|
+
for (const declarator of children(declaration.declarations)) if (node(declarator.id) && declarator.id.name === name && node(declarator.init) && declarator.init.type === "ObjectExpression") return declarator.init;
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
};
|
|
264
|
+
/** Declared fixture values are separate source targets from body bindings and literals. */
|
|
265
|
+
const inspectSourceFixtures = (source) => {
|
|
266
|
+
const tree = parsed(source);
|
|
267
|
+
if (tree === null) return [];
|
|
268
|
+
const found = [];
|
|
269
|
+
walk$1(tree, (call) => {
|
|
270
|
+
if (call.type !== "CallExpression" || !node(call.callee) || call.callee.type !== "Identifier" || call.callee.name !== "defineTemplate") return;
|
|
271
|
+
const definition = children(call.arguments)[0];
|
|
272
|
+
if (definition?.type !== "ObjectExpression") return;
|
|
273
|
+
const fixtures = children(definition.properties).find((field) => field.type === "ObjectProperty" && node(field.key) && field.key.name === "fixtures" && field.computed !== true);
|
|
274
|
+
if (fixtures === void 0 || !node(fixtures.value) || fixtures.value.type !== "ObjectExpression") return;
|
|
275
|
+
for (const field of children(fixtures.value.properties)) {
|
|
276
|
+
if (field.type !== "ObjectProperty" || field.computed === true || !node(field.key) || !node(field.value)) continue;
|
|
277
|
+
const name = field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
278
|
+
const value = field.value.type === "Identifier" && typeof field.value.name === "string" ? topLevelObject(tree, field.value.name) ?? field.value : field.value;
|
|
279
|
+
if (typeof name === "string") found.push({
|
|
280
|
+
name,
|
|
281
|
+
start: value.start,
|
|
282
|
+
end: value.end,
|
|
283
|
+
source: source.slice(value.start, value.end)
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
return found;
|
|
288
|
+
};
|
|
289
|
+
const tokens = (value) => value.split(/\s+/).filter((item) => item !== "");
|
|
290
|
+
/**
|
|
291
|
+
* Edit an element's class list where it is written. A literal `className` is replaced, extended or
|
|
292
|
+
* trimmed in place; a missing one is added; one that is a condition or any other expression is
|
|
293
|
+
* refused, because rewriting it would discard the choice the author encoded.
|
|
294
|
+
*/
|
|
295
|
+
const classNameEdit = (source, element, operation) => {
|
|
296
|
+
const field = element.properties.find((item) => item.name === "className");
|
|
297
|
+
const next = (current) => {
|
|
298
|
+
if (operation.kind === "set") return tokens(operation.value).join(" ");
|
|
299
|
+
const wanted = tokens(operation.tokens);
|
|
300
|
+
return operation.kind === "add" ? [...current, ...wanted.filter((item) => !current.includes(item))].join(" ") : current.filter((item) => !wanted.includes(item)).join(" ");
|
|
301
|
+
};
|
|
302
|
+
if (field === void 0) {
|
|
303
|
+
if (element.hasSpread) return {
|
|
304
|
+
ok: false,
|
|
305
|
+
reason: "A spread attribute can supply className here, so a class list cannot be added safely. Edit it in Source."
|
|
306
|
+
};
|
|
307
|
+
if (operation.kind === "remove") return {
|
|
308
|
+
ok: true,
|
|
309
|
+
edits: []
|
|
310
|
+
};
|
|
311
|
+
const value = next([]);
|
|
312
|
+
if (value === "") return {
|
|
313
|
+
ok: true,
|
|
314
|
+
edits: []
|
|
315
|
+
};
|
|
316
|
+
return {
|
|
317
|
+
ok: true,
|
|
318
|
+
edits: [{
|
|
319
|
+
start: element.attributeInsertion,
|
|
320
|
+
end: element.attributeInsertion,
|
|
321
|
+
before: "",
|
|
322
|
+
after: ` className=${JSON.stringify(value)}`
|
|
323
|
+
}]
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
if (field.kind !== "literal" || field.literalType !== "string") return {
|
|
327
|
+
ok: false,
|
|
328
|
+
reason: `className is ${field.kind === "binding" ? "bound to" : "computed from"} \`${field.value}\`, so editing it would discard that choice. Change the literal classes inside it in Source.`
|
|
329
|
+
};
|
|
330
|
+
const value = next(tokens(field.value));
|
|
331
|
+
return {
|
|
332
|
+
ok: true,
|
|
333
|
+
edits: [{
|
|
334
|
+
start: field.start,
|
|
335
|
+
end: field.end,
|
|
336
|
+
before: source.slice(field.start, field.end),
|
|
337
|
+
after: JSON.stringify(value)
|
|
338
|
+
}]
|
|
339
|
+
};
|
|
340
|
+
};
|
|
341
|
+
//#endregion
|
|
4
342
|
//#region src/channel-edit.ts
|
|
5
343
|
const isNode = (value) => isObject(value) && typeof value.type === "string" && typeof value.start === "number" && typeof value.end === "number";
|
|
6
344
|
const nodeList = (value) => Array.isArray(value) ? value.filter(isNode) : [];
|
|
@@ -604,6 +942,6 @@ const editWhatsAppChannel = (source, edit) => {
|
|
|
604
942
|
}
|
|
605
943
|
};
|
|
606
944
|
//#endregion
|
|
607
|
-
export { editSmsChannel, editWhatsAppChannel, readSmsChannel, readWhatsAppChannel };
|
|
945
|
+
export { JSX_SOURCE_LOCATION_PROP, applySourceReplacements, classNameEdit, completeJsxSourceLocation, editSmsChannel, editWhatsAppChannel, expressionReplacement, inspectSourceElement, inspectSourceFixtures, literalReplacement, readSmsChannel, readWhatsAppChannel, structuralReplacements };
|
|
608
946
|
|
|
609
|
-
//# sourceMappingURL=
|
|
947
|
+
//# sourceMappingURL=edit.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as JsxSource } from "../source-locations-C8wCsiAy.js";
|
|
2
|
-
import {
|
|
2
|
+
import { a as JSX, i as Fragment, n as EmailComponent, r as EmailNode } from "../jsx-runtime-ylIOnaSf.js";
|
|
3
3
|
//#region src/email/jsx-dev-runtime.d.ts
|
|
4
4
|
/** Development JSX retains compiler-provided origins directly on the semantic tree. */
|
|
5
5
|
declare const jsxDEV: (type: string | EmailComponent<Record<string, unknown>> | typeof Fragment, props: Record<string, unknown> | null, _key?: unknown, _staticChildren?: boolean, source?: JsxSource) => EmailNode;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { JSX_SOURCE_LOCATION_PROP } from "../
|
|
2
|
-
import { Fragment, jsx } from "./jsx-runtime.js";
|
|
1
|
+
import { a as jsx, n as Fragment, u as JSX_SOURCE_LOCATION_PROP } from "../jsx-runtime-CBlexoeR.js";
|
|
3
2
|
//#region src/email/jsx-dev-runtime.ts
|
|
4
3
|
/** Development JSX retains compiler-provided origins directly on the semantic tree. */
|
|
5
4
|
const jsxDEV = (type, props, _key, _staticChildren, source) => jsx(type, {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as JSX, c as jsx, d as rawMarkup, f as reportEmailDiagnostic, i as Fragment, l as jsxs, n as EmailComponent, o as currentEmailOrigins, r as EmailNode, s as drainEmailDiagnostics, t as EmailChild, u as normalizeChildren } from "../jsx-runtime-ylIOnaSf.js";
|
|
2
2
|
export { EmailChild, EmailComponent, EmailNode, Fragment, JSX, currentEmailOrigins, drainEmailDiagnostics, jsx, jsxs, normalizeChildren, rawMarkup, reportEmailDiagnostic };
|
|
@@ -1,86 +1,2 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import { JSX_SOURCE_LOCATION_PROP, completeJsxSourceLocation } from "../source-locations.js";
|
|
3
|
-
//#region src/email/jsx-runtime.ts
|
|
4
|
-
/** Constructors for the authoring tree. The tree is data; nothing here validates it. */
|
|
5
|
-
const EmailNode = {
|
|
6
|
-
Text: (fields) => ({
|
|
7
|
-
type: "Text",
|
|
8
|
-
...fields
|
|
9
|
-
}),
|
|
10
|
-
Element: (fields) => ({
|
|
11
|
-
type: "Element",
|
|
12
|
-
...fields
|
|
13
|
-
}),
|
|
14
|
-
Fragment: (fields) => ({
|
|
15
|
-
type: "Fragment",
|
|
16
|
-
...fields
|
|
17
|
-
}),
|
|
18
|
-
Raw: (fields) => ({
|
|
19
|
-
type: "Raw",
|
|
20
|
-
...fields
|
|
21
|
-
})
|
|
22
|
-
};
|
|
23
|
-
const EMAIL_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
24
|
-
"Text",
|
|
25
|
-
"Element",
|
|
26
|
-
"Fragment",
|
|
27
|
-
"Raw"
|
|
28
|
-
]);
|
|
29
|
-
/** True for a node this runtime built; anything else is not a child it can place. */
|
|
30
|
-
const isEmailNode = (input) => hasProperty(input, "type") && typeof input.type === "string" && EMAIL_NODE_TYPES.has(input.type);
|
|
31
|
-
const Fragment = Symbol.for("@samva/markup/email/Fragment");
|
|
32
|
-
const callSites = [];
|
|
33
|
-
/** Call sites enclosing the element being built, innermost first. */
|
|
34
|
-
const currentEmailOrigins = () => [...callSites].reverse();
|
|
35
|
-
const reported = [];
|
|
36
|
-
/** Record a finding from inside a compiler primitive, located at the authoring call site. */
|
|
37
|
-
const reportEmailDiagnostic = (diagnostic) => {
|
|
38
|
-
reported.push({
|
|
39
|
-
...diagnostic,
|
|
40
|
-
origins: currentEmailOrigins()
|
|
41
|
-
});
|
|
42
|
-
};
|
|
43
|
-
/** Take and clear the findings primitives reported during a render. */
|
|
44
|
-
const drainEmailDiagnostics = () => reported.splice(0);
|
|
45
|
-
/** Normalize actual values without interpolation or recording proxies. */
|
|
46
|
-
const normalizeChildren = (input) => {
|
|
47
|
-
if (input === null || input === void 0 || typeof input === "boolean") return [];
|
|
48
|
-
if (typeof input === "string") return [EmailNode.Text({ value: input })];
|
|
49
|
-
if (typeof input === "number" && Number.isFinite(input)) return [EmailNode.Text({ value: String(input) })];
|
|
50
|
-
if (Array.isArray(input)) return input.flatMap(normalizeChildren);
|
|
51
|
-
if (isEmailNode(input)) return [input];
|
|
52
|
-
throw new TypeError("Email children must be synchronous text, finite numbers, elements, or arrays");
|
|
53
|
-
};
|
|
54
|
-
/** Compatibility markup emitted by a compiler primitive, with the text it stands for. */
|
|
55
|
-
const rawMarkup = (html, text = "") => EmailNode.Raw({
|
|
56
|
-
html,
|
|
57
|
-
text,
|
|
58
|
-
origins: currentEmailOrigins()
|
|
59
|
-
});
|
|
60
|
-
/** Automatic JSX factory for ordinary HTML and synchronous imported components. */
|
|
61
|
-
const jsx = (type, props) => {
|
|
62
|
-
const { children, [JSX_SOURCE_LOCATION_PROP]: source, ...attributes } = props ?? {};
|
|
63
|
-
const origin = completeJsxSourceLocation(source);
|
|
64
|
-
if (type === Fragment) return EmailNode.Fragment({ children: normalizeChildren(children) });
|
|
65
|
-
if (typeof type === "string") return EmailNode.Element({
|
|
66
|
-
tag: type,
|
|
67
|
-
props: attributes,
|
|
68
|
-
children: normalizeChildren(children),
|
|
69
|
-
origins: origin === void 0 ? currentEmailOrigins() : [origin, ...currentEmailOrigins()],
|
|
70
|
-
authored: origin !== void 0
|
|
71
|
-
});
|
|
72
|
-
if (origin !== void 0) callSites.push(origin);
|
|
73
|
-
try {
|
|
74
|
-
return EmailNode.Fragment({ children: normalizeChildren(type({
|
|
75
|
-
...attributes,
|
|
76
|
-
children
|
|
77
|
-
})) });
|
|
78
|
-
} finally {
|
|
79
|
-
if (origin !== void 0) callSites.pop();
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
const jsxs = jsx;
|
|
83
|
-
//#endregion
|
|
1
|
+
import { a as jsx, c as rawMarkup, i as drainEmailDiagnostics, l as reportEmailDiagnostic, n as Fragment, o as jsxs, r as currentEmailOrigins, s as normalizeChildren, t as EmailNode } from "../jsx-runtime-CBlexoeR.js";
|
|
84
2
|
export { EmailNode, Fragment, currentEmailOrigins, drainEmailDiagnostics, jsx, jsxs, normalizeChildren, rawMarkup, reportEmailDiagnostic };
|
|
85
|
-
|
|
86
|
-
//# sourceMappingURL=jsx-runtime.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { b as EmailStyle, r as EmailNode, t as EmailChild } from "./jsx-runtime-ylIOnaSf.js";
|
|
2
2
|
//#region src/email/components.d.ts
|
|
3
3
|
/** Marks the hidden preview block so the renderer can read it back and the text lane can keep it. */
|
|
4
4
|
declare const PREHEADER_ATTRIBUTE = "data-samva-preheader";
|
|
@@ -133,4 +133,4 @@ interface LinkProps {
|
|
|
133
133
|
declare const Link: ({ className, href, target, rel, style, children }: LinkProps) => EmailNode;
|
|
134
134
|
//#endregion
|
|
135
135
|
export { Button, ButtonProps, Column, ColumnProps, Columns, ColumnsProps, Divider, DividerProps, Email, EmailProps, Image, ImageProps, Link, LinkProps, PREHEADER_ATTRIBUTE, Section, SectionProps, Spacer, SpacerProps, equalColumnWidth, preheaderNode };
|
|
136
|
-
//# sourceMappingURL=
|
|
136
|
+
//# sourceMappingURL=email.d.ts.map
|
package/dist/email.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as Email, c as PREHEADER_ATTRIBUTE, d as equalColumnWidth, f as preheaderNode, 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";
|
|
2
|
+
export { Button, Column, Columns, Divider, Email, Image, Link, PREHEADER_ATTRIBUTE, Section, Spacer, equalColumnWidth, preheaderNode };
|