@plitzi/sdk-schema 0.33.2 → 0.34.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/CHANGELOG.md +14 -0
- package/dist/SchemaReducer.d.ts +7 -4
- package/dist/SchemaReducer.mjs +103 -85
- package/dist/helpers/FlatMap.d.ts +57 -16
- package/dist/helpers/FlatMap.mjs +86 -91
- package/dist/helpers/elementId.d.ts +73 -0
- package/dist/helpers/elementId.mjs +114 -0
- package/dist/helpers/schemaValidator.d.ts +8 -0
- package/dist/helpers/schemaValidator.mjs +117 -51
- package/package.json +9 -9
- package/dist/helpers/idRef.d.ts +0 -34
- package/dist/helpers/idRef.mjs +0 -72
package/dist/helpers/FlatMap.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { validateSchema as
|
|
3
|
-
import { get as
|
|
4
|
-
import { generateID as c } from "@plitzi/sdk-shared/helpers/utils";
|
|
1
|
+
import { elementIdConflict as e, elementIdsFree as t, randomElementId as n, repointIds as r, takenIds as i, uniqueElementId as a } from "./elementId.mjs";
|
|
2
|
+
import { validateSchema as o } from "./schemaValidator.mjs";
|
|
3
|
+
import { get as s, set as c } from "@plitzi/plitzi-ui/helpers";
|
|
5
4
|
import { EMPTY_SCHEMA as l, VARIABLE_REGEX as u } from "@plitzi/sdk-shared/schema/schemaConstants";
|
|
6
5
|
import { EMPTY_STYLE_SCHEMA as d } from "@plitzi/sdk-shared/style/styleConstants";
|
|
7
6
|
import f from "@plitzi/sdk-style/helpers/calculateInheriting";
|
|
@@ -9,41 +8,50 @@ import f from "@plitzi/sdk-style/helpers/calculateInheriting";
|
|
|
9
8
|
var p = class {
|
|
10
9
|
flat;
|
|
11
10
|
variables;
|
|
11
|
+
pages;
|
|
12
|
+
mintId;
|
|
12
13
|
constructor(e = {}) {
|
|
13
|
-
let { flat: t, variables:
|
|
14
|
+
let { flat: t, variables: r, pages: i, mintId: a } = e;
|
|
14
15
|
if (!t) throw Error("Flat is required");
|
|
15
|
-
this.flat = t, this.variables =
|
|
16
|
+
this.flat = t, this.variables = r ?? [], this.pages = i ?? [], this.mintId = a ?? n;
|
|
16
17
|
}
|
|
18
|
+
nextId = (e, t = () => !1) => {
|
|
19
|
+
let n = this.takenIds();
|
|
20
|
+
return this.mintId(e, (e) => n.has(e) || t(e));
|
|
21
|
+
};
|
|
17
22
|
addElement = (e, n, r = "inside", i = {}) => {
|
|
18
|
-
let a
|
|
23
|
+
let a = e.id ? e : {
|
|
24
|
+
...e,
|
|
25
|
+
id: this.nextId(e.definition.type)
|
|
26
|
+
}, o;
|
|
19
27
|
if (r !== "custom") {
|
|
20
28
|
if (r !== "inside") {
|
|
21
29
|
let e = this.flat[n];
|
|
22
30
|
if (!e) return !1;
|
|
23
|
-
e.definition.parentId && (
|
|
24
|
-
} else
|
|
25
|
-
if (!
|
|
31
|
+
e.definition.parentId && (o = this.flat[e.definition.parentId]);
|
|
32
|
+
} else o = this.flat[n];
|
|
33
|
+
if (!o) return !1;
|
|
26
34
|
}
|
|
27
|
-
if (r !== "custom" &&
|
|
28
|
-
switch (
|
|
35
|
+
if (r !== "custom" && !Array.isArray(s(o, "definition.items")) || !this.isValidElement(a) || !t(this.flat, [a, ...Object.values(i)])) return !1;
|
|
36
|
+
switch (c(this.flat, a.id, a), r) {
|
|
29
37
|
case "left":
|
|
30
38
|
case "top": {
|
|
31
|
-
let
|
|
32
|
-
if (
|
|
33
|
-
|
|
39
|
+
let e = s(o, "definition.items", []);
|
|
40
|
+
if (e.splice(e.findIndex((e) => e === n), 0, a.id), !o) return !1;
|
|
41
|
+
c(this.flat, `${o.id}.definition.items`, e), c(this.flat, `${a.id}.definition.parentId`, o.id), c(this.flat, `${a.id}.definition.rootId`, o.definition.rootId);
|
|
34
42
|
break;
|
|
35
43
|
}
|
|
36
44
|
case "right":
|
|
37
45
|
case "bottom": {
|
|
38
|
-
let
|
|
39
|
-
if (
|
|
40
|
-
|
|
46
|
+
let e = s(o, "definition.items", []);
|
|
47
|
+
if (e.splice(e.findIndex((e) => e === n) + 1, 0, a.id), !o) return !1;
|
|
48
|
+
c(this.flat, `${o.id}.definition.items`, e), c(this.flat, `${a.id}.definition.parentId`, o.id), c(this.flat, `${a.id}.definition.rootId`, o.definition.rootId);
|
|
41
49
|
break;
|
|
42
50
|
}
|
|
43
51
|
case "inside": {
|
|
44
|
-
let
|
|
45
|
-
if (!
|
|
46
|
-
|
|
52
|
+
let e = s(o, "definition.items", []);
|
|
53
|
+
if (!o) return !1;
|
|
54
|
+
c(this.flat, `${n}.definition.items`, [...e, a.id]), c(this.flat, `${a.id}.definition.parentId`, n), c(this.flat, `${a.id}.definition.rootId`, o.definition.rootId);
|
|
47
55
|
break;
|
|
48
56
|
}
|
|
49
57
|
case "custom": break;
|
|
@@ -53,24 +61,22 @@ var p = class {
|
|
|
53
61
|
this.flat[e] = i[e];
|
|
54
62
|
}), !0;
|
|
55
63
|
};
|
|
56
|
-
updateElement = (e) =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return i && !t(this.flat, [e]) ? !1 : i && n && e.idRef ? (this.flat[e.id] = structuredClone(e), r(this.flat, { [n]: e.idRef }), !0) : (this.flat[e.id] = e, !0);
|
|
60
|
-
};
|
|
64
|
+
updateElement = (e) => !e || !this.flat[e.id] ? !1 : (this.flat[e.id] = e, !0);
|
|
65
|
+
renameElement = (t, n) => this.flat[t] ? t === n ? [] : !e(this.flat, n) && r(this.flat, { [t]: n }, this.pages) : !1;
|
|
66
|
+
renameConflict = (t, n) => e(this.flat, n, t);
|
|
61
67
|
moveElement = (e, t, n, r = "inside") => {
|
|
62
68
|
if (n === t || !this.flat[e]) return !1;
|
|
63
69
|
let i = this.flat[t];
|
|
64
70
|
if (!i) return !1;
|
|
65
71
|
let a = this.flat[t];
|
|
66
72
|
for (; a;) {
|
|
67
|
-
let e =
|
|
73
|
+
let e = s(a, "definition.parentId");
|
|
68
74
|
if (!e) break;
|
|
69
75
|
if (a.id === n) return !1;
|
|
70
76
|
a = this.flat[e];
|
|
71
77
|
}
|
|
72
78
|
if (!a) return !1;
|
|
73
|
-
let
|
|
79
|
+
let o = s(this.flat, `${e}.definition.items`, []).filter((e) => e !== n);
|
|
74
80
|
if ([
|
|
75
81
|
"left",
|
|
76
82
|
"top",
|
|
@@ -80,64 +86,49 @@ var p = class {
|
|
|
80
86
|
if (!i.definition.parentId) return !1;
|
|
81
87
|
let a = this.flat[i.definition.parentId];
|
|
82
88
|
if (!a) return !1;
|
|
83
|
-
let l =
|
|
84
|
-
a.id === e && (l =
|
|
89
|
+
let l = s(a, "definition.items", []);
|
|
90
|
+
a.id === e && (l = o);
|
|
85
91
|
let u = l.findIndex((e) => e === t);
|
|
86
|
-
["right", "bottom"].includes(r) && u++, l.splice(u, 0, n),
|
|
92
|
+
["right", "bottom"].includes(r) && u++, l.splice(u, 0, n), c(this.flat, `${e}.definition.items`, o), c(this.flat, `${a.id}.definition.items`, l), c(this.flat, `${n}.definition.parentId`, a.id);
|
|
87
93
|
} else if (r === "inside") {
|
|
88
94
|
if (!this.flat[t]) return !1;
|
|
89
|
-
let r =
|
|
90
|
-
e === t && (r =
|
|
95
|
+
let r = s(this.flat, `${t}.definition.items`, []);
|
|
96
|
+
e === t && (r = o), r = [...r, n], c(this.flat, `${e}.definition.items`, o), c(this.flat, `${t}.definition.items`, r), c(this.flat, `${n}.definition.parentId`, t);
|
|
91
97
|
}
|
|
92
98
|
return !0;
|
|
93
99
|
};
|
|
94
|
-
getElement = (e) =>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
cloneElements = (e, t = "",
|
|
98
|
-
let
|
|
100
|
+
getElement = (e) => s(this.flat, e);
|
|
101
|
+
takenIds = () => i(this.flat);
|
|
102
|
+
elementIdConflict = (t, n) => e(this.flat, t, n);
|
|
103
|
+
cloneElements = (e, t = "", n = "", i = !1) => {
|
|
104
|
+
let o = {
|
|
99
105
|
acum: {},
|
|
100
106
|
item: void 0
|
|
101
107
|
}, l = {}, u = this.flat[e];
|
|
102
|
-
if (!u) return
|
|
103
|
-
let d =
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
} : {
|
|
115
|
-
...e,
|
|
116
|
-
[n.id]: n
|
|
117
|
-
}) : e;
|
|
118
|
-
}, {});
|
|
119
|
-
try {
|
|
120
|
-
let t = JSON.stringify(f);
|
|
121
|
-
t = Object.keys(l).reduce((e, t) => e.replace(new RegExp(t, "g"), l[t]), t), a.acum = JSON.parse(t), a.item = a.acum[l[e]], n(a.acum, (e) => d.has(e));
|
|
122
|
-
} catch (e) {
|
|
123
|
-
return console.error("Error parsing elements", e), {
|
|
124
|
-
acum: {},
|
|
125
|
-
item: void 0
|
|
126
|
-
};
|
|
108
|
+
if (!u) return o;
|
|
109
|
+
let d = [e, ...this.childTree(e)].filter((e) => this.flat[e] !== void 0), f = this.takenIds();
|
|
110
|
+
for (let e of d) {
|
|
111
|
+
let t = a(e, (e) => f.has(e));
|
|
112
|
+
f.add(t), l[e] = t;
|
|
113
|
+
}
|
|
114
|
+
let p = {};
|
|
115
|
+
for (let e of d) {
|
|
116
|
+
let t = structuredClone(this.flat[e]);
|
|
117
|
+
n && (t.definition.rootId = n), p[e] = t;
|
|
127
118
|
}
|
|
128
|
-
i && delete
|
|
129
|
-
let
|
|
130
|
-
return (!
|
|
119
|
+
r(p, l), o.acum = p, o.item = p[l[e]], i && delete o.acum[l[e]];
|
|
120
|
+
let m = this.flat[t];
|
|
121
|
+
return (!m || !Array.isArray(s(m, "definition.items"))) && (t = s(m, "definition.parentId", s(u, "definition.parentId"))), t && c(o, "item.definition.parentId", t), o;
|
|
131
122
|
};
|
|
132
123
|
removeElement = (e, t = !1) => {
|
|
133
124
|
let n = this.flat[e];
|
|
134
|
-
if (!n || n.definition.type === "page" && !t || t &&
|
|
135
|
-
let r =
|
|
125
|
+
if (!n || n.definition.type === "page" && !t || t && s(n, "attributes.default", !1)) return !1;
|
|
126
|
+
let r = s(n, "definition.items");
|
|
136
127
|
r && r.length > 0 && r.forEach((e) => this.removeElement(e));
|
|
137
|
-
let i =
|
|
128
|
+
let i = s(n, "definition.parentId"), a = i ? this.flat[i] : void 0;
|
|
138
129
|
if (i && a) {
|
|
139
130
|
let { definition: { items: t = [] } } = a;
|
|
140
|
-
|
|
131
|
+
c(a, "definition.items", t.filter((t) => t !== e)), this.flat[i] = a;
|
|
141
132
|
}
|
|
142
133
|
return delete this.flat[e], !0;
|
|
143
134
|
};
|
|
@@ -164,18 +155,18 @@ var p = class {
|
|
|
164
155
|
let t = this.flat[e], n = [];
|
|
165
156
|
if (!t) return n;
|
|
166
157
|
do {
|
|
167
|
-
if (
|
|
168
|
-
let e =
|
|
158
|
+
if (s(t, "definition.type") === "page") {
|
|
159
|
+
let e = s(t, "attributes.layout"), r = s(t, "attributes.layoutContainer");
|
|
169
160
|
e && r && n.push(r, ...this.parentTree(r));
|
|
170
161
|
}
|
|
171
|
-
e !== t.id && n.push(t.id), t =
|
|
162
|
+
e !== t.id && n.push(t.id), t = s(this.flat, s(t, "definition.parentId"), void 0);
|
|
172
163
|
} while (t);
|
|
173
164
|
return n;
|
|
174
165
|
};
|
|
175
166
|
childTree = (e) => {
|
|
176
167
|
let t = this.flat[e];
|
|
177
168
|
if (!t) return [];
|
|
178
|
-
let n = [], r =
|
|
169
|
+
let n = [], r = s(t, "definition.items");
|
|
179
170
|
return r && r.forEach((e) => n.push(e, ...this.childTree(e))), n;
|
|
180
171
|
};
|
|
181
172
|
isValidElement = (e) => {
|
|
@@ -202,7 +193,7 @@ var p = class {
|
|
|
202
193
|
elementsStyle: r,
|
|
203
194
|
variables: i
|
|
204
195
|
};
|
|
205
|
-
let a =
|
|
196
|
+
let a = s(this.flat, t);
|
|
206
197
|
if (!a) return {
|
|
207
198
|
elements: {
|
|
208
199
|
acum: {},
|
|
@@ -211,18 +202,18 @@ var p = class {
|
|
|
211
202
|
elementsStyle: r,
|
|
212
203
|
variables: i
|
|
213
204
|
};
|
|
214
|
-
let
|
|
215
|
-
return
|
|
205
|
+
let o = this.cloneElements(t, a.definition.parentId);
|
|
206
|
+
return o.item ? (Object.values(o.acum).forEach((t) => {
|
|
216
207
|
let { id: n } = t;
|
|
217
|
-
if (
|
|
208
|
+
if (c(o.acum, `${n}.definition.rootId`, o.item?.id), f(t, t.definition.type, this.flat, e.platform, {}, { includeSelf: !0 }).tree.forEach((t) => {
|
|
218
209
|
let { displayMode: n, name: i } = t;
|
|
219
210
|
!(i in r.platform[n]) && i in e.platform[n] && (r.platform[n][i] = e.platform[n][i]);
|
|
220
211
|
}), this.variables.length > 0) {
|
|
221
|
-
let t = this.getElementVariables(e, n,
|
|
212
|
+
let t = this.getElementVariables(e, n, o.acum);
|
|
222
213
|
i = [...i, ...t];
|
|
223
214
|
}
|
|
224
|
-
}),
|
|
225
|
-
elements:
|
|
215
|
+
}), c(o.acum, `${o.item.id}.definition.parentId`, null), n && delete o.acum[o.item.id], i.length > 1 && (i = [...new Set(i)]), {
|
|
216
|
+
elements: o,
|
|
226
217
|
elementsStyle: r,
|
|
227
218
|
variables: i
|
|
228
219
|
}) : {
|
|
@@ -234,14 +225,14 @@ var p = class {
|
|
|
234
225
|
variables: i
|
|
235
226
|
};
|
|
236
227
|
};
|
|
237
|
-
validate = () =>
|
|
228
|
+
validate = () => o({
|
|
238
229
|
...l.schema,
|
|
239
230
|
flat: this.flat,
|
|
240
231
|
variables: this.variables
|
|
241
232
|
});
|
|
242
233
|
isValid = () => this.validate().valid;
|
|
243
234
|
assertValid = (e) => {
|
|
244
|
-
let t =
|
|
235
|
+
let t = o({
|
|
245
236
|
...l.schema,
|
|
246
237
|
flat: this.flat,
|
|
247
238
|
variables: this.variables
|
|
@@ -252,13 +243,13 @@ var p = class {
|
|
|
252
243
|
}
|
|
253
244
|
};
|
|
254
245
|
getElementVariables = (e, t, n = this.flat, r = this.variables) => {
|
|
255
|
-
let i = [], a =
|
|
246
|
+
let i = [], a = s(n, `${t}.definition.styleSelectors`);
|
|
256
247
|
if (!a) return i;
|
|
257
|
-
let
|
|
248
|
+
let o = new RegExp(u, "g");
|
|
258
249
|
return Object.values(a).filter(Boolean).forEach((t) => {
|
|
259
250
|
Object.values(e.platform).forEach((e) => {
|
|
260
251
|
let n = e[t];
|
|
261
|
-
n && [...JSON.stringify(n.attributes).matchAll(
|
|
252
|
+
n && [...JSON.stringify(n.attributes).matchAll(o)].forEach((e) => {
|
|
262
253
|
let t = r.find((t) => t.name === e[1] || t.name === e[2]);
|
|
263
254
|
t && !i.find((e) => e.name === t.name) && i.push(t);
|
|
264
255
|
});
|
|
@@ -268,12 +259,16 @@ var p = class {
|
|
|
268
259
|
static getInstance = (e) => new this(e);
|
|
269
260
|
static addElement = (e, t, n, r = "inside", i = {}) => this.getInstance({ flat: e }).addElement(t, n, r, i);
|
|
270
261
|
static updateElement = (e, t) => this.getInstance({ flat: e }).updateElement(t);
|
|
262
|
+
static renameElement = (e, t, n) => this.getInstance({
|
|
263
|
+
flat: e.flat,
|
|
264
|
+
pages: e.pages
|
|
265
|
+
}).renameElement(t, n);
|
|
271
266
|
static moveElement = (e, t, n, r, i = "inside") => this.getInstance({ flat: e }).moveElement(t, n, r, i);
|
|
272
267
|
static getElement = (e, t) => this.getInstance({ flat: e }).getElement(t);
|
|
273
268
|
static cloneElements = (e, t, n = "", r = "", i = !1) => this.getInstance({ flat: e }).cloneElements(t, n, r, i);
|
|
274
269
|
static removeElement = (e, t, n = !1) => this.getInstance({ flat: e }).removeElement(t, n);
|
|
275
|
-
static
|
|
276
|
-
static
|
|
270
|
+
static takenIds = (e) => this.getInstance({ flat: e }).takenIds();
|
|
271
|
+
static elementIdConflict = (e, t, n) => this.getInstance({ flat: e }).elementIdConflict(t, n);
|
|
277
272
|
static addVariables = (e, t) => this.getInstance({ variables: e }).addVariables(t);
|
|
278
273
|
static addVariable = (e, t) => this.getInstance({ variables: e }).addVariable(t);
|
|
279
274
|
static updateVariable = (e, t) => this.getInstance({ variables: e }).updateVariable(t);
|
|
@@ -296,10 +291,10 @@ var p = class {
|
|
|
296
291
|
variables: i
|
|
297
292
|
}).getElementVariables(t, n);
|
|
298
293
|
};
|
|
299
|
-
static validate = (e) =>
|
|
300
|
-
static isValid = (e) =>
|
|
294
|
+
static validate = (e) => o(e);
|
|
295
|
+
static isValid = (e) => o(e).valid;
|
|
301
296
|
static assertValid = (e, t) => {
|
|
302
|
-
let n =
|
|
297
|
+
let n = o(e);
|
|
303
298
|
if (!n.valid) {
|
|
304
299
|
let e = `Invalid schema${t ? ` (${t})` : ""}: ${n.errors.map((e) => e.message).join("; ")}`;
|
|
305
300
|
throw Error(e);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Element, Schema } from '@plitzi/sdk-shared';
|
|
2
|
+
/** True when an element id is well formed: it starts with a letter and then carries letters, numbers, hyphens and
|
|
3
|
+
* underscores. Leading letter keeps a source `<type>_<id>` a valid twig identifier. A '.' would split the
|
|
4
|
+
* `<type>_<id>.<field>` source path, an interaction target lookup, and the lodash paths `flat` is read and written
|
|
5
|
+
* through (`get(flat, id)`, `set(flat, \`${id}.definition.rootId\`)`) — so it is not allowed. A source name uses
|
|
6
|
+
* the first '_' as separator between `<type>` and `<id>` (element types are camelCase with no underscore), so
|
|
7
|
+
* underscores inside the id are unambiguous. A hyphen is allowed too: a source doubles as a twig token, and
|
|
8
|
+
* `processTwig` resolves a hyphenated `<type>_<id>` through `_context` subscript access. */
|
|
9
|
+
export declare const isValidElementId: (id: string) => boolean;
|
|
10
|
+
/** What a person typed, as an id this document can hold. Anything outside the charset becomes a hyphen, runs
|
|
11
|
+
* collapse, and a leading non-letter is dropped — so a name is editable as prose in the builder's tree and still
|
|
12
|
+
* lands as a valid key. Empty when nothing usable survives, which the caller reports rather than storing. */
|
|
13
|
+
export declare const slugifyElementId: (value: string) => string;
|
|
14
|
+
/** Every id currently in use, so a new one can be checked or minted against it. Uniqueness is per DOCUMENT — a
|
|
15
|
+
* space schema, and each segment separately — which is what makes a lookup a `flat` key access. A segment used on
|
|
16
|
+
* several pages repeats its ids in the rendered tree; `rootId`/`referenceId` are what tell those apart. */
|
|
17
|
+
export declare const takenIds: (flat: Record<Element["id"], Element>) => Set<string>;
|
|
18
|
+
/** Why `id` cannot be used in this document, phrased for a person, or null when it is free and well formed.
|
|
19
|
+
* `ignoreElementId` exempts the element being edited, so re-saving an element its own id is not a conflict. */
|
|
20
|
+
export declare const elementIdConflict: (flat: Record<Element["id"], Element>, id: string, ignoreElementId?: Element["id"]) => string | null;
|
|
21
|
+
/** Whether every id a write brings INTO a document is free: well formed, not already stored, and not repeated
|
|
22
|
+
* inside the incoming set — which `elementIdConflict` cannot see on its own, because those elements are not in
|
|
23
|
+
* `flat` yet. For a rename, where the element IS stored, call `elementIdConflict` with the new name directly. */
|
|
24
|
+
export declare const elementIdsFree: (flat: Record<Element["id"], Element>, elements: Element[]) => boolean;
|
|
25
|
+
/** How a document mints an id for an element nobody named. Injected rather than chosen here: a live document wants
|
|
26
|
+
* uniqueness with no coordination between writers, an authored one wants byte-identical output when re-run, and
|
|
27
|
+
* the difference is the caller's to state. See `randomElementId` and `positionalElementId`. */
|
|
28
|
+
export type MintElementId = (type: string, isTaken: (candidate: string) => boolean) => string;
|
|
29
|
+
/** The live-editing minter: `<type>-<4 random chars>`. Random, not positional, because the builder and the MCP
|
|
30
|
+
* write concurrently — a counter has two collaborators mint `heading-3` and one of the two writes refused at the
|
|
31
|
+
* merge, which is the worst moment to discover it. At the scale of a document four characters are plenty, and the
|
|
32
|
+
* name is a default the author renames. */
|
|
33
|
+
export declare const randomElementId: MintElementId;
|
|
34
|
+
/** The offline-authoring minter: `<type>-<n>`, incrementing until `isTaken` says it is free. Deterministic, so a
|
|
35
|
+
* seed or a transformer re-run writes the same document it wrote last time and a diff against a committed copy
|
|
36
|
+
* stays empty. The hyphen sits between two alphanumeric runs, so it is valid and readable. */
|
|
37
|
+
export declare const positionalElementId: MintElementId;
|
|
38
|
+
/**
|
|
39
|
+
* `desired` when it is free, else the next number after it — the readable answer to a name collision.
|
|
40
|
+
*
|
|
41
|
+
* A copy of `hero` is `hero-2`, not `container-7`: the whole value of a name is that the copy still says what it
|
|
42
|
+
* is. A name that ALREADY ends in a number counts on from it rather than stacking another one, so a derived
|
|
43
|
+
* `container-1` meeting a `container-1` becomes `container-2` and not `container-1-2`. Names degrading this way is
|
|
44
|
+
* the one cost of a readable unique key, and it is cheaper than the alternative.
|
|
45
|
+
*/
|
|
46
|
+
export declare const uniqueElementId: (desired: string, isTaken: (candidate: string) => boolean) => string;
|
|
47
|
+
/**
|
|
48
|
+
* Apply a renaming (old id → new id) across a set of elements: every reference written against the old name, and
|
|
49
|
+
* the elements' own identity.
|
|
50
|
+
*
|
|
51
|
+
* This is what makes an id both readable and safe to change. It is a field-by-field pass, never a string replace
|
|
52
|
+
* over the serialized tree: an id is short and human-readable, so replacing "card-1" everywhere would rewrite it
|
|
53
|
+
* inside labels, prose, class names and content. So each kind of reference is rewritten where it lives —
|
|
54
|
+
* structure (`flat` key, `parentId`, `items`, `rootId`, `pages`), the attributes that point at another element,
|
|
55
|
+
* a binding's `source` and the tokens embedded in its transformers and conditions, and an interaction's target
|
|
56
|
+
* and params.
|
|
57
|
+
*
|
|
58
|
+
* An id absent from the map is left alone, so references leaving this set keep pointing outward, which is what
|
|
59
|
+
* callers want. `pages` is rewritten in place when given — a page rename is not complete without it.
|
|
60
|
+
*
|
|
61
|
+
* Returns the ids, under their new names, of every element it actually wrote to. A caller broadcasting the rename
|
|
62
|
+
* publishes that set: the element being renamed is almost never the whole of what moved.
|
|
63
|
+
*/
|
|
64
|
+
export declare const repointIds: (flat: Record<Element["id"], Element>, map: Record<Element["id"], Element["id"]>, pages?: Schema["pages"]) => Element["id"][];
|
|
65
|
+
/**
|
|
66
|
+
* Renames only the elements of an incoming set whose names this document has already taken, and repoints the set's
|
|
67
|
+
* own references onto the new names.
|
|
68
|
+
*
|
|
69
|
+
* What a template drop and a cross-document paste both need. Renaming everything would be simpler and worse: the
|
|
70
|
+
* names an author gave the template are the reason it is readable, and a document that had no `hero` should get
|
|
71
|
+
* one called `hero`. Returns the renaming it applied, so a caller can report what it had to change.
|
|
72
|
+
*/
|
|
73
|
+
export declare const remapCollidingIds: (elements: Record<Element["id"], Element>, isTaken: (candidate: string) => boolean) => Record<Element["id"], Element["id"]>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
//#region src/helpers/elementId.ts
|
|
2
|
+
var e = /^[A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9_])?$/, t = (t) => e.test(t), n = (e) => e.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^A-Za-z0-9_-]+/g, "-").replace(/-{2,}/g, "-").replace(/^[^A-Za-z]+/, "").replace(/-+$/, ""), r = (e) => new Set(Object.keys(e)), i = (e, n, r) => t(n) ? e[n] && n !== r ? `"${n}" is already used by another element here` : null : `"${n}" is not a valid name: start with a letter, then letters, numbers, hyphens and underscores`, a = (e, t) => {
|
|
3
|
+
let n = /* @__PURE__ */ new Set();
|
|
4
|
+
for (let r of t) {
|
|
5
|
+
if (n.has(r.id) || i(e, r.id)) return !1;
|
|
6
|
+
n.add(r.id);
|
|
7
|
+
}
|
|
8
|
+
return !0;
|
|
9
|
+
}, o = (e) => e.replace(/[^A-Za-z0-9]/g, "") || "element", s = "abcdefghijklmnopqrstuvwxyz0123456789", c = (e, t) => {
|
|
10
|
+
let n = o(e);
|
|
11
|
+
for (let e = 0; e < 100; e += 1) {
|
|
12
|
+
let e = "";
|
|
13
|
+
for (let t = 0; t < 4; t += 1) e += s[Math.floor(Math.random() * s.length)];
|
|
14
|
+
if (!t(`${n}-${e}`)) return `${n}-${e}`;
|
|
15
|
+
}
|
|
16
|
+
return l(e, t);
|
|
17
|
+
}, l = (e, t) => {
|
|
18
|
+
let n = o(e), r = 1;
|
|
19
|
+
for (; t(`${n}-${r}`);) r += 1;
|
|
20
|
+
return `${n}-${r}`;
|
|
21
|
+
}, u = (e, t) => {
|
|
22
|
+
if (!t(e)) return e;
|
|
23
|
+
let n = /^(.*[A-Za-z0-9])-(\d+)$/.exec(e), r = n ? n[1] : e, i = n ? Number(n[2]) + 1 : 2;
|
|
24
|
+
for (; t(`${r}-${i}`);) i += 1;
|
|
25
|
+
return `${r}-${i}`;
|
|
26
|
+
}, d = {
|
|
27
|
+
page: [{ attribute: "layout" }, { attribute: "layoutContainer" }],
|
|
28
|
+
reference: [{
|
|
29
|
+
attribute: "referenceId",
|
|
30
|
+
when: (e) => e.referenceType === "element"
|
|
31
|
+
}],
|
|
32
|
+
link: [{
|
|
33
|
+
attribute: "href",
|
|
34
|
+
when: (e) => e.mode === "page"
|
|
35
|
+
}]
|
|
36
|
+
}, f = { navigate: [{
|
|
37
|
+
param: "url",
|
|
38
|
+
when: (e) => e.urlType === "page"
|
|
39
|
+
}] }, p = (e, t) => {
|
|
40
|
+
let n = e.indexOf("."), r = n === -1 ? e : e.slice(0, n), i = r.indexOf("_");
|
|
41
|
+
if (i === -1) return e;
|
|
42
|
+
let a = t[r.slice(i + 1)];
|
|
43
|
+
return a ? `${r.slice(0, i + 1)}${a}${n === -1 ? "" : e.slice(n)}` : e;
|
|
44
|
+
}, m = /([A-Za-z][A-Za-z0-9]*)_([A-Za-z][A-Za-z0-9_-]*)/g, h = (e, t) => e.replace(m, (e, n, r) => {
|
|
45
|
+
let i = t[r];
|
|
46
|
+
return i ? `${n}_${i}` : e;
|
|
47
|
+
}), g = (e, t) => {
|
|
48
|
+
let n = !1;
|
|
49
|
+
if (Array.isArray(e)) return e.forEach((r, i) => {
|
|
50
|
+
if (typeof r == "string") {
|
|
51
|
+
let a = h(r, t);
|
|
52
|
+
a !== r && (e[i] = a, n = !0);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
n = g(r, t) || n;
|
|
56
|
+
}), n;
|
|
57
|
+
if (!e || typeof e != "object") return !1;
|
|
58
|
+
let r = e;
|
|
59
|
+
for (let [e, i] of Object.entries(r)) {
|
|
60
|
+
if (typeof i == "string") {
|
|
61
|
+
let a = h(i, t);
|
|
62
|
+
a !== i && (r[e] = a, n = !0);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
n = g(i, t) || n;
|
|
66
|
+
}
|
|
67
|
+
return n;
|
|
68
|
+
}, _ = (e, t, n) => {
|
|
69
|
+
let r = Object.entries(t).filter(([e, t]) => e !== t);
|
|
70
|
+
if (r.length === 0) return [];
|
|
71
|
+
let i = (e) => t[e] ?? e, a = /* @__PURE__ */ new Set();
|
|
72
|
+
for (let n of Object.values(e)) {
|
|
73
|
+
let e = !1;
|
|
74
|
+
t[n.id] && (n.id = t[n.id], e = !0);
|
|
75
|
+
let { definition: r, attributes: o } = n;
|
|
76
|
+
t[r.rootId] && (r.rootId = t[r.rootId], e = !0), r.parentId && t[r.parentId] && (r.parentId = t[r.parentId], e = !0), r.items?.some((e) => t[e]) && (r.items = r.items.map(i), e = !0);
|
|
77
|
+
for (let { attribute: n, when: i } of d[r.type] ?? []) {
|
|
78
|
+
let r = o[n];
|
|
79
|
+
typeof r == "string" && t[r] && (!i || i(o)) && (o[n] = t[r], e = !0);
|
|
80
|
+
}
|
|
81
|
+
for (let n of Object.values(r.bindings ?? {})) for (let r of n) {
|
|
82
|
+
let n = p(r.source, t);
|
|
83
|
+
n !== r.source && (r.source = n, e = !0), r.transformers && g(r.transformers, t) && (e = !0), r.when && g(r.when, t) && (e = !0);
|
|
84
|
+
}
|
|
85
|
+
for (let n of Object.values(r.interactions ?? {})) {
|
|
86
|
+
n.elementId && t[n.elementId] && (n.elementId = t[n.elementId], e = !0);
|
|
87
|
+
for (let { param: r, when: i } of f[n.action] ?? []) {
|
|
88
|
+
let a = n.params[r];
|
|
89
|
+
typeof a == "string" && t[a] && (!i || i(n.params)) && (n.params[r] = t[a], e = !0);
|
|
90
|
+
}
|
|
91
|
+
g(n.params, t) && (e = !0), n.when && g(n.when, t) && (e = !0);
|
|
92
|
+
}
|
|
93
|
+
e && a.add(n.id);
|
|
94
|
+
}
|
|
95
|
+
let o = r.filter(([t]) => e[t] !== void 0), s = o.map(([t, n]) => [n, e[t]]);
|
|
96
|
+
for (let [t] of o) delete e[t];
|
|
97
|
+
for (let [t, n] of s) e[t] = n;
|
|
98
|
+
return n && n.forEach((e, r) => {
|
|
99
|
+
t[e] && (n[r] = t[e]);
|
|
100
|
+
}), [...a];
|
|
101
|
+
}, v = (e, t) => {
|
|
102
|
+
let n = {}, r = /* @__PURE__ */ new Set();
|
|
103
|
+
for (let i of Object.values(e)) {
|
|
104
|
+
if (!t(i.id)) {
|
|
105
|
+
r.add(i.id);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
let a = u(i.id, (n) => t(n) || r.has(n) || e[n] !== void 0);
|
|
109
|
+
r.add(a), n[i.id] = a;
|
|
110
|
+
}
|
|
111
|
+
return _(e, n), n;
|
|
112
|
+
};
|
|
113
|
+
//#endregion
|
|
114
|
+
export { i as elementIdConflict, a as elementIdsFree, t as isValidElementId, l as positionalElementId, c as randomElementId, v as remapCollidingIds, _ as repointIds, n as slugifyElementId, r as takenIds, u as uniqueElementId };
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { Schema } from '@plitzi/sdk-shared';
|
|
2
2
|
export type SchemaValidationOptions = {
|
|
3
3
|
baseElementId?: string;
|
|
4
|
+
/**
|
|
5
|
+
* Element type → the source name it publishes under.
|
|
6
|
+
*
|
|
7
|
+
* Without it a binding's source can only be half-checked: the type half is compared against the types that
|
|
8
|
+
* happen to be in this document, so a typo'd id is caught only when some element of that same type is present,
|
|
9
|
+
* and a prefix that is simply the wrong word for the element it names is never caught at all.
|
|
10
|
+
*/
|
|
11
|
+
sourceTypes?: Record<string, string>;
|
|
4
12
|
};
|
|
5
13
|
export type SchemaValidationError = {
|
|
6
14
|
code: string;
|