@preprio/toolkit 0.5.0-rc.4 → 0.5.1-dom.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/README.md +67 -2
- package/dist/astro/index.cjs +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/auto.cjs +4572 -0
- package/dist/auto.d.cts +42 -0
- package/dist/auto.d.ts +42 -0
- package/dist/auto.js +105 -0
- package/dist/browser/auto.js +117 -0
- package/dist/chunk-5I5TQ3NO.js +74 -0
- package/dist/chunk-AZC4BZJI.js +447 -0
- package/dist/{chunk-TG6N3C22.js → chunk-IKOOXTPM.js} +1 -1
- package/dist/{chunk-AHBN6FQP.js → chunk-IOSO232M.js} +4 -2
- package/dist/{chunk-PDNCNTDB.js → chunk-RRM5762S.js} +2 -71
- package/dist/chunk-SZTHSJUA.js +326 -0
- package/dist/{chunk-46ISDRHT.js → chunk-TEB7M7EM.js} +0 -4
- package/dist/chunk-UK664KSV.js +6 -0
- package/dist/{chunk-74KY5EAM.js → chunk-ZFGVP2GW.js} +28 -447
- package/dist/html-live-DzmEG9wU.d.cts +42 -0
- package/dist/html-live-DzmEG9wU.d.ts +42 -0
- package/dist/index.cjs +177 -2
- package/dist/index.d.cts +31 -28
- package/dist/index.d.ts +31 -28
- package/dist/index.js +21 -154
- package/dist/{live-preview-H4643QYF.js → live-preview-IYAS7PYV.js} +3 -2
- package/dist/nextjs/components.cjs +1 -1
- package/dist/nextjs/components.d.cts +2 -1
- package/dist/nextjs/components.d.ts +2 -1
- package/dist/nextjs/components.js +6 -3
- package/dist/nextjs/index.cjs +1 -1
- package/dist/nextjs/index.js +1 -1
- package/dist/nextjs/live-action.js +5 -3
- package/dist/nextjs/live.js +1 -1
- package/dist/nuxt/index.cjs +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/pixel-DwW7cXJm.d.cts +49 -0
- package/dist/pixel-DwW7cXJm.d.ts +49 -0
- package/dist/react/components.cjs +1 -1
- package/dist/react/components.js +1 -1
- package/dist/sveltekit/index.cjs +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/dist/{pixel-CX422dCO.d.cts → types-DtMa_-f2.d.cts} +1 -49
- package/dist/{pixel-CX422dCO.d.ts → types-DtMa_-f2.d.ts} +1 -49
- package/package.json +12 -2
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TEXT_BODY_FIELDS,
|
|
3
|
+
graphqlFieldName,
|
|
4
|
+
indexSnapshot,
|
|
5
|
+
isLiveFieldChange,
|
|
6
|
+
isPlainRecord,
|
|
7
|
+
isTextEntry,
|
|
8
|
+
keepEditLink,
|
|
9
|
+
parseEditTarget,
|
|
10
|
+
parseListEntries,
|
|
11
|
+
plainTextFromHtml,
|
|
12
|
+
syncListAtPath,
|
|
13
|
+
writeAtPath
|
|
14
|
+
} from "./chunk-AZC4BZJI.js";
|
|
1
15
|
import {
|
|
2
16
|
createScopedLogger
|
|
3
17
|
} from "./chunk-S3VT4UWM.js";
|
|
@@ -6,327 +20,9 @@ import {
|
|
|
6
20
|
import {
|
|
7
21
|
vercelStegaCombine,
|
|
8
22
|
vercelStegaDecode,
|
|
9
|
-
vercelStegaSplit
|
|
23
|
+
vercelStegaSplit
|
|
10
24
|
} from "@vercel/stega";
|
|
11
25
|
|
|
12
|
-
// src/core/live-preview/field-name.ts
|
|
13
|
-
var UPPERCASE_BOUNDARY = /(.)(?=[A-Z])/g;
|
|
14
|
-
function graphqlFieldName(apiId) {
|
|
15
|
-
return apiId.replace(UPPERCASE_BOUNDARY, "$1_").toLowerCase();
|
|
16
|
-
}
|
|
17
|
-
function fieldKeyIn(record, apiId) {
|
|
18
|
-
if (Object.prototype.hasOwnProperty.call(record, apiId)) return apiId;
|
|
19
|
-
const name = graphqlFieldName(apiId);
|
|
20
|
-
return Object.prototype.hasOwnProperty.call(record, name) ? name : void 0;
|
|
21
|
-
}
|
|
22
|
-
var TEXT_BODY_FIELDS = [
|
|
23
|
-
{ name: "body", plain: false },
|
|
24
|
-
{ name: "html", plain: false },
|
|
25
|
-
{ name: "text", plain: true }
|
|
26
|
-
];
|
|
27
|
-
var TAG_PATTERN = /<[^>]+>/g;
|
|
28
|
-
function plainTextFromHtml(html) {
|
|
29
|
-
return html.replace(TAG_PATTERN, "");
|
|
30
|
-
}
|
|
31
|
-
function isTextEntry(record) {
|
|
32
|
-
const typename = record.__typename;
|
|
33
|
-
if (typeof typename === "string") return typename === "Text";
|
|
34
|
-
return "html" in record || "text" in record && "format" in record;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// src/core/live-preview/index-snapshot.ts
|
|
38
|
-
var debug = createScopedLogger("live-preview");
|
|
39
|
-
function indexSnapshot(data) {
|
|
40
|
-
const index = /* @__PURE__ */ new Map();
|
|
41
|
-
const visited = /* @__PURE__ */ new WeakSet();
|
|
42
|
-
const path = [];
|
|
43
|
-
function walk(node) {
|
|
44
|
-
if (node === null || typeof node !== "object") return;
|
|
45
|
-
if (visited.has(node)) return;
|
|
46
|
-
visited.add(node);
|
|
47
|
-
if (Array.isArray(node)) {
|
|
48
|
-
for (let i = 0; i < node.length; i++) {
|
|
49
|
-
path.push(i);
|
|
50
|
-
walk(node[i]);
|
|
51
|
-
path.pop();
|
|
52
|
-
}
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
const record = node;
|
|
56
|
-
if (typeof record._id === "string") {
|
|
57
|
-
const paths = index.get(record._id);
|
|
58
|
-
if (paths) paths.push([...path]);
|
|
59
|
-
else index.set(record._id, [[...path]]);
|
|
60
|
-
} else if (typeof record.__typename === "string") {
|
|
61
|
-
debug.log("item without _id will not live-update", {
|
|
62
|
-
__typename: record.__typename
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
for (const key in record) {
|
|
66
|
-
if (!Object.prototype.hasOwnProperty.call(record, key)) continue;
|
|
67
|
-
path.push(key);
|
|
68
|
-
walk(record[key]);
|
|
69
|
-
path.pop();
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
walk(data);
|
|
73
|
-
return index;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// src/core/live-preview/patch.ts
|
|
77
|
-
import { vercelStegaSplit } from "@vercel/stega";
|
|
78
|
-
var INDEX_SEGMENT = /^\d+$/;
|
|
79
|
-
var FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
80
|
-
function isPlainObject(value) {
|
|
81
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
82
|
-
}
|
|
83
|
-
function mergeObject(current, incoming) {
|
|
84
|
-
let changed = false;
|
|
85
|
-
const next = { ...current };
|
|
86
|
-
for (const key of Object.keys(current)) {
|
|
87
|
-
if (!(key in incoming) || incoming[key] === void 0) continue;
|
|
88
|
-
const value = isPlainObject(current[key]) && isPlainObject(incoming[key]) ? mergeObject(current[key], incoming[key]) : incoming[key];
|
|
89
|
-
if (value !== current[key]) {
|
|
90
|
-
next[key] = value;
|
|
91
|
-
changed = true;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return changed ? next : current;
|
|
95
|
-
}
|
|
96
|
-
function shapeLike(template, incoming) {
|
|
97
|
-
const out = {};
|
|
98
|
-
for (const key of Object.keys(template)) {
|
|
99
|
-
const value = incoming[key];
|
|
100
|
-
out[key] = value === void 0 ? null : isPlainObject(template[key]) && isPlainObject(value) ? shapeLike(template[key], value) : value;
|
|
101
|
-
}
|
|
102
|
-
return out;
|
|
103
|
-
}
|
|
104
|
-
function mergeStructured(current, incoming) {
|
|
105
|
-
if (incoming === null || Array.isArray(incoming) && !incoming.length) {
|
|
106
|
-
if (Array.isArray(current)) return current.length ? [] : current;
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
const list = Array.isArray(incoming) ? incoming : [incoming];
|
|
110
|
-
if (current === null || current === void 0) return incoming;
|
|
111
|
-
if (Array.isArray(current)) {
|
|
112
|
-
const template = current.find(isPlainObject);
|
|
113
|
-
return list.map((entry) => {
|
|
114
|
-
const match = current.find(
|
|
115
|
-
(c) => isPlainObject(c) && c._id !== void 0 && c._id === entry._id
|
|
116
|
-
);
|
|
117
|
-
if (isPlainObject(match)) return mergeObject(match, entry);
|
|
118
|
-
return isPlainObject(template) ? shapeLike(template, entry) : entry;
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
if (isPlainObject(current)) return mergeObject(current, list[0]);
|
|
122
|
-
return current;
|
|
123
|
-
}
|
|
124
|
-
function isPatchable(current) {
|
|
125
|
-
return current === null || typeof current === "string" || typeof current === "number" || typeof current === "boolean";
|
|
126
|
-
}
|
|
127
|
-
function cloneNode(node) {
|
|
128
|
-
return Array.isArray(node) ? [...node] : { ...node };
|
|
129
|
-
}
|
|
130
|
-
function patchSnapshot(snapshot, index, itemId, field, value) {
|
|
131
|
-
const paths = index.get(itemId);
|
|
132
|
-
if (!paths?.length) return snapshot;
|
|
133
|
-
let root = snapshot;
|
|
134
|
-
let changed = false;
|
|
135
|
-
const markChanged = () => {
|
|
136
|
-
changed = true;
|
|
137
|
-
};
|
|
138
|
-
for (const path of paths) {
|
|
139
|
-
for (const [target, targetValue] of expandTextBody(
|
|
140
|
-
root,
|
|
141
|
-
path,
|
|
142
|
-
field,
|
|
143
|
-
value
|
|
144
|
-
)) {
|
|
145
|
-
root = patchAtPath(root, path, target, targetValue, markChanged);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return changed ? root : snapshot;
|
|
149
|
-
}
|
|
150
|
-
function expandTextBody(root, path, field, value) {
|
|
151
|
-
const segments = field.split(".");
|
|
152
|
-
if (segments[segments.length - 1] !== "body" || typeof value !== "string") {
|
|
153
|
-
return [[field, value]];
|
|
154
|
-
}
|
|
155
|
-
let node = root;
|
|
156
|
-
for (const step of path) {
|
|
157
|
-
node = node[step];
|
|
158
|
-
}
|
|
159
|
-
for (const segment of segments.slice(0, -1)) {
|
|
160
|
-
if (node === null || typeof node !== "object") return [[field, value]];
|
|
161
|
-
const record2 = node;
|
|
162
|
-
node = INDEX_SEGMENT.test(segment) ? record2[Number(segment)] : record2[fieldKeyIn(record2, segment) ?? segment];
|
|
163
|
-
}
|
|
164
|
-
if (node === null || typeof node !== "object" || Array.isArray(node)) {
|
|
165
|
-
return [[field, value]];
|
|
166
|
-
}
|
|
167
|
-
const record = node;
|
|
168
|
-
if (!isTextEntry(record)) return [[field, value]];
|
|
169
|
-
const prefix = segments.slice(0, -1);
|
|
170
|
-
const writes = [];
|
|
171
|
-
for (const { name, plain } of TEXT_BODY_FIELDS) {
|
|
172
|
-
if (!(name in record)) continue;
|
|
173
|
-
writes.push([
|
|
174
|
-
[...prefix, name].join("."),
|
|
175
|
-
plain ? plainTextFromHtml(value) : value
|
|
176
|
-
]);
|
|
177
|
-
}
|
|
178
|
-
return writes.length ? writes : [[field, value]];
|
|
179
|
-
}
|
|
180
|
-
function patchAtPath(root, path, field, value, markChanged) {
|
|
181
|
-
const segments = field.split(".");
|
|
182
|
-
if (segments.some((s) => FORBIDDEN_SEGMENTS.has(s))) return root;
|
|
183
|
-
let node = root;
|
|
184
|
-
for (const step of path) {
|
|
185
|
-
node = node[step];
|
|
186
|
-
}
|
|
187
|
-
const fullPath = [...path];
|
|
188
|
-
for (const segment of segments) {
|
|
189
|
-
let step = segment;
|
|
190
|
-
if (INDEX_SEGMENT.test(segment)) {
|
|
191
|
-
step = Number(segment);
|
|
192
|
-
} else if (node !== null && typeof node === "object") {
|
|
193
|
-
step = fieldKeyIn(node, segment) ?? segment;
|
|
194
|
-
}
|
|
195
|
-
fullPath.push(step);
|
|
196
|
-
node = node !== null && typeof node === "object" ? node[step] : void 0;
|
|
197
|
-
}
|
|
198
|
-
return writeAtPath(root, fullPath, value, markChanged);
|
|
199
|
-
}
|
|
200
|
-
function writeAtPath(root, fullPath, value, markChanged = () => {
|
|
201
|
-
}) {
|
|
202
|
-
if (!fullPath.length) return root;
|
|
203
|
-
if (fullPath.some((s) => FORBIDDEN_SEGMENTS.has(String(s)))) return root;
|
|
204
|
-
const last = fullPath.length - 1;
|
|
205
|
-
let node = root;
|
|
206
|
-
for (let i = 0; i < last; i++) {
|
|
207
|
-
if (node === null || typeof node !== "object") return root;
|
|
208
|
-
node = node[fullPath[i]];
|
|
209
|
-
}
|
|
210
|
-
if (node === null || typeof node !== "object") return root;
|
|
211
|
-
const container = node;
|
|
212
|
-
const key = fullPath[last];
|
|
213
|
-
if (!(key in container)) return root;
|
|
214
|
-
let next;
|
|
215
|
-
if (value === null || typeof value === "object") {
|
|
216
|
-
next = mergeStructured(container[key], value);
|
|
217
|
-
} else if (!isPatchable(container[key])) {
|
|
218
|
-
return root;
|
|
219
|
-
} else {
|
|
220
|
-
next = keepEditLink(container[key], value);
|
|
221
|
-
}
|
|
222
|
-
if (container[key] === next) return root;
|
|
223
|
-
const newRoot = cloneNode(root);
|
|
224
|
-
let cursor = newRoot;
|
|
225
|
-
for (let i = 0; i < last; i++) {
|
|
226
|
-
const child = cloneNode(cursor[fullPath[i]]);
|
|
227
|
-
cursor[fullPath[i]] = child;
|
|
228
|
-
cursor = child;
|
|
229
|
-
}
|
|
230
|
-
cursor[key] = next;
|
|
231
|
-
markChanged();
|
|
232
|
-
return newRoot;
|
|
233
|
-
}
|
|
234
|
-
function listKeysOf(entry) {
|
|
235
|
-
if (!isPlainObject(entry)) return [];
|
|
236
|
-
const keys = [];
|
|
237
|
-
if (typeof entry._id === "string") keys.push(entry._id);
|
|
238
|
-
const first = Array.isArray(entry.items) ? entry.items[0] : null;
|
|
239
|
-
if (isPlainObject(first) && typeof first._id === "string") {
|
|
240
|
-
keys.push(first._id);
|
|
241
|
-
}
|
|
242
|
-
return keys;
|
|
243
|
-
}
|
|
244
|
-
function buildText(entry, template) {
|
|
245
|
-
const body = typeof entry.body === "string" ? entry.body : "";
|
|
246
|
-
const full = {
|
|
247
|
-
__typename: "Text",
|
|
248
|
-
_id: entry._id,
|
|
249
|
-
body,
|
|
250
|
-
html: body,
|
|
251
|
-
text: plainTextFromHtml(body),
|
|
252
|
-
format: entry.format ?? null
|
|
253
|
-
};
|
|
254
|
-
if (!template) return full;
|
|
255
|
-
const out = {};
|
|
256
|
-
for (const key of Object.keys(template)) {
|
|
257
|
-
out[key] = key in full ? full[key] : null;
|
|
258
|
-
}
|
|
259
|
-
return out;
|
|
260
|
-
}
|
|
261
|
-
function syncList(current, entries, build) {
|
|
262
|
-
if (current === null || current === void 0) {
|
|
263
|
-
const built = syncList([], entries, build);
|
|
264
|
-
return built.length ? built : current;
|
|
265
|
-
}
|
|
266
|
-
if (!Array.isArray(current)) return current;
|
|
267
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
268
|
-
const templates = /* @__PURE__ */ new Map();
|
|
269
|
-
for (const c of current) {
|
|
270
|
-
for (const key of listKeysOf(c)) byKey.set(key, c);
|
|
271
|
-
if (isPlainObject(c) && typeof c.__typename === "string") {
|
|
272
|
-
if (!templates.has(c.__typename)) templates.set(c.__typename, c);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
if (!entries.length) return current.length ? [] : current;
|
|
276
|
-
if (current.length && !byKey.size) return current;
|
|
277
|
-
if (current.length && !entries.some((e) => byKey.has(e._id))) return current;
|
|
278
|
-
const next = [];
|
|
279
|
-
for (const entry of entries) {
|
|
280
|
-
const existing = byKey.get(entry._id);
|
|
281
|
-
const built = existing === void 0 && entry.fields ? build?.(entry) : void 0;
|
|
282
|
-
if (existing !== void 0) {
|
|
283
|
-
next.push(existing);
|
|
284
|
-
} else if (built) {
|
|
285
|
-
next.push(built);
|
|
286
|
-
} else if (entry.__typename === "Text") {
|
|
287
|
-
next.push(buildText(entry, templates.get("Text")));
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
const same = next.length === current.length && next.every((n, i) => n === current[i]);
|
|
291
|
-
return same ? current : next;
|
|
292
|
-
}
|
|
293
|
-
function syncListSnapshot(snapshot, index, itemId, field, entries) {
|
|
294
|
-
const paths = index.get(itemId);
|
|
295
|
-
if (!paths?.length) return snapshot;
|
|
296
|
-
const fieldPath = field.split(".");
|
|
297
|
-
if (fieldPath.some((s) => FORBIDDEN_SEGMENTS.has(s))) return snapshot;
|
|
298
|
-
let root = snapshot;
|
|
299
|
-
for (const path of paths) {
|
|
300
|
-
root = syncListAtPath(root, [...path, ...fieldPath], entries);
|
|
301
|
-
}
|
|
302
|
-
return root;
|
|
303
|
-
}
|
|
304
|
-
function syncListAtPath(root, fullPath, entries, build) {
|
|
305
|
-
if (!fullPath.length) return root;
|
|
306
|
-
if (fullPath.some((s) => FORBIDDEN_SEGMENTS.has(String(s)))) return root;
|
|
307
|
-
let node = root;
|
|
308
|
-
for (const key of fullPath) {
|
|
309
|
-
node = node !== null && typeof node === "object" ? node[key] : void 0;
|
|
310
|
-
}
|
|
311
|
-
const next = syncList(node, entries, build);
|
|
312
|
-
if (next === node) return root;
|
|
313
|
-
const newRoot = cloneNode(root);
|
|
314
|
-
let cursor = newRoot;
|
|
315
|
-
for (let i = 0; i < fullPath.length - 1; i++) {
|
|
316
|
-
const child = cloneNode(cursor[fullPath[i]]);
|
|
317
|
-
cursor[fullPath[i]] = child;
|
|
318
|
-
cursor = child;
|
|
319
|
-
}
|
|
320
|
-
cursor[fullPath[fullPath.length - 1]] = next;
|
|
321
|
-
return newRoot;
|
|
322
|
-
}
|
|
323
|
-
function keepEditLink(current, value) {
|
|
324
|
-
if (typeof current !== "string" || typeof value !== "string") return value;
|
|
325
|
-
const { encoded } = vercelStegaSplit(current);
|
|
326
|
-
if (!encoded || vercelStegaSplit(value).encoded) return value;
|
|
327
|
-
return value + encoded;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
26
|
// src/core/live-preview/query-document.ts
|
|
331
27
|
var NAME_START = /[_A-Za-z]/;
|
|
332
28
|
var NAME_CONTINUE = /[_0-9A-Za-z]/;
|
|
@@ -662,121 +358,12 @@ function typeConditions(selections, doc) {
|
|
|
662
358
|
return conditions;
|
|
663
359
|
}
|
|
664
360
|
|
|
665
|
-
// src/core/live-preview/validate.ts
|
|
666
|
-
function isScalar(v) {
|
|
667
|
-
return typeof v === "string" || typeof v === "number" || typeof v === "boolean";
|
|
668
|
-
}
|
|
669
|
-
var MAX_DEPTH = 3;
|
|
670
|
-
var MAX_ENTRIES = 50;
|
|
671
|
-
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
672
|
-
var SAFE_URL = /^https?:\/\//i;
|
|
673
|
-
var URL_KEY = /^(?:url|href|src|link)$|(?:_url|Url)$/;
|
|
674
|
-
var URL_IGNORED_ANYWHERE = /[\t\n\r]/g;
|
|
675
|
-
var SCRIPT_SCHEME = /^(?:javascript|vbscript|data):/i;
|
|
676
|
-
function isSafeLinkValue(value) {
|
|
677
|
-
const stripped = value.replace(URL_IGNORED_ANYWHERE, "");
|
|
678
|
-
let start = 0;
|
|
679
|
-
while (start < stripped.length && stripped.charCodeAt(start) <= 32) start++;
|
|
680
|
-
return !SCRIPT_SCHEME.test(stripped.slice(start));
|
|
681
|
-
}
|
|
682
|
-
function isUrlKey(key) {
|
|
683
|
-
return URL_KEY.test(key);
|
|
684
|
-
}
|
|
685
|
-
function isSafeKeyValue(key, value) {
|
|
686
|
-
if (typeof value !== "string") return true;
|
|
687
|
-
if (key === "url") return SAFE_URL.test(value);
|
|
688
|
-
return !isUrlKey(key) || isSafeLinkValue(value);
|
|
689
|
-
}
|
|
690
|
-
function isLiveValue(v, depth = 0) {
|
|
691
|
-
if (v === null || isScalar(v)) return true;
|
|
692
|
-
if (depth >= MAX_DEPTH) return false;
|
|
693
|
-
if (Array.isArray(v)) {
|
|
694
|
-
return depth === 0 && v.length <= MAX_ENTRIES && v.every((entry) => isPlainRecord(entry) && isLiveValue(entry, depth + 1));
|
|
695
|
-
}
|
|
696
|
-
if (!isPlainRecord(v)) return false;
|
|
697
|
-
const keys = Object.keys(v);
|
|
698
|
-
if (keys.length > MAX_ENTRIES) return false;
|
|
699
|
-
return keys.every((key) => {
|
|
700
|
-
if (FORBIDDEN_KEYS.has(key)) return false;
|
|
701
|
-
const value = v[key];
|
|
702
|
-
if (!isSafeKeyValue(key, value)) return false;
|
|
703
|
-
return !Array.isArray(value) && isLiveValue(value, depth + 1);
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
function isLiveFieldChange(field, value) {
|
|
707
|
-
if (!isLiveValue(value)) return false;
|
|
708
|
-
const name = field.slice(field.lastIndexOf(".") + 1);
|
|
709
|
-
return typeof value !== "string" || !isUrlKey(name) || isSafeLinkValue(value);
|
|
710
|
-
}
|
|
711
|
-
var MAX_LIST_ENTRIES = 200;
|
|
712
|
-
function parseListEntries(value) {
|
|
713
|
-
if (!Array.isArray(value) || value.length > MAX_LIST_ENTRIES) return null;
|
|
714
|
-
const list = [];
|
|
715
|
-
for (const raw of value) {
|
|
716
|
-
if (!isPlainRecord(raw) || typeof raw._id !== "string" || !raw._id) {
|
|
717
|
-
return null;
|
|
718
|
-
}
|
|
719
|
-
const entry = { _id: raw._id };
|
|
720
|
-
if (typeof raw.__typename === "string") entry.__typename = raw.__typename;
|
|
721
|
-
if (typeof raw.body === "string") entry.body = raw.body;
|
|
722
|
-
if (typeof raw.format === "string" || raw.format === null) {
|
|
723
|
-
entry.format = raw.format;
|
|
724
|
-
}
|
|
725
|
-
if (typeof raw.typeName === "string" && raw.typeName.length <= 200) {
|
|
726
|
-
entry.typeName = raw.typeName;
|
|
727
|
-
}
|
|
728
|
-
if (raw.fields !== void 0) {
|
|
729
|
-
if (!isPlainRecord(raw.fields) || !isFieldValue(raw.fields, 0)) {
|
|
730
|
-
return null;
|
|
731
|
-
}
|
|
732
|
-
entry.fields = raw.fields;
|
|
733
|
-
}
|
|
734
|
-
list.push(entry);
|
|
735
|
-
}
|
|
736
|
-
return list;
|
|
737
|
-
}
|
|
738
|
-
function isPlainRecord(v) {
|
|
739
|
-
return v !== null && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype;
|
|
740
|
-
}
|
|
741
|
-
var MAX_FIELD_DEPTH = 6;
|
|
742
|
-
var MAX_FIELD_KEYS = 100;
|
|
743
|
-
function isFieldValue(v, depth) {
|
|
744
|
-
if (v === null || isScalar(v)) return true;
|
|
745
|
-
if (depth >= MAX_FIELD_DEPTH) return false;
|
|
746
|
-
if (Array.isArray(v)) {
|
|
747
|
-
return v.length <= MAX_ENTRIES && v.every(
|
|
748
|
-
(entry) => isScalar(entry) || isPlainRecord(entry) && isFieldValue(entry, depth + 1)
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
if (!isPlainRecord(v)) return false;
|
|
752
|
-
const keys = Object.keys(v);
|
|
753
|
-
if (keys.length > MAX_FIELD_KEYS) return false;
|
|
754
|
-
return keys.every((key) => {
|
|
755
|
-
if (FORBIDDEN_KEYS.has(key)) return false;
|
|
756
|
-
const value = v[key];
|
|
757
|
-
if (!isSafeKeyValue(key, value)) return false;
|
|
758
|
-
return isFieldValue(value, depth + 1);
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
var MAX_EDIT_LENGTH = 1e3;
|
|
762
|
-
function parseEditTarget(raw) {
|
|
763
|
-
if (!isPlainRecord(raw)) return void 0;
|
|
764
|
-
const { item, field } = raw;
|
|
765
|
-
if (typeof item !== "string" || !item || item.length > MAX_EDIT_LENGTH) {
|
|
766
|
-
return void 0;
|
|
767
|
-
}
|
|
768
|
-
if (typeof field !== "string" || !field || field.length > MAX_EDIT_LENGTH) {
|
|
769
|
-
return void 0;
|
|
770
|
-
}
|
|
771
|
-
return { item, field };
|
|
772
|
-
}
|
|
773
|
-
|
|
774
361
|
// src/core/live-preview/normalize-query.ts
|
|
775
|
-
var
|
|
362
|
+
var debug = createScopedLogger("live-preview:normalize");
|
|
776
363
|
var MAX_LOG_ENTRIES = 500;
|
|
777
364
|
var MAX_LOG_SOURCE_LENGTH = 1e6;
|
|
778
|
-
var
|
|
779
|
-
var
|
|
365
|
+
var INDEX_SEGMENT = /^\d+$/;
|
|
366
|
+
var FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
780
367
|
var ENVELOPE_KEYS = /* @__PURE__ */ new Set(["data", "errors", "extensions"]);
|
|
781
368
|
function parsePatchLog(raw) {
|
|
782
369
|
let value = raw;
|
|
@@ -827,7 +414,7 @@ function applyPatchLog(query, result, log, operationName) {
|
|
|
827
414
|
try {
|
|
828
415
|
doc = readQueryDocument(query, operationName);
|
|
829
416
|
} catch (error) {
|
|
830
|
-
|
|
417
|
+
debug.log("could not read the query; applying fields by literal path", {
|
|
831
418
|
error: String(error)
|
|
832
419
|
});
|
|
833
420
|
}
|
|
@@ -844,7 +431,7 @@ function applyPatchLog(query, result, log, operationName) {
|
|
|
844
431
|
}
|
|
845
432
|
}
|
|
846
433
|
} catch (error) {
|
|
847
|
-
|
|
434
|
+
debug.log("could not apply a change", {
|
|
848
435
|
itemId: patch.itemId,
|
|
849
436
|
field: patch.field,
|
|
850
437
|
error: String(error)
|
|
@@ -860,7 +447,7 @@ function applyPatch(root, index, patch, doc, result) {
|
|
|
860
447
|
const ownerPaths = index.get(patch.itemId);
|
|
861
448
|
if (!ownerPaths?.length) return root;
|
|
862
449
|
const segments = patch.field.split(".");
|
|
863
|
-
if (segments.some((s) => !s ||
|
|
450
|
+
if (segments.some((s) => !s || FORBIDDEN_SEGMENTS.has(s))) return root;
|
|
864
451
|
const targets = [];
|
|
865
452
|
const plain = /* @__PURE__ */ new Set();
|
|
866
453
|
for (const ownerPath of ownerPaths) {
|
|
@@ -874,7 +461,7 @@ function applyPatch(root, index, patch, doc, result) {
|
|
|
874
461
|
}
|
|
875
462
|
}
|
|
876
463
|
if (!targets.length) {
|
|
877
|
-
|
|
464
|
+
debug.log("no field in the query matches", {
|
|
878
465
|
itemId: patch.itemId,
|
|
879
466
|
field: patch.field
|
|
880
467
|
});
|
|
@@ -884,7 +471,7 @@ function applyPatch(root, index, patch, doc, result) {
|
|
|
884
471
|
for (const target of targets) {
|
|
885
472
|
const pushed = plain.has(target) && typeof patch.value === "string" ? plainTextFromHtml(patch.value) : patch.value;
|
|
886
473
|
let value = keepEditLink(readAtPath(root, target), pushed);
|
|
887
|
-
if (patch.edit && typeof value === "string" && !
|
|
474
|
+
if (patch.edit && typeof value === "string" && !vercelStegaSplit(value).encoded) {
|
|
888
475
|
value = editLinksFor(result, patch.edit.item)?.encode(
|
|
889
476
|
value,
|
|
890
477
|
patch.edit.field
|
|
@@ -917,7 +504,7 @@ function isEnvelope(value) {
|
|
|
917
504
|
return Object.keys(value).every((key) => ENVELOPE_KEYS.has(key));
|
|
918
505
|
}
|
|
919
506
|
function toPathSegment(segment) {
|
|
920
|
-
return
|
|
507
|
+
return INDEX_SEGMENT.test(segment) ? Number(segment) : segment;
|
|
921
508
|
}
|
|
922
509
|
function typenameOf(node) {
|
|
923
510
|
const typename = node.__typename;
|
|
@@ -958,7 +545,7 @@ function resolveTargets(root, doc, ownerPath, segments) {
|
|
|
958
545
|
}
|
|
959
546
|
const segment = segments[i];
|
|
960
547
|
if (Array.isArray(node)) {
|
|
961
|
-
if (
|
|
548
|
+
if (INDEX_SEGMENT.test(segment)) {
|
|
962
549
|
const position = Number(segment);
|
|
963
550
|
if (position < node.length) {
|
|
964
551
|
walk(node[position], sel, i + 1, [...path, position]);
|
|
@@ -982,7 +569,7 @@ function resolveTargets(root, doc, ownerPath, segments) {
|
|
|
982
569
|
if (alias?.plain) plain.add(fieldPath);
|
|
983
570
|
let value = record[field.key];
|
|
984
571
|
const next = segments[i + 1];
|
|
985
|
-
if (value === null && field.selections.length && next !== void 0 && next !== "items" && !
|
|
572
|
+
if (value === null && field.selections.length && next !== void 0 && next !== "items" && !INDEX_SEGMENT.test(next)) {
|
|
986
573
|
const typename = resolveTypename(
|
|
987
574
|
doc,
|
|
988
575
|
field.selections,
|
|
@@ -1161,7 +748,7 @@ function editLinksFor(result, item) {
|
|
|
1161
748
|
if (!template) return null;
|
|
1162
749
|
return {
|
|
1163
750
|
encode(text, field) {
|
|
1164
|
-
if (
|
|
751
|
+
if (vercelStegaSplit(text).encoded) return text;
|
|
1165
752
|
let href;
|
|
1166
753
|
try {
|
|
1167
754
|
href = new URL(template.href);
|
|
@@ -1187,7 +774,7 @@ function findEditLink(root) {
|
|
|
1187
774
|
const walk = (node) => {
|
|
1188
775
|
if (found) return;
|
|
1189
776
|
if (typeof node === "string") {
|
|
1190
|
-
if (!
|
|
777
|
+
if (!vercelStegaSplit(node).encoded) return;
|
|
1191
778
|
try {
|
|
1192
779
|
const decoded = vercelStegaDecode(node);
|
|
1193
780
|
if (typeof decoded?.origin === "string" && typeof decoded.href === "string" && EDIT_PATH.test(decoded.href)) {
|
|
@@ -1207,12 +794,6 @@ function findEditLink(root) {
|
|
|
1207
794
|
}
|
|
1208
795
|
|
|
1209
796
|
export {
|
|
1210
|
-
indexSnapshot,
|
|
1211
|
-
patchSnapshot,
|
|
1212
|
-
syncListSnapshot,
|
|
1213
|
-
isLiveFieldChange,
|
|
1214
|
-
parseListEntries,
|
|
1215
|
-
parseEditTarget,
|
|
1216
797
|
parsePatchLog,
|
|
1217
798
|
normalizeQuery,
|
|
1218
799
|
applyPatchLog
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
interface HtmlLivePreviewOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Replace the `*.prepr.io` editor-origin wildcard with an exact list, as
|
|
4
|
+
* the toolbar's option of the same name.
|
|
5
|
+
*/
|
|
6
|
+
allowedEditorOrigins?: string[];
|
|
7
|
+
/** See the toolbar's `strictMode`. */
|
|
8
|
+
strictMode?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Where unsaved changes are applied while the editor types.
|
|
11
|
+
*
|
|
12
|
+
* - `'server'` (default): the page is requested again with the changes,
|
|
13
|
+
* and the server applies them with `normalizeQuery`.
|
|
14
|
+
* - `'dom'`: text changes are written straight into the elements whose
|
|
15
|
+
* edit links point at the changed field. Needs no server support, so it
|
|
16
|
+
* suits any backend (PHP, Ruby, Python). Changes the page cannot show
|
|
17
|
+
* this way (images, links, reordered or added blocks) appear on save.
|
|
18
|
+
*
|
|
19
|
+
* Every mode renders the stored content again on save.
|
|
20
|
+
*/
|
|
21
|
+
apply?: 'server' | 'dom';
|
|
22
|
+
}
|
|
23
|
+
interface HtmlLivePreview {
|
|
24
|
+
stop(): void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Live preview for server-rendered pages without a client framework (Astro,
|
|
28
|
+
* or any HTML-first site): while an editor types, the page is requested
|
|
29
|
+
* again from the server with the unsaved changes in the request body, and
|
|
30
|
+
* only the parts of the DOM that changed are updated. On save the page is
|
|
31
|
+
* requested without changes. The server applies them with `normalizeQuery`;
|
|
32
|
+
* see `readLiveLogFromRequest`.
|
|
33
|
+
*
|
|
34
|
+
* A backend that cannot run `normalizeQuery` passes `apply: 'dom'`: text
|
|
35
|
+
* changes are then written into the page directly, and the server only
|
|
36
|
+
* renders the stored content on save.
|
|
37
|
+
*
|
|
38
|
+
* A no-op outside the Prepr editor's preview iframe.
|
|
39
|
+
*/
|
|
40
|
+
declare function startHtmlLivePreview(options?: HtmlLivePreviewOptions): HtmlLivePreview;
|
|
41
|
+
|
|
42
|
+
export { type HtmlLivePreviewOptions as H, type HtmlLivePreview as a, startHtmlLivePreview as s };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
interface HtmlLivePreviewOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Replace the `*.prepr.io` editor-origin wildcard with an exact list, as
|
|
4
|
+
* the toolbar's option of the same name.
|
|
5
|
+
*/
|
|
6
|
+
allowedEditorOrigins?: string[];
|
|
7
|
+
/** See the toolbar's `strictMode`. */
|
|
8
|
+
strictMode?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Where unsaved changes are applied while the editor types.
|
|
11
|
+
*
|
|
12
|
+
* - `'server'` (default): the page is requested again with the changes,
|
|
13
|
+
* and the server applies them with `normalizeQuery`.
|
|
14
|
+
* - `'dom'`: text changes are written straight into the elements whose
|
|
15
|
+
* edit links point at the changed field. Needs no server support, so it
|
|
16
|
+
* suits any backend (PHP, Ruby, Python). Changes the page cannot show
|
|
17
|
+
* this way (images, links, reordered or added blocks) appear on save.
|
|
18
|
+
*
|
|
19
|
+
* Every mode renders the stored content again on save.
|
|
20
|
+
*/
|
|
21
|
+
apply?: 'server' | 'dom';
|
|
22
|
+
}
|
|
23
|
+
interface HtmlLivePreview {
|
|
24
|
+
stop(): void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Live preview for server-rendered pages without a client framework (Astro,
|
|
28
|
+
* or any HTML-first site): while an editor types, the page is requested
|
|
29
|
+
* again from the server with the unsaved changes in the request body, and
|
|
30
|
+
* only the parts of the DOM that changed are updated. On save the page is
|
|
31
|
+
* requested without changes. The server applies them with `normalizeQuery`;
|
|
32
|
+
* see `readLiveLogFromRequest`.
|
|
33
|
+
*
|
|
34
|
+
* A backend that cannot run `normalizeQuery` passes `apply: 'dom'`: text
|
|
35
|
+
* changes are then written into the page directly, and the server only
|
|
36
|
+
* renders the stored content on save.
|
|
37
|
+
*
|
|
38
|
+
* A no-op outside the Prepr editor's preview iframe.
|
|
39
|
+
*/
|
|
40
|
+
declare function startHtmlLivePreview(options?: HtmlLivePreviewOptions): HtmlLivePreview;
|
|
41
|
+
|
|
42
|
+
export { type HtmlLivePreviewOptions as H, type HtmlLivePreview as a, startHtmlLivePreview as s };
|