@samva/markup 0.4.0 → 0.6.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 +19 -0
- package/dist/{ast-63I-VLQx.d.ts → ast-BrDtApwS.d.ts} +34 -60
- package/dist/ast.d.ts +2 -2
- package/dist/ast.js +4 -52
- package/dist/{catalog-Ck1tAGwO.d.ts → catalog-60eaYC7b.d.ts} +2 -2
- package/dist/{checks-DXuERO4B.js → checks-DBweSIPN.js} +18 -20
- package/dist/checks.d.ts +1 -1
- package/dist/checks.js +1 -1
- package/dist/classes.d.ts +1 -1
- package/dist/compile-sms.d.ts +3 -3
- package/dist/compile-whatsapp.d.ts +5 -5
- package/dist/compile-whatsapp.js +3 -3
- package/dist/compile.d.ts +5 -5
- package/dist/compile.js +5 -5
- package/dist/compiler.d.ts +8 -5
- package/dist/compiler.js +45 -45
- package/dist/components.d.ts +4 -4
- package/dist/conditionals.js +56 -24
- package/dist/corpus-BjYMqOoY.d.ts +106 -0
- package/dist/{css-ar2lU3NC.d.ts → css-COcFOT1y.d.ts} +4 -5
- package/dist/diagnostics-BMYtBCQk.d.ts +46 -0
- package/dist/diagnostics-OcQKQYSY.d.ts +49 -0
- package/dist/diagnostics.d.ts +2 -2
- package/dist/diagnostics.js +24 -33
- package/dist/{elements-DtwKlFVH.d.ts → elements-BzjjnoCS.d.ts} +40 -161
- package/dist/email/components.d.ts +1 -1
- package/dist/email/components.js +40 -10
- package/dist/email/diagnostics.d.ts +1 -1
- package/dist/email/diagnostics.js +8 -43
- package/dist/email/elements.d.ts +1 -1
- package/dist/email/html.d.ts +2 -2
- package/dist/email/html.js +11 -10
- package/dist/email/jsx-dev-runtime.d.ts +3 -21
- package/dist/email/jsx-runtime.d.ts +1 -1
- package/dist/email/jsx-runtime.js +29 -3
- package/dist/email/render.d.ts +4 -4
- package/dist/email/render.js +1 -1
- package/dist/email/selection.d.ts +20 -31
- package/dist/email/selection.js +1 -53
- package/dist/email/source-edit.d.ts +1 -1
- package/dist/email/source-edit.js +2 -2
- package/dist/email/stylesheets.d.ts +1 -1
- package/dist/email/text.d.ts +1 -1
- package/dist/email/text.js +3 -3
- package/dist/emit.d.ts +2 -2
- package/dist/emit.js +5 -5
- package/dist/footer.js +7 -4
- package/dist/guards-eV1qLbf2.js +9 -0
- package/dist/{input-schema-BIfzI63B.d.ts → input-schema-7dPFVvNE.d.ts} +2 -2
- package/dist/input-schema-zjkgrXn2.js +455 -0
- package/dist/input-schema.d.ts +1 -1
- package/dist/input-schema.js +1 -169
- package/dist/interpolate.d.ts +9 -12
- package/dist/interpolate.js +1 -19
- package/dist/jsx-dev-runtime.d.ts +2 -2
- package/dist/{jsx-runtime-C5k_Zq0A.d.ts → jsx-runtime-I-1nCtl2.d.ts} +3 -3
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +3 -3
- package/dist/lexer.js +17 -5
- package/dist/model.d.ts +18 -33
- package/dist/model.js +4 -17
- package/dist/ops-2aiiYO7m.js +630 -0
- package/dist/ops.d.ts +78 -172
- package/dist/ops.js +2 -546
- package/dist/{parser-CGBS73Gz.d.ts → parser-CQcASEyy.d.ts} +5 -5
- package/dist/parser.d.ts +1 -1
- package/dist/parser.js +8 -8
- package/dist/{predicate-CvkzT-a2.js → predicate-BI6uRRM-.js} +24 -14
- package/dist/{predicate-G2SeSvoN.d.ts → predicate-M-05Nd7R.d.ts} +4 -4
- package/dist/predicate.d.ts +1 -1
- package/dist/predicate.js +1 -1
- package/dist/presets/category-mapping.d.ts +1 -1
- package/dist/presets/corpus.d.ts +1 -1
- package/dist/presets/corpus.js +74 -117
- package/dist/presets/public-gallery.d.ts +2 -2
- package/dist/presets/public-gallery.js +17 -9
- package/dist/presets/public-handoff.d.ts +4 -4
- package/dist/presets/public-handoff.js +10 -9
- package/dist/presets/public-preview-artifacts.d.ts +7 -5
- package/dist/presets/public-preview-artifacts.js +10 -9
- package/dist/presets/showcase.d.ts +1 -1
- package/dist/presets.d.ts +1 -1
- package/dist/printer.d.ts +1 -1
- package/dist/{render-ykQhtMs_.js → render-aaJBnsXK.js} +44 -27
- package/dist/render-template.d.ts +7 -7
- package/dist/render-template.js +6 -6
- package/dist/{showcase-C_xgl6hz.d.ts → showcase-DGZAWn4Z.d.ts} +2 -2
- package/dist/{source-locations-BCJQnIfU.d.ts → source-locations-DrCM82Ym.d.ts} +4 -26
- package/dist/source-locations.d.ts +2 -2
- package/dist/source-locations.js +3 -26
- package/dist/{template-Dy5yOPyp.d.ts → template-DWftqh4x.d.ts} +4 -4
- package/dist/template.d.ts +1 -1
- package/dist/template.js +3 -3
- package/dist/theme-css.d.ts +3 -3
- package/dist/theme-css.js +3 -3
- package/dist/theme.js +25 -2
- package/dist/tsx-source-instrumentation.d.ts +1 -3
- package/dist/tsx-source-instrumentation.js +6 -6
- package/dist/tsx-tracer.d.ts +7 -11
- package/dist/tsx-tracer.js +165 -165
- package/dist/{variables-if1VrV34.d.ts → variables-J_L3qxRa.d.ts} +2 -2
- package/dist/variables.d.ts +1 -1
- package/dist/variables.js +4 -4
- package/package.json +4 -3
- package/dist/corpus-U2EeALvS.d.ts +0 -234
- package/dist/diagnostics-Djd9ckLg.d.ts +0 -90
- package/dist/diagnostics-oveE-laV.d.ts +0 -79
package/dist/ops.js
CHANGED
|
@@ -1,546 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { CHANNEL_TAG_SPECS, allowedChildren, parseSml } from "./parser.js";
|
|
4
|
-
import { compileSml } from "./compile.js";
|
|
5
|
-
import { a as isBreakingCompatibilityCheck, r as checkSmlCompatibility } from "./checks-DXuERO4B.js";
|
|
6
|
-
import { printSml } from "./printer.js";
|
|
7
|
-
import { Result, Schema } from "effect";
|
|
8
|
-
//#region src/ops.ts
|
|
9
|
-
const InsertPositionSchema = Schema.Literals([
|
|
10
|
-
"before",
|
|
11
|
-
"after",
|
|
12
|
-
"inside"
|
|
13
|
-
]);
|
|
14
|
-
const SetPropOpSchema = Schema.TaggedStruct("setProp", {
|
|
15
|
-
nodeId: Schema.String,
|
|
16
|
-
name: Schema.String,
|
|
17
|
-
value: Schema.NullOr(AttributeValueSchema)
|
|
18
|
-
});
|
|
19
|
-
/**
|
|
20
|
-
* Replace a node's complete child list. The normal print → parse → compile
|
|
21
|
-
* preflight remains the authority on whether the replacement satisfies the
|
|
22
|
-
* target's content model.
|
|
23
|
-
*/
|
|
24
|
-
const SetChildrenOpSchema = Schema.TaggedStruct("setChildren", {
|
|
25
|
-
nodeId: Schema.String,
|
|
26
|
-
children: Schema.Array(SmlNodeSchema)
|
|
27
|
-
});
|
|
28
|
-
const InsertBlockOpSchema = Schema.TaggedStruct("insertBlock", {
|
|
29
|
-
tag: Schema.Literals(SML_TAGS),
|
|
30
|
-
referenceId: Schema.NullOr(Schema.String),
|
|
31
|
-
position: InsertPositionSchema
|
|
32
|
-
});
|
|
33
|
-
const InsertSnippetOpSchema = Schema.TaggedStruct("insertSnippet", {
|
|
34
|
-
snippet: Schema.String,
|
|
35
|
-
referenceId: Schema.NullOr(Schema.String),
|
|
36
|
-
position: InsertPositionSchema
|
|
37
|
-
});
|
|
38
|
-
/**
|
|
39
|
-
* `insertionIndex` is an element-child index into `targetParentId` evaluated
|
|
40
|
-
* AFTER the moved node has left its old position — the lifted-out model, so a
|
|
41
|
-
* same-parent caller must compute the index against the sibling list without
|
|
42
|
-
* the node. Indexes past the end append.
|
|
43
|
-
*/
|
|
44
|
-
const MoveNodeOpSchema = Schema.TaggedStruct("moveNode", {
|
|
45
|
-
nodeId: Schema.String,
|
|
46
|
-
targetParentId: Schema.String,
|
|
47
|
-
insertionIndex: Schema.Finite
|
|
48
|
-
});
|
|
49
|
-
const DuplicateNodeOpSchema = Schema.TaggedStruct("duplicateNode", { nodeId: Schema.String });
|
|
50
|
-
const RemoveNodeOpSchema = Schema.TaggedStruct("removeNode", { nodeId: Schema.String });
|
|
51
|
-
/**
|
|
52
|
-
* A single edit against SML source. `nodeId` (and `referenceId` /
|
|
53
|
-
* `targetParentId`) are positional ids from the source the caller last read;
|
|
54
|
-
* within a batch they always resolve against that snapshot — see {@link applyOps}.
|
|
55
|
-
*/
|
|
56
|
-
const SmlOpSchema = Schema.Union([
|
|
57
|
-
SetPropOpSchema,
|
|
58
|
-
SetChildrenOpSchema,
|
|
59
|
-
InsertBlockOpSchema,
|
|
60
|
-
InsertSnippetOpSchema,
|
|
61
|
-
MoveNodeOpSchema,
|
|
62
|
-
DuplicateNodeOpSchema,
|
|
63
|
-
RemoveNodeOpSchema
|
|
64
|
-
]);
|
|
65
|
-
const SmlOpResultSchema = Schema.Struct({
|
|
66
|
-
index: Schema.Finite,
|
|
67
|
-
status: Schema.Literals(["applied", "failed"]),
|
|
68
|
-
op: SmlOpSchema,
|
|
69
|
-
diagnostics: Schema.Array(DiagnosticSchema)
|
|
70
|
-
});
|
|
71
|
-
const ApplyOpsResultSchema = Schema.Struct({
|
|
72
|
-
source: Schema.String,
|
|
73
|
-
revision: Schema.String,
|
|
74
|
-
diagnostics: Schema.Array(DiagnosticSchema),
|
|
75
|
-
results: Schema.Array(SmlOpResultSchema)
|
|
76
|
-
});
|
|
77
|
-
var SmlOpError = class extends Schema.TaggedError()("SmlOpError", { diagnostics: Schema.Array(DiagnosticSchema) }) {
|
|
78
|
-
get message() {
|
|
79
|
-
return this.diagnostics.map((diagnostic) => diagnostic.message).join("\n");
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
const zeroSpan = {
|
|
83
|
-
start: 0,
|
|
84
|
-
end: 0
|
|
85
|
-
};
|
|
86
|
-
const diagnostic = (code, message, span = zeroSpan) => ({
|
|
87
|
-
code,
|
|
88
|
-
message,
|
|
89
|
-
span
|
|
90
|
-
});
|
|
91
|
-
const cloneAttribute = (attribute) => ({ ...attribute });
|
|
92
|
-
/**
|
|
93
|
-
* Deep-copy a subtree, blanking every id to "". A duplicated node must not
|
|
94
|
-
* alias the original's object graph or share its ids: batch ids resolve
|
|
95
|
-
* against the pre-batch snapshot, and mapElement transforms every node whose
|
|
96
|
-
* id matches — so two live copies sharing an id would both be rewritten by a
|
|
97
|
-
* later op. Blank ids make the clone unaddressable (like any freshly created
|
|
98
|
-
* node) until the next reprint reassigns positional ids.
|
|
99
|
-
*/
|
|
100
|
-
const cloneSubtree = (node) => {
|
|
101
|
-
if (node.kind === "element") return {
|
|
102
|
-
...node,
|
|
103
|
-
id: "",
|
|
104
|
-
attributes: node.attributes.map(cloneAttribute),
|
|
105
|
-
children: node.children.map(cloneSubtree)
|
|
106
|
-
};
|
|
107
|
-
return {
|
|
108
|
-
...node,
|
|
109
|
-
id: ""
|
|
110
|
-
};
|
|
111
|
-
};
|
|
112
|
-
const makeAttribute = (name, value) => ({
|
|
113
|
-
name,
|
|
114
|
-
value,
|
|
115
|
-
span: zeroSpan,
|
|
116
|
-
valueSpan: zeroSpan
|
|
117
|
-
});
|
|
118
|
-
const classAttr = (value) => makeAttribute("class", {
|
|
119
|
-
kind: "string",
|
|
120
|
-
value
|
|
121
|
-
});
|
|
122
|
-
const sourceRevision = (source) => {
|
|
123
|
-
let hash = 2166136261;
|
|
124
|
-
for (let index = 0; index < source.length; index++) {
|
|
125
|
-
hash ^= source.charCodeAt(index);
|
|
126
|
-
hash = Math.imul(hash, 16777619);
|
|
127
|
-
}
|
|
128
|
-
return `sml:${(hash >>> 0).toString(16).padStart(8, "0")}`;
|
|
129
|
-
};
|
|
130
|
-
const normalizeSml = (source) => {
|
|
131
|
-
const parsed = parseSml(source);
|
|
132
|
-
if (Result.isFailure(parsed)) return Result.fail(parsed.failure);
|
|
133
|
-
return Result.succeed(printSml(parsed.success));
|
|
134
|
-
};
|
|
135
|
-
const findElement = (node, nodeId) => {
|
|
136
|
-
if (node.kind !== "element") return null;
|
|
137
|
-
if (node.id === nodeId) return node;
|
|
138
|
-
for (const child of node.children) {
|
|
139
|
-
const found = findElement(child, nodeId);
|
|
140
|
-
if (found !== null) return found;
|
|
141
|
-
}
|
|
142
|
-
return null;
|
|
143
|
-
};
|
|
144
|
-
const mapElement = (node, targetId, transform) => {
|
|
145
|
-
if (node.kind !== "element") return node;
|
|
146
|
-
if (node.id === targetId) return transform(node);
|
|
147
|
-
return {
|
|
148
|
-
...node,
|
|
149
|
-
children: node.children.map((child) => mapElement(child, targetId, transform))
|
|
150
|
-
};
|
|
151
|
-
};
|
|
152
|
-
const transformElement = (document, targetId, transform) => {
|
|
153
|
-
if (findElement(document.root, targetId) === null) return Result.fail([diagnostic("op-target-not-found", `node "${targetId}" was not found in the current source`)]);
|
|
154
|
-
return Result.succeed({
|
|
155
|
-
...document,
|
|
156
|
-
root: mapElement(document.root, targetId, transform)
|
|
157
|
-
});
|
|
158
|
-
};
|
|
159
|
-
const setElementAttribute = (element, name, value) => {
|
|
160
|
-
const rest = element.attributes.filter((attribute) => attribute.name !== name);
|
|
161
|
-
if (value === null || value.kind === "string" && value.value === "") return {
|
|
162
|
-
...element,
|
|
163
|
-
attributes: rest
|
|
164
|
-
};
|
|
165
|
-
const next = makeAttribute(name, value);
|
|
166
|
-
const attributes = element.attributes.some((attribute) => attribute.name === name) ? element.attributes.map((attribute) => attribute.name === name ? next : cloneAttribute(attribute)) : [...element.attributes, next];
|
|
167
|
-
return {
|
|
168
|
-
...element,
|
|
169
|
-
attributes
|
|
170
|
-
};
|
|
171
|
-
};
|
|
172
|
-
const setImageSchemeSource = (document, nodeId, scheme, value) => transformElement(document, nodeId, (element) => {
|
|
173
|
-
if (element.tag !== "Image") return element;
|
|
174
|
-
const src = value?.kind === "string" ? value.value : "";
|
|
175
|
-
const children = element.children.filter((child) => !(child.kind === "element" && child.tag === "Source" && child.attributes.some((attribute) => attribute.name === "scheme" && attribute.value.kind === "string" && attribute.value.value === scheme)));
|
|
176
|
-
if (src === "") return {
|
|
177
|
-
...element,
|
|
178
|
-
children
|
|
179
|
-
};
|
|
180
|
-
const sourceNode = {
|
|
181
|
-
kind: "element",
|
|
182
|
-
id: "",
|
|
183
|
-
tag: "Source",
|
|
184
|
-
attributes: [makeAttribute("scheme", {
|
|
185
|
-
kind: "string",
|
|
186
|
-
value: scheme
|
|
187
|
-
}), makeAttribute("src", {
|
|
188
|
-
kind: "string",
|
|
189
|
-
value: src
|
|
190
|
-
})],
|
|
191
|
-
children: [],
|
|
192
|
-
span: zeroSpan
|
|
193
|
-
};
|
|
194
|
-
return {
|
|
195
|
-
...element,
|
|
196
|
-
children: [...children, sourceNode]
|
|
197
|
-
};
|
|
198
|
-
});
|
|
199
|
-
const setProp = (document, op) => {
|
|
200
|
-
if (op.name === "src:dark") return setImageSchemeSource(document, op.nodeId, "dark", op.value);
|
|
201
|
-
return transformElement(document, op.nodeId, (element) => {
|
|
202
|
-
const value = op.name === "class" && op.value?.kind === "string" ? {
|
|
203
|
-
kind: "string",
|
|
204
|
-
value: op.value.value.trim().replace(/\s+/g, " ")
|
|
205
|
-
} : op.value;
|
|
206
|
-
return setElementAttribute(element, op.name, value);
|
|
207
|
-
});
|
|
208
|
-
};
|
|
209
|
-
const setChildren = (document, op) => transformElement(document, op.nodeId, (element) => ({
|
|
210
|
-
...element,
|
|
211
|
-
children: [...op.children]
|
|
212
|
-
}));
|
|
213
|
-
const makeColumn = () => ({
|
|
214
|
-
kind: "element",
|
|
215
|
-
id: "",
|
|
216
|
-
tag: "Column",
|
|
217
|
-
attributes: [classAttr("px-8")],
|
|
218
|
-
children: [{
|
|
219
|
-
kind: "element",
|
|
220
|
-
id: "",
|
|
221
|
-
tag: "Text",
|
|
222
|
-
attributes: [classAttr("text-base text-gray-700")],
|
|
223
|
-
children: [{
|
|
224
|
-
kind: "text",
|
|
225
|
-
id: "",
|
|
226
|
-
value: "Column",
|
|
227
|
-
span: zeroSpan
|
|
228
|
-
}],
|
|
229
|
-
span: zeroSpan
|
|
230
|
-
}],
|
|
231
|
-
span: zeroSpan
|
|
232
|
-
});
|
|
233
|
-
const makeBlock = (tag) => {
|
|
234
|
-
const base = (attributes, children) => ({
|
|
235
|
-
kind: "element",
|
|
236
|
-
id: "",
|
|
237
|
-
tag,
|
|
238
|
-
attributes,
|
|
239
|
-
children,
|
|
240
|
-
span: zeroSpan
|
|
241
|
-
});
|
|
242
|
-
const text = (value) => ({
|
|
243
|
-
kind: "text",
|
|
244
|
-
id: "",
|
|
245
|
-
value,
|
|
246
|
-
span: zeroSpan
|
|
247
|
-
});
|
|
248
|
-
switch (tag) {
|
|
249
|
-
case "Heading": return base([makeAttribute("level", {
|
|
250
|
-
kind: "number",
|
|
251
|
-
value: 2
|
|
252
|
-
}), classAttr("text-xl text-gray-900")], [text("Heading")]);
|
|
253
|
-
case "Text": return base([classAttr("text-base text-gray-700")], [text("Add your text here.")]);
|
|
254
|
-
case "Button": return base([makeAttribute("href", {
|
|
255
|
-
kind: "string",
|
|
256
|
-
value: "https://example.com"
|
|
257
|
-
}), classAttr("bg-blue-600 text-white px-20 py-12 rounded-md")], [text("Button")]);
|
|
258
|
-
case "Image": return base([
|
|
259
|
-
makeAttribute("src", {
|
|
260
|
-
kind: "string",
|
|
261
|
-
value: "https://placehold.co/600x200"
|
|
262
|
-
}),
|
|
263
|
-
makeAttribute("alt", {
|
|
264
|
-
kind: "string",
|
|
265
|
-
value: ""
|
|
266
|
-
}),
|
|
267
|
-
makeAttribute("width", {
|
|
268
|
-
kind: "number",
|
|
269
|
-
value: 600
|
|
270
|
-
})
|
|
271
|
-
], []);
|
|
272
|
-
case "Divider": return base([classAttr("border-gray-200")], []);
|
|
273
|
-
case "Spacer": return base([makeAttribute("height", {
|
|
274
|
-
kind: "number",
|
|
275
|
-
value: 24
|
|
276
|
-
})], []);
|
|
277
|
-
case "Section": return base([classAttr("py-16")], [makeBlock("Text")]);
|
|
278
|
-
case "Container": return base([classAttr("bg-white p-24")], [makeBlock("Text")]);
|
|
279
|
-
case "Row": return base([], [makeColumn(), makeColumn()]);
|
|
280
|
-
default: return base([], []);
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
const elementChildIndexToNodeIndex = (children, insertionIndex) => {
|
|
284
|
-
if (insertionIndex <= 0) {
|
|
285
|
-
const firstElement = children.findIndex((child) => child.kind === "element");
|
|
286
|
-
return firstElement === -1 ? children.length : firstElement;
|
|
287
|
-
}
|
|
288
|
-
let elementIndex = 0;
|
|
289
|
-
for (let index = 0; index < children.length; index++) {
|
|
290
|
-
if (children[index]?.kind !== "element") continue;
|
|
291
|
-
if (elementIndex === insertionIndex) return index;
|
|
292
|
-
elementIndex++;
|
|
293
|
-
}
|
|
294
|
-
return children.length;
|
|
295
|
-
};
|
|
296
|
-
const insertChildren = (document, childrenToInsert, referenceId, position) => {
|
|
297
|
-
if (childrenToInsert.length === 0) return Result.fail([diagnostic("op-empty-insert", "insert operation produced no element nodes")]);
|
|
298
|
-
if (referenceId === null || referenceId === document.root.id) {
|
|
299
|
-
const children = position === "before" ? [...childrenToInsert, ...document.root.children] : [...document.root.children, ...childrenToInsert];
|
|
300
|
-
return Result.succeed({
|
|
301
|
-
...document,
|
|
302
|
-
root: {
|
|
303
|
-
...document.root,
|
|
304
|
-
children
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
if (findElement(document.root, referenceId) === null) return Result.fail([diagnostic("op-target-not-found", `node "${referenceId}" was not found in the current source`)]);
|
|
309
|
-
if (position === "inside") return transformElement(document, referenceId, (element) => ({
|
|
310
|
-
...element,
|
|
311
|
-
children: [...element.children, ...childrenToInsert]
|
|
312
|
-
}));
|
|
313
|
-
const insertInParent = (node) => {
|
|
314
|
-
if (node.kind !== "element") return node;
|
|
315
|
-
const index = node.children.findIndex((child) => child.kind === "element" && child.id === referenceId);
|
|
316
|
-
if (index >= 0) {
|
|
317
|
-
const at = position === "after" ? index + 1 : index;
|
|
318
|
-
return {
|
|
319
|
-
...node,
|
|
320
|
-
children: [
|
|
321
|
-
...node.children.slice(0, at),
|
|
322
|
-
...childrenToInsert,
|
|
323
|
-
...node.children.slice(at)
|
|
324
|
-
]
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
return {
|
|
328
|
-
...node,
|
|
329
|
-
children: node.children.map(insertInParent)
|
|
330
|
-
};
|
|
331
|
-
};
|
|
332
|
-
return Result.succeed({
|
|
333
|
-
...document,
|
|
334
|
-
root: insertInParent(document.root)
|
|
335
|
-
});
|
|
336
|
-
};
|
|
337
|
-
const insertBlock = (document, op) => insertChildren(document, [makeBlock(op.tag)], op.referenceId, op.position);
|
|
338
|
-
const insertSnippet = (document, op) => {
|
|
339
|
-
const rootTag = document.root.tag;
|
|
340
|
-
const parsed = parseSml(`<${rootTag}>${op.snippet}</${rootTag}>`);
|
|
341
|
-
if (Result.isFailure(parsed)) return Result.fail(parsed.failure.diagnostics);
|
|
342
|
-
const children = parsed.success.root.children.filter((child) => child.kind === "element");
|
|
343
|
-
return insertChildren(document, children, op.referenceId, op.position);
|
|
344
|
-
};
|
|
345
|
-
const removeNodeById = (document, nodeId) => {
|
|
346
|
-
if (document.root.id === nodeId) return Result.fail([diagnostic("op-root-target", "the document root cannot be removed")]);
|
|
347
|
-
if (findElement(document.root, nodeId) === null) return Result.fail([diagnostic("op-target-not-found", `node "${nodeId}" was not found in the current source`)]);
|
|
348
|
-
const prune = (node) => {
|
|
349
|
-
if (node.kind !== "element") return node;
|
|
350
|
-
return {
|
|
351
|
-
...node,
|
|
352
|
-
children: node.children.filter((child) => !(child.kind === "element" && child.id === nodeId)).map(prune)
|
|
353
|
-
};
|
|
354
|
-
};
|
|
355
|
-
return Result.succeed({
|
|
356
|
-
...document,
|
|
357
|
-
root: prune(document.root)
|
|
358
|
-
});
|
|
359
|
-
};
|
|
360
|
-
const isDescendant = (ancestor, nodeId) => ancestor.children.some((child) => {
|
|
361
|
-
if (child.kind !== "element") return false;
|
|
362
|
-
return child.id === nodeId || isDescendant(child, nodeId);
|
|
363
|
-
});
|
|
364
|
-
const removeElement = (node, nodeId) => {
|
|
365
|
-
let removed = null;
|
|
366
|
-
const children = [];
|
|
367
|
-
for (const child of node.children) {
|
|
368
|
-
if (child.kind === "element" && child.id === nodeId) {
|
|
369
|
-
removed = child;
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
if (child.kind === "element") {
|
|
373
|
-
const result = removeElement(child, nodeId);
|
|
374
|
-
if (result.removed !== null) removed = result.removed;
|
|
375
|
-
children.push(result.node);
|
|
376
|
-
} else children.push(child);
|
|
377
|
-
}
|
|
378
|
-
return {
|
|
379
|
-
node: {
|
|
380
|
-
...node,
|
|
381
|
-
children
|
|
382
|
-
},
|
|
383
|
-
removed
|
|
384
|
-
};
|
|
385
|
-
};
|
|
386
|
-
const insertIntoParent = (node, targetParentId, insertionIndex, moved) => {
|
|
387
|
-
if (node.id === targetParentId) {
|
|
388
|
-
const at = elementChildIndexToNodeIndex(node.children, insertionIndex);
|
|
389
|
-
return {
|
|
390
|
-
...node,
|
|
391
|
-
children: [
|
|
392
|
-
...node.children.slice(0, at),
|
|
393
|
-
moved,
|
|
394
|
-
...node.children.slice(at)
|
|
395
|
-
]
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
return {
|
|
399
|
-
...node,
|
|
400
|
-
children: node.children.map((child) => child.kind === "element" ? insertIntoParent(child, targetParentId, insertionIndex, moved) : child)
|
|
401
|
-
};
|
|
402
|
-
};
|
|
403
|
-
const moveNode = (document, op) => {
|
|
404
|
-
if (document.root.id === op.nodeId) return Result.fail([diagnostic("op-root-target", "the document root cannot be moved")]);
|
|
405
|
-
const moved = findElement(document.root, op.nodeId);
|
|
406
|
-
if (moved === null) return Result.fail([diagnostic("op-target-not-found", `node "${op.nodeId}" was not found in the current source`)]);
|
|
407
|
-
const parent = findElement(document.root, op.targetParentId);
|
|
408
|
-
if (parent === null) return Result.fail([diagnostic("op-target-not-found", `target parent "${op.targetParentId}" was not found in the current source`)]);
|
|
409
|
-
if (moved.id === parent.id || isDescendant(moved, parent.id)) return Result.fail([diagnostic("op-invalid-reparent", "a node cannot be moved into itself or its descendants")]);
|
|
410
|
-
const channel = documentChannel(document);
|
|
411
|
-
const childModel = CHANNEL_TAG_SPECS[channel][parent.tag]?.content;
|
|
412
|
-
if (!(childModel === void 0 ? [] : allowedChildren(childModel)).includes(moved.tag)) return Result.fail([diagnostic("op-invalid-reparent", `<${moved.tag}> is not allowed directly inside <${parent.tag}>`, parent.span)]);
|
|
413
|
-
const { node: rootWithoutMoved, removed } = removeElement(document.root, op.nodeId);
|
|
414
|
-
if (removed === null) return Result.fail([diagnostic("op-target-not-found", `node "${op.nodeId}" was not found in the current source`)]);
|
|
415
|
-
return Result.succeed({
|
|
416
|
-
...document,
|
|
417
|
-
root: insertIntoParent(rootWithoutMoved, op.targetParentId, op.insertionIndex, removed)
|
|
418
|
-
});
|
|
419
|
-
};
|
|
420
|
-
const duplicateNode = (document, nodeId) => {
|
|
421
|
-
if (document.root.id === nodeId) return Result.fail([diagnostic("op-root-target", "the document root cannot be duplicated")]);
|
|
422
|
-
if (findElement(document.root, nodeId) === null) return Result.fail([diagnostic("op-target-not-found", `node "${nodeId}" was not found in the current source`)]);
|
|
423
|
-
const insertClone = (node) => {
|
|
424
|
-
if (node.kind !== "element") return node;
|
|
425
|
-
const index = node.children.findIndex((child) => child.kind === "element" && child.id === nodeId);
|
|
426
|
-
if (index >= 0) {
|
|
427
|
-
const clone = cloneSubtree(node.children[index]);
|
|
428
|
-
return {
|
|
429
|
-
...node,
|
|
430
|
-
children: [
|
|
431
|
-
...node.children.slice(0, index + 1),
|
|
432
|
-
clone,
|
|
433
|
-
...node.children.slice(index + 1)
|
|
434
|
-
]
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
return {
|
|
438
|
-
...node,
|
|
439
|
-
children: node.children.map(insertClone)
|
|
440
|
-
};
|
|
441
|
-
};
|
|
442
|
-
return Result.succeed({
|
|
443
|
-
...document,
|
|
444
|
-
root: insertClone(document.root)
|
|
445
|
-
});
|
|
446
|
-
};
|
|
447
|
-
const mutate = (document, op) => {
|
|
448
|
-
switch (op._tag) {
|
|
449
|
-
case "setProp": return setProp(document, op);
|
|
450
|
-
case "setChildren": return setChildren(document, op);
|
|
451
|
-
case "insertBlock": return insertBlock(document, op);
|
|
452
|
-
case "insertSnippet": return insertSnippet(document, op);
|
|
453
|
-
case "moveNode": return moveNode(document, op);
|
|
454
|
-
case "duplicateNode": return duplicateNode(document, op.nodeId);
|
|
455
|
-
case "removeNode": return removeNodeById(document, op.nodeId);
|
|
456
|
-
}
|
|
457
|
-
};
|
|
458
|
-
const preflight = (source) => {
|
|
459
|
-
const reparsed = parseSml(source);
|
|
460
|
-
if (Result.isFailure(reparsed)) return Result.fail(reparsed.failure.diagnostics);
|
|
461
|
-
const compiled = compileSml(reparsed.success);
|
|
462
|
-
if (Result.isFailure(compiled)) return Result.fail(compiled.failure.diagnostics);
|
|
463
|
-
const compatibilityErrors = checkSmlCompatibility({ source }).filter(isBreakingCompatibilityCheck).map((message) => diagnostic("compatibility-error", message));
|
|
464
|
-
if (compatibilityErrors.length > 0) return Result.fail(compatibilityErrors);
|
|
465
|
-
return Result.succeed(reparsed.success);
|
|
466
|
-
};
|
|
467
|
-
const failedResult = (index, op, diagnostics) => ({
|
|
468
|
-
index,
|
|
469
|
-
status: "failed",
|
|
470
|
-
op,
|
|
471
|
-
diagnostics: [...diagnostics]
|
|
472
|
-
});
|
|
473
|
-
/**
|
|
474
|
-
* Apply a batch of ops to SML source, returning the rewritten source, its new
|
|
475
|
-
* revision, and a per-op result.
|
|
476
|
-
*
|
|
477
|
-
* Id contract: every id in the batch resolves against the id-space of the
|
|
478
|
-
* source the caller read (the `expectedRevision` snapshot). Ids are positional
|
|
479
|
-
* (`n0..nN` in document order) and are NOT rewritten between ops, so:
|
|
480
|
-
*
|
|
481
|
-
* - An op cannot target a node created earlier in the same batch — freshly
|
|
482
|
-
* inserted/duplicated nodes carry a blank id until the next reprint reassigns
|
|
483
|
-
* positional ids, and are unaddressable within the batch (fails with
|
|
484
|
-
* `op-target-not-found`).
|
|
485
|
-
* - After `duplicateNode`, the duplicated id still addresses the ORIGINAL (the
|
|
486
|
-
* first match in document order); the clone is a blank-id copy.
|
|
487
|
-
* - Structural ops (insert/remove/move) do not shift the ids of the nodes that
|
|
488
|
-
* survive, so a later op targeting a surviving sibling lands on the intended
|
|
489
|
-
* node regardless of what earlier ops did.
|
|
490
|
-
*
|
|
491
|
-
* Each op is still preflighted (reprint, reparse, compile, compatibility check)
|
|
492
|
-
* as an independent validity gate; a failing op is recorded as `failed` and
|
|
493
|
-
* does not advance the working document.
|
|
494
|
-
*/
|
|
495
|
-
const applyOps = (source, ops, options) => {
|
|
496
|
-
const parsed = parseSml(source);
|
|
497
|
-
if (Result.isFailure(parsed)) return Result.fail(new SmlOpError({ diagnostics: parsed.failure.diagnostics }));
|
|
498
|
-
const currentRevision = String(options?.currentRevision ?? sourceRevision(source));
|
|
499
|
-
if (options?.expectedRevision !== void 0 && String(options.expectedRevision) !== currentRevision) {
|
|
500
|
-
const stale = diagnostic("stale-revision", `source revision changed from ${String(options.expectedRevision)} to ${currentRevision}`);
|
|
501
|
-
return Result.succeed({
|
|
502
|
-
source,
|
|
503
|
-
revision: currentRevision,
|
|
504
|
-
diagnostics: [stale],
|
|
505
|
-
results: ops.map((op, index) => failedResult(index, op, [stale]))
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
let currentSource = source;
|
|
509
|
-
let currentDocument = parsed.success;
|
|
510
|
-
const results = [];
|
|
511
|
-
const diagnostics = [];
|
|
512
|
-
ops.forEach((op, index) => {
|
|
513
|
-
const mutated = mutate(currentDocument, op);
|
|
514
|
-
if (Result.isFailure(mutated)) {
|
|
515
|
-
results.push(failedResult(index, op, mutated.failure));
|
|
516
|
-
diagnostics.push(...mutated.failure);
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
const candidateSource = printSml(mutated.success);
|
|
520
|
-
const checked = preflight(candidateSource);
|
|
521
|
-
if (Result.isFailure(checked)) {
|
|
522
|
-
results.push(failedResult(index, op, checked.failure));
|
|
523
|
-
diagnostics.push(...checked.failure);
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
currentSource = candidateSource;
|
|
527
|
-
currentDocument = mutated.success;
|
|
528
|
-
results.push({
|
|
529
|
-
index,
|
|
530
|
-
status: "applied",
|
|
531
|
-
op,
|
|
532
|
-
diagnostics: []
|
|
533
|
-
});
|
|
534
|
-
});
|
|
535
|
-
return Result.succeed({
|
|
536
|
-
source: currentSource,
|
|
537
|
-
revision: sourceRevision(currentSource),
|
|
538
|
-
diagnostics,
|
|
539
|
-
results
|
|
540
|
-
});
|
|
541
|
-
};
|
|
542
|
-
const applyOp = (source, op, options) => applyOps(source, [op], options);
|
|
543
|
-
//#endregion
|
|
544
|
-
export { ApplyOpsResultSchema, SmlOpError, SmlOpResultSchema, SmlOpSchema, applyOp, applyOps, normalizeSml, sourceRevision };
|
|
545
|
-
|
|
546
|
-
//# sourceMappingURL=ops.js.map
|
|
1
|
+
import { a as sourceRevision, i as normalizeSml, n as applyOp, r as applyOps, t as SmlOpError } from "./ops-2aiiYO7m.js";
|
|
2
|
+
export { SmlOpError, applyOp, applyOps, normalizeSml, sourceRevision };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Result } from "
|
|
1
|
+
import { i as SmlParseError } from "./diagnostics-OcQKQYSY.js";
|
|
2
|
+
import { S as SmlTag, b as SmlDocument, l as Channel } from "./ast-BrDtApwS.js";
|
|
3
|
+
import { Result } from "better-result";
|
|
4
4
|
//#region src/limits.d.ts
|
|
5
5
|
/** Hard ceilings shared by SML documents and standalone predicate parsing. */
|
|
6
6
|
declare const SML_PARSER_LIMITS: {
|
|
@@ -390,7 +390,7 @@ declare const UNIVERSAL_ATTRIBUTES: {
|
|
|
390
390
|
declare const UNIVERSAL_ATTRIBUTE_TAGS: ReadonlySet<SmlTag>;
|
|
391
391
|
/** Tags a content model admits as direct children (public alongside the spec tables). */
|
|
392
392
|
declare const allowedChildren: (model: ContentModel) => ReadonlyArray<SmlTag>;
|
|
393
|
-
declare const parseSml: (source: string) => Result
|
|
393
|
+
declare const parseSml: (source: string) => Result<SmlDocument, SmlParseError>;
|
|
394
394
|
//#endregion
|
|
395
395
|
export { SMS_TAG_SPECS as a, UNIVERSAL_ATTRIBUTE_TAGS as c, parseSml as d, SML_PARSER_LIMITS as f, EMAIL_TAG_SPECS as i, WHATSAPP_TAG_SPECS as l, CHANNEL_TAG_SPECS as n, TagSpec as o, ContentModel as r, UNIVERSAL_ATTRIBUTES as s, AttributeSpec as t, allowedChildren as u };
|
|
396
|
-
//# sourceMappingURL=parser-
|
|
396
|
+
//# sourceMappingURL=parser-CQcASEyy.d.ts.map
|
package/dist/parser.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as SMS_TAG_SPECS, c as UNIVERSAL_ATTRIBUTE_TAGS, d as parseSml, f as SML_PARSER_LIMITS, i as EMAIL_TAG_SPECS, l as WHATSAPP_TAG_SPECS, n as CHANNEL_TAG_SPECS, o as TagSpec, r as ContentModel, s as UNIVERSAL_ATTRIBUTES, t as AttributeSpec, u as allowedChildren } from "./parser-
|
|
1
|
+
import { a as SMS_TAG_SPECS, c as UNIVERSAL_ATTRIBUTE_TAGS, d as parseSml, f as SML_PARSER_LIMITS, i as EMAIL_TAG_SPECS, l as WHATSAPP_TAG_SPECS, n as CHANNEL_TAG_SPECS, o as TagSpec, r as ContentModel, s as UNIVERSAL_ATTRIBUTES, t as AttributeSpec, u as allowedChildren } from "./parser-CQcASEyy.js";
|
|
2
2
|
export { AttributeSpec, CHANNEL_TAG_SPECS, ContentModel, EMAIL_TAG_SPECS, SML_PARSER_LIMITS, SMS_TAG_SPECS, TagSpec, UNIVERSAL_ATTRIBUTES, UNIVERSAL_ATTRIBUTE_TAGS, WHATSAPP_TAG_SPECS, allowedChildren, parseSml };
|
package/dist/parser.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { SmlParseError } from "./diagnostics.js";
|
|
2
|
-
import { a as printPredicate, i as parsePredicate, o as SML_PARSER_LIMITS } from "./predicate-
|
|
2
|
+
import { a as printPredicate, i as parsePredicate, o as SML_PARSER_LIMITS } from "./predicate-BI6uRRM-.js";
|
|
3
3
|
import { CHANNEL_BY_ROOT_TAG, ROOT_TAGS, SML_TAGS, isRootTag } from "./ast.js";
|
|
4
4
|
import { TokenKind, createLexer } from "./lexer.js";
|
|
5
|
-
import { Result } from "
|
|
5
|
+
import { Result } from "better-result";
|
|
6
6
|
//#region src/parser.ts
|
|
7
|
-
const parserLimitFailure = (code, message, sourceLength) => Result.
|
|
7
|
+
const parserLimitFailure = (code, message, sourceLength) => Result.err(new SmlParseError({ diagnostics: [{
|
|
8
8
|
code,
|
|
9
9
|
message,
|
|
10
10
|
span: {
|
|
@@ -649,15 +649,15 @@ const parseSml = (source) => {
|
|
|
649
649
|
const testAttribute = element.attributes[testIndex];
|
|
650
650
|
if (testAttribute.value.kind === "string") {
|
|
651
651
|
const parsed = parsePredicate(testAttribute.value.value, testAttribute.valueSpan.start);
|
|
652
|
-
if (Result.
|
|
653
|
-
const first = parsed.
|
|
652
|
+
if (Result.isError(parsed)) {
|
|
653
|
+
const first = parsed.error[0];
|
|
654
654
|
return fail(first.code, first.message, first.span);
|
|
655
655
|
}
|
|
656
656
|
element.attributes[testIndex] = {
|
|
657
657
|
...testAttribute,
|
|
658
658
|
value: {
|
|
659
659
|
kind: "string",
|
|
660
|
-
value: printPredicate(parsed.
|
|
660
|
+
value: printPredicate(parsed.value)
|
|
661
661
|
}
|
|
662
662
|
};
|
|
663
663
|
}
|
|
@@ -815,7 +815,7 @@ const parseSml = (source) => {
|
|
|
815
815
|
root = null;
|
|
816
816
|
}
|
|
817
817
|
}
|
|
818
|
-
if (root === null || diagnostic !== void 0) return Result.
|
|
818
|
+
if (root === null || diagnostic !== void 0) return Result.err(new SmlParseError({ diagnostics: [diagnostic ?? {
|
|
819
819
|
code: "parse-failed",
|
|
820
820
|
message: "parse failed",
|
|
821
821
|
span: {
|
|
@@ -824,7 +824,7 @@ const parseSml = (source) => {
|
|
|
824
824
|
}
|
|
825
825
|
}] }));
|
|
826
826
|
assignNodeIds(root);
|
|
827
|
-
return Result.
|
|
827
|
+
return Result.ok({
|
|
828
828
|
kind: "document",
|
|
829
829
|
root
|
|
830
830
|
});
|