@preprio/toolkit 0.5.0-rc.2 → 0.5.0-rc.4
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 +209 -10
- package/dist/astro/index.cjs +1194 -1
- package/dist/astro/index.d.cts +47 -1
- package/dist/astro/index.d.ts +47 -1
- package/dist/astro/index.js +1197 -1
- package/dist/chunk-46ISDRHT.js +26 -0
- package/dist/chunk-74KY5EAM.js +1219 -0
- package/dist/chunk-AHBN6FQP.js +184 -0
- package/dist/{chunk-FLPNN3CV.js → chunk-PDNCNTDB.js} +1370 -623
- package/dist/chunk-S3VT4UWM.js +75 -0
- package/dist/chunk-TG6N3C22.js +246 -0
- package/dist/chunk-URXSDJOU.js +356 -0
- package/dist/index.cjs +3166 -392
- package/dist/index.d.cts +238 -4
- package/dist/index.d.ts +238 -4
- package/dist/index.js +189 -149
- package/dist/live-preview-H4643QYF.js +18 -0
- package/dist/nextjs/components.cjs +1522 -359
- package/dist/nextjs/components.d.cts +1 -1
- package/dist/nextjs/components.d.ts +1 -1
- package/dist/nextjs/components.js +4 -1
- package/dist/nextjs/index.cjs +1085 -6
- package/dist/nextjs/index.d.cts +73 -1
- package/dist/nextjs/index.d.ts +73 -1
- package/dist/nextjs/index.js +1079 -7
- package/dist/nextjs/live-action.cjs +285 -0
- package/dist/nextjs/live-action.d.cts +12 -0
- package/dist/nextjs/live-action.d.ts +12 -0
- package/dist/nextjs/live-action.js +68 -0
- package/dist/nextjs/live.cjs +1896 -0
- package/dist/nextjs/live.d.cts +52 -0
- package/dist/nextjs/live.d.ts +52 -0
- package/dist/nextjs/live.js +265 -0
- package/dist/nuxt/index.cjs +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/nuxt/live-preview-O2ZTJNW4.js +1520 -0
- package/dist/nuxt/live.cjs +1720 -0
- package/dist/nuxt/live.d.cts +43 -0
- package/dist/nuxt/live.d.ts +43 -0
- package/dist/nuxt/live.js +94 -0
- package/dist/{pixel-B3oQNbZ3.d.cts → pixel-CX422dCO.d.cts} +10 -1
- package/dist/{pixel-B3oQNbZ3.d.ts → pixel-CX422dCO.d.ts} +10 -1
- package/dist/react/components.cjs +1522 -359
- package/dist/react/components.d.cts +9 -0
- package/dist/react/components.d.ts +9 -0
- package/dist/react/components.js +1522 -359
- package/dist/sveltekit/index.cjs +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/package.json +12 -1
- package/src/astro/PreprToolbar.astro +7 -1
|
@@ -0,0 +1,1520 @@
|
|
|
1
|
+
// src/core/utils.ts
|
|
2
|
+
var trustedParentOrigin = null;
|
|
3
|
+
function getTrustedParentOrigin() {
|
|
4
|
+
return trustedParentOrigin;
|
|
5
|
+
}
|
|
6
|
+
function sendPreprEvent(event, data, options) {
|
|
7
|
+
if (typeof window !== "undefined") {
|
|
8
|
+
const message = {
|
|
9
|
+
name: "prepr_preview_bar",
|
|
10
|
+
event,
|
|
11
|
+
...data
|
|
12
|
+
};
|
|
13
|
+
window.dispatchEvent(
|
|
14
|
+
new CustomEvent("prepr_preview_bar", { detail: message })
|
|
15
|
+
);
|
|
16
|
+
if (window.parent && window.parent !== window) {
|
|
17
|
+
if (trustedParentOrigin) {
|
|
18
|
+
window.parent.postMessage(message, trustedParentOrigin);
|
|
19
|
+
} else if (options?.allowUntrustedTarget) {
|
|
20
|
+
window.parent.postMessage(message, "*");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
var DebugLogger = class {
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.options = {
|
|
28
|
+
prefix: "[Prepr]",
|
|
29
|
+
...options
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// A local `enabled` wins; otherwise defer to the global logger so scoped
|
|
33
|
+
// loggers created before initDebugLogger() still pick up the setting.
|
|
34
|
+
isEnabled() {
|
|
35
|
+
if (this.options.enabled !== void 0) {
|
|
36
|
+
return this.options.enabled;
|
|
37
|
+
}
|
|
38
|
+
return globalDebugLogger?.options?.enabled ?? false;
|
|
39
|
+
}
|
|
40
|
+
log(message, ...args) {
|
|
41
|
+
if (!this.isEnabled()) return;
|
|
42
|
+
const prefix = this.options.prefix;
|
|
43
|
+
console.log(`${prefix} ${message}`, ...args);
|
|
44
|
+
}
|
|
45
|
+
warn(message, ...args) {
|
|
46
|
+
if (!this.isEnabled()) return;
|
|
47
|
+
const prefix = this.options.prefix;
|
|
48
|
+
console.warn(`${prefix} ${message}`, ...args);
|
|
49
|
+
}
|
|
50
|
+
error(message, ...args) {
|
|
51
|
+
if (!this.isEnabled()) return;
|
|
52
|
+
const prefix = this.options.prefix;
|
|
53
|
+
console.error(`${prefix} ${message}`, ...args);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var globalDebugLogger = null;
|
|
57
|
+
function createScopedLogger(scopeName) {
|
|
58
|
+
return new DebugLogger({
|
|
59
|
+
prefix: `[Prepr][${scopeName}]`
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/core/live-preview/index-snapshot.ts
|
|
64
|
+
var debug = createScopedLogger("live-preview");
|
|
65
|
+
function indexSnapshot(data) {
|
|
66
|
+
const index = /* @__PURE__ */ new Map();
|
|
67
|
+
const visited = /* @__PURE__ */ new WeakSet();
|
|
68
|
+
const path = [];
|
|
69
|
+
function walk(node) {
|
|
70
|
+
if (node === null || typeof node !== "object") return;
|
|
71
|
+
if (visited.has(node)) return;
|
|
72
|
+
visited.add(node);
|
|
73
|
+
if (Array.isArray(node)) {
|
|
74
|
+
for (let i = 0; i < node.length; i++) {
|
|
75
|
+
path.push(i);
|
|
76
|
+
walk(node[i]);
|
|
77
|
+
path.pop();
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const record = node;
|
|
82
|
+
if (typeof record._id === "string") {
|
|
83
|
+
const paths = index.get(record._id);
|
|
84
|
+
if (paths) paths.push([...path]);
|
|
85
|
+
else index.set(record._id, [[...path]]);
|
|
86
|
+
} else if (typeof record.__typename === "string") {
|
|
87
|
+
debug.log("item without _id will not live-update", {
|
|
88
|
+
__typename: record.__typename
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
for (const key in record) {
|
|
92
|
+
if (!Object.prototype.hasOwnProperty.call(record, key)) continue;
|
|
93
|
+
path.push(key);
|
|
94
|
+
walk(record[key]);
|
|
95
|
+
path.pop();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
walk(data);
|
|
99
|
+
return index;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/core/live-preview/patch.ts
|
|
103
|
+
import { vercelStegaSplit } from "@vercel/stega";
|
|
104
|
+
|
|
105
|
+
// src/core/live-preview/field-name.ts
|
|
106
|
+
var UPPERCASE_BOUNDARY = /(.)(?=[A-Z])/g;
|
|
107
|
+
function graphqlFieldName(apiId) {
|
|
108
|
+
return apiId.replace(UPPERCASE_BOUNDARY, "$1_").toLowerCase();
|
|
109
|
+
}
|
|
110
|
+
function fieldKeyIn(record, apiId) {
|
|
111
|
+
if (Object.prototype.hasOwnProperty.call(record, apiId)) return apiId;
|
|
112
|
+
const name = graphqlFieldName(apiId);
|
|
113
|
+
return Object.prototype.hasOwnProperty.call(record, name) ? name : void 0;
|
|
114
|
+
}
|
|
115
|
+
var TEXT_BODY_FIELDS = [
|
|
116
|
+
{ name: "body", plain: false },
|
|
117
|
+
{ name: "html", plain: false },
|
|
118
|
+
{ name: "text", plain: true }
|
|
119
|
+
];
|
|
120
|
+
var TAG_PATTERN = /<[^>]+>/g;
|
|
121
|
+
function plainTextFromHtml(html) {
|
|
122
|
+
return html.replace(TAG_PATTERN, "");
|
|
123
|
+
}
|
|
124
|
+
function isTextEntry(record) {
|
|
125
|
+
const typename = record.__typename;
|
|
126
|
+
if (typeof typename === "string") return typename === "Text";
|
|
127
|
+
return "html" in record || "text" in record && "format" in record;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/core/live-preview/patch.ts
|
|
131
|
+
var INDEX_SEGMENT = /^\d+$/;
|
|
132
|
+
var FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
133
|
+
function isPlainObject(value) {
|
|
134
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
135
|
+
}
|
|
136
|
+
function mergeObject(current, incoming) {
|
|
137
|
+
let changed = false;
|
|
138
|
+
const next = { ...current };
|
|
139
|
+
for (const key of Object.keys(current)) {
|
|
140
|
+
if (!(key in incoming) || incoming[key] === void 0) continue;
|
|
141
|
+
const value = isPlainObject(current[key]) && isPlainObject(incoming[key]) ? mergeObject(current[key], incoming[key]) : incoming[key];
|
|
142
|
+
if (value !== current[key]) {
|
|
143
|
+
next[key] = value;
|
|
144
|
+
changed = true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return changed ? next : current;
|
|
148
|
+
}
|
|
149
|
+
function shapeLike(template, incoming) {
|
|
150
|
+
const out = {};
|
|
151
|
+
for (const key of Object.keys(template)) {
|
|
152
|
+
const value = incoming[key];
|
|
153
|
+
out[key] = value === void 0 ? null : isPlainObject(template[key]) && isPlainObject(value) ? shapeLike(template[key], value) : value;
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
function mergeStructured(current, incoming) {
|
|
158
|
+
if (incoming === null || Array.isArray(incoming) && !incoming.length) {
|
|
159
|
+
if (Array.isArray(current)) return current.length ? [] : current;
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const list = Array.isArray(incoming) ? incoming : [incoming];
|
|
163
|
+
if (current === null || current === void 0) return incoming;
|
|
164
|
+
if (Array.isArray(current)) {
|
|
165
|
+
const template = current.find(isPlainObject);
|
|
166
|
+
return list.map((entry) => {
|
|
167
|
+
const match = current.find(
|
|
168
|
+
(c) => isPlainObject(c) && c._id !== void 0 && c._id === entry._id
|
|
169
|
+
);
|
|
170
|
+
if (isPlainObject(match)) return mergeObject(match, entry);
|
|
171
|
+
return isPlainObject(template) ? shapeLike(template, entry) : entry;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (isPlainObject(current)) return mergeObject(current, list[0]);
|
|
175
|
+
return current;
|
|
176
|
+
}
|
|
177
|
+
function isPatchable(current) {
|
|
178
|
+
return current === null || typeof current === "string" || typeof current === "number" || typeof current === "boolean";
|
|
179
|
+
}
|
|
180
|
+
function cloneNode(node) {
|
|
181
|
+
return Array.isArray(node) ? [...node] : { ...node };
|
|
182
|
+
}
|
|
183
|
+
function patchSnapshot(snapshot, index, itemId, field, value) {
|
|
184
|
+
const paths = index.get(itemId);
|
|
185
|
+
if (!paths?.length) return snapshot;
|
|
186
|
+
let root = snapshot;
|
|
187
|
+
let changed = false;
|
|
188
|
+
const markChanged = () => {
|
|
189
|
+
changed = true;
|
|
190
|
+
};
|
|
191
|
+
for (const path of paths) {
|
|
192
|
+
for (const [target, targetValue] of expandTextBody(
|
|
193
|
+
root,
|
|
194
|
+
path,
|
|
195
|
+
field,
|
|
196
|
+
value
|
|
197
|
+
)) {
|
|
198
|
+
root = patchAtPath(root, path, target, targetValue, markChanged);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return changed ? root : snapshot;
|
|
202
|
+
}
|
|
203
|
+
function expandTextBody(root, path, field, value) {
|
|
204
|
+
const segments = field.split(".");
|
|
205
|
+
if (segments[segments.length - 1] !== "body" || typeof value !== "string") {
|
|
206
|
+
return [[field, value]];
|
|
207
|
+
}
|
|
208
|
+
let node = root;
|
|
209
|
+
for (const step of path) {
|
|
210
|
+
node = node[step];
|
|
211
|
+
}
|
|
212
|
+
for (const segment of segments.slice(0, -1)) {
|
|
213
|
+
if (node === null || typeof node !== "object") return [[field, value]];
|
|
214
|
+
const record2 = node;
|
|
215
|
+
node = INDEX_SEGMENT.test(segment) ? record2[Number(segment)] : record2[fieldKeyIn(record2, segment) ?? segment];
|
|
216
|
+
}
|
|
217
|
+
if (node === null || typeof node !== "object" || Array.isArray(node)) {
|
|
218
|
+
return [[field, value]];
|
|
219
|
+
}
|
|
220
|
+
const record = node;
|
|
221
|
+
if (!isTextEntry(record)) return [[field, value]];
|
|
222
|
+
const prefix = segments.slice(0, -1);
|
|
223
|
+
const writes = [];
|
|
224
|
+
for (const { name, plain } of TEXT_BODY_FIELDS) {
|
|
225
|
+
if (!(name in record)) continue;
|
|
226
|
+
writes.push([
|
|
227
|
+
[...prefix, name].join("."),
|
|
228
|
+
plain ? plainTextFromHtml(value) : value
|
|
229
|
+
]);
|
|
230
|
+
}
|
|
231
|
+
return writes.length ? writes : [[field, value]];
|
|
232
|
+
}
|
|
233
|
+
function patchAtPath(root, path, field, value, markChanged) {
|
|
234
|
+
const segments = field.split(".");
|
|
235
|
+
if (segments.some((s) => FORBIDDEN_SEGMENTS.has(s))) return root;
|
|
236
|
+
let node = root;
|
|
237
|
+
for (const step of path) {
|
|
238
|
+
node = node[step];
|
|
239
|
+
}
|
|
240
|
+
const fullPath = [...path];
|
|
241
|
+
for (const segment of segments) {
|
|
242
|
+
let step = segment;
|
|
243
|
+
if (INDEX_SEGMENT.test(segment)) {
|
|
244
|
+
step = Number(segment);
|
|
245
|
+
} else if (node !== null && typeof node === "object") {
|
|
246
|
+
step = fieldKeyIn(node, segment) ?? segment;
|
|
247
|
+
}
|
|
248
|
+
fullPath.push(step);
|
|
249
|
+
node = node !== null && typeof node === "object" ? node[step] : void 0;
|
|
250
|
+
}
|
|
251
|
+
return writeAtPath(root, fullPath, value, markChanged);
|
|
252
|
+
}
|
|
253
|
+
function writeAtPath(root, fullPath, value, markChanged = () => {
|
|
254
|
+
}) {
|
|
255
|
+
if (!fullPath.length) return root;
|
|
256
|
+
if (fullPath.some((s) => FORBIDDEN_SEGMENTS.has(String(s)))) return root;
|
|
257
|
+
const last = fullPath.length - 1;
|
|
258
|
+
let node = root;
|
|
259
|
+
for (let i = 0; i < last; i++) {
|
|
260
|
+
if (node === null || typeof node !== "object") return root;
|
|
261
|
+
node = node[fullPath[i]];
|
|
262
|
+
}
|
|
263
|
+
if (node === null || typeof node !== "object") return root;
|
|
264
|
+
const container = node;
|
|
265
|
+
const key = fullPath[last];
|
|
266
|
+
if (!(key in container)) return root;
|
|
267
|
+
let next;
|
|
268
|
+
if (value === null || typeof value === "object") {
|
|
269
|
+
next = mergeStructured(container[key], value);
|
|
270
|
+
} else if (!isPatchable(container[key])) {
|
|
271
|
+
return root;
|
|
272
|
+
} else {
|
|
273
|
+
next = keepEditLink(container[key], value);
|
|
274
|
+
}
|
|
275
|
+
if (container[key] === next) return root;
|
|
276
|
+
const newRoot = cloneNode(root);
|
|
277
|
+
let cursor = newRoot;
|
|
278
|
+
for (let i = 0; i < last; i++) {
|
|
279
|
+
const child = cloneNode(cursor[fullPath[i]]);
|
|
280
|
+
cursor[fullPath[i]] = child;
|
|
281
|
+
cursor = child;
|
|
282
|
+
}
|
|
283
|
+
cursor[key] = next;
|
|
284
|
+
markChanged();
|
|
285
|
+
return newRoot;
|
|
286
|
+
}
|
|
287
|
+
function listKeysOf(entry) {
|
|
288
|
+
if (!isPlainObject(entry)) return [];
|
|
289
|
+
const keys = [];
|
|
290
|
+
if (typeof entry._id === "string") keys.push(entry._id);
|
|
291
|
+
const first = Array.isArray(entry.items) ? entry.items[0] : null;
|
|
292
|
+
if (isPlainObject(first) && typeof first._id === "string") {
|
|
293
|
+
keys.push(first._id);
|
|
294
|
+
}
|
|
295
|
+
return keys;
|
|
296
|
+
}
|
|
297
|
+
function buildText(entry, template) {
|
|
298
|
+
const body = typeof entry.body === "string" ? entry.body : "";
|
|
299
|
+
const full = {
|
|
300
|
+
__typename: "Text",
|
|
301
|
+
_id: entry._id,
|
|
302
|
+
body,
|
|
303
|
+
html: body,
|
|
304
|
+
text: plainTextFromHtml(body),
|
|
305
|
+
format: entry.format ?? null
|
|
306
|
+
};
|
|
307
|
+
if (!template) return full;
|
|
308
|
+
const out = {};
|
|
309
|
+
for (const key of Object.keys(template)) {
|
|
310
|
+
out[key] = key in full ? full[key] : null;
|
|
311
|
+
}
|
|
312
|
+
return out;
|
|
313
|
+
}
|
|
314
|
+
function syncList(current, entries, build) {
|
|
315
|
+
if (current === null || current === void 0) {
|
|
316
|
+
const built = syncList([], entries, build);
|
|
317
|
+
return built.length ? built : current;
|
|
318
|
+
}
|
|
319
|
+
if (!Array.isArray(current)) return current;
|
|
320
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
321
|
+
const templates = /* @__PURE__ */ new Map();
|
|
322
|
+
for (const c of current) {
|
|
323
|
+
for (const key of listKeysOf(c)) byKey.set(key, c);
|
|
324
|
+
if (isPlainObject(c) && typeof c.__typename === "string") {
|
|
325
|
+
if (!templates.has(c.__typename)) templates.set(c.__typename, c);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (!entries.length) return current.length ? [] : current;
|
|
329
|
+
if (current.length && !byKey.size) return current;
|
|
330
|
+
if (current.length && !entries.some((e) => byKey.has(e._id))) return current;
|
|
331
|
+
const next = [];
|
|
332
|
+
for (const entry of entries) {
|
|
333
|
+
const existing = byKey.get(entry._id);
|
|
334
|
+
const built = existing === void 0 && entry.fields ? build?.(entry) : void 0;
|
|
335
|
+
if (existing !== void 0) {
|
|
336
|
+
next.push(existing);
|
|
337
|
+
} else if (built) {
|
|
338
|
+
next.push(built);
|
|
339
|
+
} else if (entry.__typename === "Text") {
|
|
340
|
+
next.push(buildText(entry, templates.get("Text")));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const same = next.length === current.length && next.every((n, i) => n === current[i]);
|
|
344
|
+
return same ? current : next;
|
|
345
|
+
}
|
|
346
|
+
function syncListSnapshot(snapshot, index, itemId, field, entries) {
|
|
347
|
+
const paths = index.get(itemId);
|
|
348
|
+
if (!paths?.length) return snapshot;
|
|
349
|
+
const fieldPath = field.split(".");
|
|
350
|
+
if (fieldPath.some((s) => FORBIDDEN_SEGMENTS.has(s))) return snapshot;
|
|
351
|
+
let root = snapshot;
|
|
352
|
+
for (const path of paths) {
|
|
353
|
+
root = syncListAtPath(root, [...path, ...fieldPath], entries);
|
|
354
|
+
}
|
|
355
|
+
return root;
|
|
356
|
+
}
|
|
357
|
+
function syncListAtPath(root, fullPath, entries, build) {
|
|
358
|
+
if (!fullPath.length) return root;
|
|
359
|
+
if (fullPath.some((s) => FORBIDDEN_SEGMENTS.has(String(s)))) return root;
|
|
360
|
+
let node = root;
|
|
361
|
+
for (const key of fullPath) {
|
|
362
|
+
node = node !== null && typeof node === "object" ? node[key] : void 0;
|
|
363
|
+
}
|
|
364
|
+
const next = syncList(node, entries, build);
|
|
365
|
+
if (next === node) return root;
|
|
366
|
+
const newRoot = cloneNode(root);
|
|
367
|
+
let cursor = newRoot;
|
|
368
|
+
for (let i = 0; i < fullPath.length - 1; i++) {
|
|
369
|
+
const child = cloneNode(cursor[fullPath[i]]);
|
|
370
|
+
cursor[fullPath[i]] = child;
|
|
371
|
+
cursor = child;
|
|
372
|
+
}
|
|
373
|
+
cursor[fullPath[fullPath.length - 1]] = next;
|
|
374
|
+
return newRoot;
|
|
375
|
+
}
|
|
376
|
+
function keepEditLink(current, value) {
|
|
377
|
+
if (typeof current !== "string" || typeof value !== "string") return value;
|
|
378
|
+
const { encoded } = vercelStegaSplit(current);
|
|
379
|
+
if (!encoded || vercelStegaSplit(value).encoded) return value;
|
|
380
|
+
return value + encoded;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/core/live-preview/live-store.ts
|
|
384
|
+
var debug2 = createScopedLogger("live-preview");
|
|
385
|
+
var INVALIDATE_DEBOUNCE_MS = 150;
|
|
386
|
+
var MAX_LOG_ENTRIES = 500;
|
|
387
|
+
function withPatch(log, patch) {
|
|
388
|
+
let previous = -1;
|
|
389
|
+
for (let i = log.length - 1; i >= 0; i--) {
|
|
390
|
+
const p = log[i];
|
|
391
|
+
if (p.type === patch.type && p.itemId === patch.itemId && p.field === patch.field) {
|
|
392
|
+
previous = i;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (previous === -1) return [...log, patch];
|
|
397
|
+
const since = log.slice(previous + 1);
|
|
398
|
+
const listPrefix = `${patch.field}.`;
|
|
399
|
+
const dependsOnPosition = since.some(
|
|
400
|
+
(p) => p.type === "list" || patch.type === "list" && p.itemId === patch.itemId && p.field.startsWith(listPrefix)
|
|
401
|
+
);
|
|
402
|
+
if (dependsOnPosition) return [...log, patch];
|
|
403
|
+
return [...log.slice(0, previous), ...since, patch];
|
|
404
|
+
}
|
|
405
|
+
function createLiveStore(options = {}) {
|
|
406
|
+
const accepts = (patch) => options.accept ? options.accept(patch) : true;
|
|
407
|
+
const entries = /* @__PURE__ */ new Set();
|
|
408
|
+
const invalidateListeners = /* @__PURE__ */ new Set();
|
|
409
|
+
const pendingInvalidates = /* @__PURE__ */ new Map();
|
|
410
|
+
let defaultFetcher = null;
|
|
411
|
+
let log = [];
|
|
412
|
+
const logListeners = /* @__PURE__ */ new Set();
|
|
413
|
+
function record(patch) {
|
|
414
|
+
log = withPatch(log, patch).slice(-MAX_LOG_ENTRIES);
|
|
415
|
+
logListeners.forEach((fn) => fn(log));
|
|
416
|
+
}
|
|
417
|
+
function notify(entry) {
|
|
418
|
+
entry.listeners.forEach((fn) => fn());
|
|
419
|
+
}
|
|
420
|
+
async function refetch(entry) {
|
|
421
|
+
const fetcher = entry.opts.fetcher ?? defaultFetcher;
|
|
422
|
+
if (!fetcher || entry.opts.query == null) return;
|
|
423
|
+
const token = ++entry.fetchToken;
|
|
424
|
+
try {
|
|
425
|
+
const result = await fetcher(entry.opts.query, entry.opts.variables);
|
|
426
|
+
if (token !== entry.fetchToken || !entries.has(entry)) return;
|
|
427
|
+
entry.snapshot = result;
|
|
428
|
+
entry.index = indexSnapshot(result);
|
|
429
|
+
notify(entry);
|
|
430
|
+
} catch (error) {
|
|
431
|
+
debug2.log("refetch failed, keeping previous snapshot", error);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function runInvalidate(itemId) {
|
|
435
|
+
pendingInvalidates.delete(itemId);
|
|
436
|
+
invalidateListeners.forEach((fn) => fn(itemId));
|
|
437
|
+
for (const entry of entries) {
|
|
438
|
+
if (entry.index.has(itemId)) void refetch(entry);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
register(data, opts = {}) {
|
|
443
|
+
const entry = {
|
|
444
|
+
snapshot: data,
|
|
445
|
+
index: indexSnapshot(data),
|
|
446
|
+
opts,
|
|
447
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
448
|
+
fetchToken: 0
|
|
449
|
+
};
|
|
450
|
+
for (const patch of log) {
|
|
451
|
+
if (patch.type === "field") {
|
|
452
|
+
entry.snapshot = patchSnapshot(
|
|
453
|
+
entry.snapshot,
|
|
454
|
+
entry.index,
|
|
455
|
+
patch.itemId,
|
|
456
|
+
patch.field,
|
|
457
|
+
patch.value
|
|
458
|
+
);
|
|
459
|
+
} else {
|
|
460
|
+
const next = syncListSnapshot(
|
|
461
|
+
entry.snapshot,
|
|
462
|
+
entry.index,
|
|
463
|
+
patch.itemId,
|
|
464
|
+
patch.field,
|
|
465
|
+
patch.entries
|
|
466
|
+
);
|
|
467
|
+
if (next !== entry.snapshot) {
|
|
468
|
+
entry.snapshot = next;
|
|
469
|
+
entry.index = indexSnapshot(next);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
entries.add(entry);
|
|
474
|
+
return {
|
|
475
|
+
getSnapshot: () => entry.snapshot,
|
|
476
|
+
subscribe(fn) {
|
|
477
|
+
entry.listeners.add(fn);
|
|
478
|
+
return () => entry.listeners.delete(fn);
|
|
479
|
+
},
|
|
480
|
+
unregister() {
|
|
481
|
+
entry.listeners.clear();
|
|
482
|
+
entries.delete(entry);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
},
|
|
486
|
+
pushPatch(itemId, field, value, edit) {
|
|
487
|
+
const patch = edit ? { type: "field", itemId, field, value, edit } : { type: "field", itemId, field, value };
|
|
488
|
+
if (!accepts(patch)) return;
|
|
489
|
+
record(patch);
|
|
490
|
+
for (const entry of entries) {
|
|
491
|
+
const next = patchSnapshot(
|
|
492
|
+
entry.snapshot,
|
|
493
|
+
entry.index,
|
|
494
|
+
itemId,
|
|
495
|
+
field,
|
|
496
|
+
value
|
|
497
|
+
);
|
|
498
|
+
if (next !== entry.snapshot) {
|
|
499
|
+
entry.snapshot = next;
|
|
500
|
+
notify(entry);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
pushList(itemId, field, list) {
|
|
505
|
+
const patch = { type: "list", itemId, field, entries: list };
|
|
506
|
+
if (!accepts(patch)) return;
|
|
507
|
+
record(patch);
|
|
508
|
+
for (const entry of entries) {
|
|
509
|
+
const next = syncListSnapshot(
|
|
510
|
+
entry.snapshot,
|
|
511
|
+
entry.index,
|
|
512
|
+
itemId,
|
|
513
|
+
field,
|
|
514
|
+
list
|
|
515
|
+
);
|
|
516
|
+
if (next !== entry.snapshot) {
|
|
517
|
+
entry.snapshot = next;
|
|
518
|
+
entry.index = indexSnapshot(next);
|
|
519
|
+
notify(entry);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
invalidate(itemId) {
|
|
524
|
+
if (log.length) {
|
|
525
|
+
log = [];
|
|
526
|
+
logListeners.forEach((fn) => fn(log));
|
|
527
|
+
}
|
|
528
|
+
const pending = pendingInvalidates.get(itemId);
|
|
529
|
+
if (pending) clearTimeout(pending);
|
|
530
|
+
pendingInvalidates.set(
|
|
531
|
+
itemId,
|
|
532
|
+
setTimeout(() => runInvalidate(itemId), INVALIDATE_DEBOUNCE_MS)
|
|
533
|
+
);
|
|
534
|
+
},
|
|
535
|
+
setFetcher(fetcher) {
|
|
536
|
+
defaultFetcher = fetcher;
|
|
537
|
+
},
|
|
538
|
+
onInvalidate(fn) {
|
|
539
|
+
invalidateListeners.add(fn);
|
|
540
|
+
return () => invalidateListeners.delete(fn);
|
|
541
|
+
},
|
|
542
|
+
getLog: () => log,
|
|
543
|
+
onLogChange(fn) {
|
|
544
|
+
logListeners.add(fn);
|
|
545
|
+
return () => logListeners.delete(fn);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/core/iframe-bridge.ts
|
|
551
|
+
var debug3 = createScopedLogger("iframe-bridge");
|
|
552
|
+
var EDITOR_BASE_DOMAIN = "prepr.io";
|
|
553
|
+
function isAllowedEditorOrigin(origin, allowed) {
|
|
554
|
+
if (allowed) {
|
|
555
|
+
if (allowed.includes("*")) return origin !== "null";
|
|
556
|
+
return allowed.includes(origin);
|
|
557
|
+
}
|
|
558
|
+
let url;
|
|
559
|
+
try {
|
|
560
|
+
url = new URL(origin);
|
|
561
|
+
} catch {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
if (url.protocol !== "https:" || url.port !== "") return false;
|
|
565
|
+
const suffix = `.${EDITOR_BASE_DOMAIN}`;
|
|
566
|
+
if (!url.hostname.endsWith(suffix)) return false;
|
|
567
|
+
const label = url.hostname.slice(0, -suffix.length);
|
|
568
|
+
return label.length > 0 && !label.includes(".");
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/core/live-preview/validate.ts
|
|
572
|
+
function isScalar(v) {
|
|
573
|
+
return typeof v === "string" || typeof v === "number" || typeof v === "boolean";
|
|
574
|
+
}
|
|
575
|
+
var MAX_DEPTH = 3;
|
|
576
|
+
var MAX_ENTRIES = 50;
|
|
577
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
578
|
+
var SAFE_URL = /^https?:\/\//i;
|
|
579
|
+
var URL_KEY = /^(?:url|href|src|link)$|(?:_url|Url)$/;
|
|
580
|
+
var URL_IGNORED_ANYWHERE = /[\t\n\r]/g;
|
|
581
|
+
var SCRIPT_SCHEME = /^(?:javascript|vbscript|data):/i;
|
|
582
|
+
function isSafeLinkValue(value) {
|
|
583
|
+
const stripped = value.replace(URL_IGNORED_ANYWHERE, "");
|
|
584
|
+
let start = 0;
|
|
585
|
+
while (start < stripped.length && stripped.charCodeAt(start) <= 32) start++;
|
|
586
|
+
return !SCRIPT_SCHEME.test(stripped.slice(start));
|
|
587
|
+
}
|
|
588
|
+
function isUrlKey(key) {
|
|
589
|
+
return URL_KEY.test(key);
|
|
590
|
+
}
|
|
591
|
+
function isSafeKeyValue(key, value) {
|
|
592
|
+
if (typeof value !== "string") return true;
|
|
593
|
+
if (key === "url") return SAFE_URL.test(value);
|
|
594
|
+
return !isUrlKey(key) || isSafeLinkValue(value);
|
|
595
|
+
}
|
|
596
|
+
function isLiveValue(v, depth = 0) {
|
|
597
|
+
if (v === null || isScalar(v)) return true;
|
|
598
|
+
if (depth >= MAX_DEPTH) return false;
|
|
599
|
+
if (Array.isArray(v)) {
|
|
600
|
+
return depth === 0 && v.length <= MAX_ENTRIES && v.every((entry) => isPlainRecord(entry) && isLiveValue(entry, depth + 1));
|
|
601
|
+
}
|
|
602
|
+
if (!isPlainRecord(v)) return false;
|
|
603
|
+
const keys = Object.keys(v);
|
|
604
|
+
if (keys.length > MAX_ENTRIES) return false;
|
|
605
|
+
return keys.every((key) => {
|
|
606
|
+
if (FORBIDDEN_KEYS.has(key)) return false;
|
|
607
|
+
const value = v[key];
|
|
608
|
+
if (!isSafeKeyValue(key, value)) return false;
|
|
609
|
+
return !Array.isArray(value) && isLiveValue(value, depth + 1);
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
function isLiveFieldChange(field, value) {
|
|
613
|
+
if (!isLiveValue(value)) return false;
|
|
614
|
+
const name = field.slice(field.lastIndexOf(".") + 1);
|
|
615
|
+
return typeof value !== "string" || !isUrlKey(name) || isSafeLinkValue(value);
|
|
616
|
+
}
|
|
617
|
+
var MAX_LIST_ENTRIES = 200;
|
|
618
|
+
function parseListEntries(value) {
|
|
619
|
+
if (!Array.isArray(value) || value.length > MAX_LIST_ENTRIES) return null;
|
|
620
|
+
const list = [];
|
|
621
|
+
for (const raw of value) {
|
|
622
|
+
if (!isPlainRecord(raw) || typeof raw._id !== "string" || !raw._id) {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
const entry = { _id: raw._id };
|
|
626
|
+
if (typeof raw.__typename === "string") entry.__typename = raw.__typename;
|
|
627
|
+
if (typeof raw.body === "string") entry.body = raw.body;
|
|
628
|
+
if (typeof raw.format === "string" || raw.format === null) {
|
|
629
|
+
entry.format = raw.format;
|
|
630
|
+
}
|
|
631
|
+
if (typeof raw.typeName === "string" && raw.typeName.length <= 200) {
|
|
632
|
+
entry.typeName = raw.typeName;
|
|
633
|
+
}
|
|
634
|
+
if (raw.fields !== void 0) {
|
|
635
|
+
if (!isPlainRecord(raw.fields) || !isFieldValue(raw.fields, 0)) {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
entry.fields = raw.fields;
|
|
639
|
+
}
|
|
640
|
+
list.push(entry);
|
|
641
|
+
}
|
|
642
|
+
return list;
|
|
643
|
+
}
|
|
644
|
+
function isPlainRecord(v) {
|
|
645
|
+
return v !== null && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype;
|
|
646
|
+
}
|
|
647
|
+
var MAX_FIELD_DEPTH = 6;
|
|
648
|
+
var MAX_FIELD_KEYS = 100;
|
|
649
|
+
function isFieldValue(v, depth) {
|
|
650
|
+
if (v === null || isScalar(v)) return true;
|
|
651
|
+
if (depth >= MAX_FIELD_DEPTH) return false;
|
|
652
|
+
if (Array.isArray(v)) {
|
|
653
|
+
return v.length <= MAX_ENTRIES && v.every(
|
|
654
|
+
(entry) => isScalar(entry) || isPlainRecord(entry) && isFieldValue(entry, depth + 1)
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
if (!isPlainRecord(v)) return false;
|
|
658
|
+
const keys = Object.keys(v);
|
|
659
|
+
if (keys.length > MAX_FIELD_KEYS) return false;
|
|
660
|
+
return keys.every((key) => {
|
|
661
|
+
if (FORBIDDEN_KEYS.has(key)) return false;
|
|
662
|
+
const value = v[key];
|
|
663
|
+
if (!isSafeKeyValue(key, value)) return false;
|
|
664
|
+
return isFieldValue(value, depth + 1);
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
var MAX_EDIT_LENGTH = 1e3;
|
|
668
|
+
function parseEditTarget(raw) {
|
|
669
|
+
if (!isPlainRecord(raw)) return void 0;
|
|
670
|
+
const { item, field } = raw;
|
|
671
|
+
if (typeof item !== "string" || !item || item.length > MAX_EDIT_LENGTH) {
|
|
672
|
+
return void 0;
|
|
673
|
+
}
|
|
674
|
+
if (typeof field !== "string" || !field || field.length > MAX_EDIT_LENGTH) {
|
|
675
|
+
return void 0;
|
|
676
|
+
}
|
|
677
|
+
return { item, field };
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// src/core/live-preview/bridge-events.ts
|
|
681
|
+
var debug4 = createScopedLogger("live-preview:bridge");
|
|
682
|
+
function createLiveBridge(store, options = {}) {
|
|
683
|
+
let parentOrigin = null;
|
|
684
|
+
const onMessage = (evt) => {
|
|
685
|
+
const data = evt?.data;
|
|
686
|
+
if (data?.event === "prepr:initVE" && !parentOrigin) {
|
|
687
|
+
if (isAllowedEditorOrigin(evt.origin, options.allowedEditorOrigins?.())) {
|
|
688
|
+
parentOrigin = evt.origin;
|
|
689
|
+
} else {
|
|
690
|
+
debug4.warn("ignored live preview handshake from origin", evt.origin);
|
|
691
|
+
}
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
const trusted = parentOrigin ?? getTrustedParentOrigin();
|
|
695
|
+
if (!trusted || evt.origin !== trusted) return;
|
|
696
|
+
if (data?.event === "prepr:fieldChange") {
|
|
697
|
+
if (typeof data.itemId === "string" && typeof data.field === "string" && data.field && isLiveFieldChange(data.field, data.value)) {
|
|
698
|
+
const edit = parseEditTarget(data.edit);
|
|
699
|
+
if (edit) store.pushPatch(data.itemId, data.field, data.value, edit);
|
|
700
|
+
else store.pushPatch(data.itemId, data.field, data.value);
|
|
701
|
+
} else {
|
|
702
|
+
debug4.log("dropped malformed fieldChange", data);
|
|
703
|
+
}
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (data?.event === "prepr:listChange") {
|
|
707
|
+
const list = parseListEntries(data.entries);
|
|
708
|
+
if (typeof data.itemId === "string" && typeof data.field === "string" && data.field && list) {
|
|
709
|
+
store.pushList(data.itemId, data.field, list);
|
|
710
|
+
} else {
|
|
711
|
+
debug4.log("dropped malformed listChange", data);
|
|
712
|
+
}
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (data?.event === "prepr:itemChanged") {
|
|
716
|
+
if (typeof data.itemId === "string") {
|
|
717
|
+
store.invalidate(data.itemId);
|
|
718
|
+
} else {
|
|
719
|
+
debug4.log("dropped malformed itemChanged", data);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
return {
|
|
724
|
+
start() {
|
|
725
|
+
window.addEventListener("message", onMessage);
|
|
726
|
+
sendPreprEvent(
|
|
727
|
+
"live_ready",
|
|
728
|
+
options.strictMode?.() ? { strictMode: true } : void 0,
|
|
729
|
+
{ allowUntrustedTarget: true }
|
|
730
|
+
);
|
|
731
|
+
},
|
|
732
|
+
stop() {
|
|
733
|
+
window.removeEventListener("message", onMessage);
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// src/core/live-preview/normalize-query.ts
|
|
739
|
+
import {
|
|
740
|
+
vercelStegaCombine,
|
|
741
|
+
vercelStegaDecode,
|
|
742
|
+
vercelStegaSplit as vercelStegaSplit2
|
|
743
|
+
} from "@vercel/stega";
|
|
744
|
+
|
|
745
|
+
// src/core/live-preview/query-document.ts
|
|
746
|
+
var NAME_START = /[_A-Za-z]/;
|
|
747
|
+
var NAME_CONTINUE = /[_0-9A-Za-z]/;
|
|
748
|
+
var PUNCT = /* @__PURE__ */ new Set([
|
|
749
|
+
"!",
|
|
750
|
+
"$",
|
|
751
|
+
"&",
|
|
752
|
+
"(",
|
|
753
|
+
")",
|
|
754
|
+
":",
|
|
755
|
+
"=",
|
|
756
|
+
"@",
|
|
757
|
+
"[",
|
|
758
|
+
"]",
|
|
759
|
+
"{",
|
|
760
|
+
"|",
|
|
761
|
+
"}"
|
|
762
|
+
]);
|
|
763
|
+
function tokenize(source) {
|
|
764
|
+
const tokens = [];
|
|
765
|
+
let i = 0;
|
|
766
|
+
const n = source.length;
|
|
767
|
+
while (i < n) {
|
|
768
|
+
const ch = source[i];
|
|
769
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "," || ch === "\uFEFF") {
|
|
770
|
+
i++;
|
|
771
|
+
} else if (ch === "#") {
|
|
772
|
+
while (i < n && source[i] !== "\n" && source[i] !== "\r") i++;
|
|
773
|
+
} else if (ch === "." && source.startsWith("...", i)) {
|
|
774
|
+
tokens.push({ type: "punct", value: "..." });
|
|
775
|
+
i += 3;
|
|
776
|
+
} else if (PUNCT.has(ch)) {
|
|
777
|
+
tokens.push({ type: "punct", value: ch });
|
|
778
|
+
i++;
|
|
779
|
+
} else if (NAME_START.test(ch)) {
|
|
780
|
+
const start = i;
|
|
781
|
+
while (i < n && NAME_CONTINUE.test(source[i])) i++;
|
|
782
|
+
tokens.push({ type: "name", value: source.slice(start, i) });
|
|
783
|
+
} else if (ch === '"') {
|
|
784
|
+
if (source.startsWith('"""', i)) {
|
|
785
|
+
i += 3;
|
|
786
|
+
while (i < n && !source.startsWith('"""', i)) {
|
|
787
|
+
i += source.startsWith('\\"""', i) ? 4 : 1;
|
|
788
|
+
}
|
|
789
|
+
if (i >= n) throw new SyntaxError("Unterminated block string");
|
|
790
|
+
i += 3;
|
|
791
|
+
} else {
|
|
792
|
+
i++;
|
|
793
|
+
while (i < n && source[i] !== '"') {
|
|
794
|
+
if (source[i] === "\n" || source[i] === "\r") {
|
|
795
|
+
throw new SyntaxError("Unterminated string");
|
|
796
|
+
}
|
|
797
|
+
i += source[i] === "\\" ? 2 : 1;
|
|
798
|
+
}
|
|
799
|
+
if (i >= n) throw new SyntaxError("Unterminated string");
|
|
800
|
+
i++;
|
|
801
|
+
}
|
|
802
|
+
tokens.push({ type: "value" });
|
|
803
|
+
} else if (ch === "-" || ch >= "0" && ch <= "9") {
|
|
804
|
+
i++;
|
|
805
|
+
while (i < n && /[0-9.eE+-]/.test(source[i])) i++;
|
|
806
|
+
tokens.push({ type: "value" });
|
|
807
|
+
} else {
|
|
808
|
+
throw new SyntaxError(`Unexpected character "${ch}"`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
return tokens;
|
|
812
|
+
}
|
|
813
|
+
function parseSource(source, operationName) {
|
|
814
|
+
const tokens = tokenize(source);
|
|
815
|
+
let pos = 0;
|
|
816
|
+
const peek = () => tokens[pos];
|
|
817
|
+
const isPunct = (value) => {
|
|
818
|
+
const t = tokens[pos];
|
|
819
|
+
return t?.type === "punct" && t.value === value;
|
|
820
|
+
};
|
|
821
|
+
const expectPunct = (value) => {
|
|
822
|
+
if (!isPunct(value)) throw new SyntaxError(`Expected "${value}"`);
|
|
823
|
+
pos++;
|
|
824
|
+
};
|
|
825
|
+
const expectName = () => {
|
|
826
|
+
const t = tokens[pos];
|
|
827
|
+
if (t?.type !== "name") throw new SyntaxError("Expected a name");
|
|
828
|
+
pos++;
|
|
829
|
+
return t.value;
|
|
830
|
+
};
|
|
831
|
+
const skipParens = () => {
|
|
832
|
+
expectPunct("(");
|
|
833
|
+
let depth = 1;
|
|
834
|
+
while (depth > 0) {
|
|
835
|
+
const t = tokens[pos++];
|
|
836
|
+
if (!t) throw new SyntaxError("Unbalanced parentheses");
|
|
837
|
+
if (t.type === "punct" && t.value === "(") depth++;
|
|
838
|
+
else if (t.type === "punct" && t.value === ")") depth--;
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
const skipDirectives = () => {
|
|
842
|
+
while (isPunct("@")) {
|
|
843
|
+
pos++;
|
|
844
|
+
expectName();
|
|
845
|
+
if (isPunct("(")) skipParens();
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
const parseSelectionSet = () => {
|
|
849
|
+
expectPunct("{");
|
|
850
|
+
const selections = [];
|
|
851
|
+
while (!isPunct("}")) {
|
|
852
|
+
if (!peek()) throw new SyntaxError("Unterminated selection set");
|
|
853
|
+
selections.push(parseSelection());
|
|
854
|
+
}
|
|
855
|
+
pos++;
|
|
856
|
+
return selections;
|
|
857
|
+
};
|
|
858
|
+
const parseSelection = () => {
|
|
859
|
+
if (isPunct("...")) {
|
|
860
|
+
pos++;
|
|
861
|
+
const t = peek();
|
|
862
|
+
if (t?.type === "name" && t.value !== "on") {
|
|
863
|
+
pos++;
|
|
864
|
+
skipDirectives();
|
|
865
|
+
return { kind: "spread", name: t.value };
|
|
866
|
+
}
|
|
867
|
+
let on = null;
|
|
868
|
+
if (t?.type === "name") {
|
|
869
|
+
pos++;
|
|
870
|
+
on = expectName();
|
|
871
|
+
}
|
|
872
|
+
skipDirectives();
|
|
873
|
+
return { kind: "inline", on, selections: parseSelectionSet() };
|
|
874
|
+
}
|
|
875
|
+
let name = expectName();
|
|
876
|
+
const key = name;
|
|
877
|
+
if (isPunct(":")) {
|
|
878
|
+
pos++;
|
|
879
|
+
name = expectName();
|
|
880
|
+
}
|
|
881
|
+
let hasArgs = false;
|
|
882
|
+
if (isPunct("(")) {
|
|
883
|
+
hasArgs = true;
|
|
884
|
+
skipParens();
|
|
885
|
+
}
|
|
886
|
+
skipDirectives();
|
|
887
|
+
const selections = isPunct("{") ? parseSelectionSet() : null;
|
|
888
|
+
return { kind: "field", name, key, hasArgs, selections };
|
|
889
|
+
};
|
|
890
|
+
const fragments = /* @__PURE__ */ new Map();
|
|
891
|
+
const operations = [];
|
|
892
|
+
while (pos < tokens.length) {
|
|
893
|
+
if (isPunct("{")) {
|
|
894
|
+
operations.push({ name: null, selections: parseSelectionSet() });
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
const keyword = expectName();
|
|
898
|
+
if (keyword === "fragment") {
|
|
899
|
+
const name = expectName();
|
|
900
|
+
if (expectName() !== "on") throw new SyntaxError('Expected "on"');
|
|
901
|
+
const on = expectName();
|
|
902
|
+
skipDirectives();
|
|
903
|
+
fragments.set(name, { on, selections: parseSelectionSet() });
|
|
904
|
+
} else if (keyword === "query" || keyword === "mutation" || keyword === "subscription") {
|
|
905
|
+
const t = peek();
|
|
906
|
+
let name = null;
|
|
907
|
+
if (t?.type === "name") name = expectName();
|
|
908
|
+
if (isPunct("(")) skipParens();
|
|
909
|
+
skipDirectives();
|
|
910
|
+
operations.push({ name, selections: parseSelectionSet() });
|
|
911
|
+
} else {
|
|
912
|
+
throw new SyntaxError(`Unexpected "${keyword}"`);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return { operation: pickOperation(operations, operationName), fragments };
|
|
916
|
+
}
|
|
917
|
+
function pickOperation(operations, operationName) {
|
|
918
|
+
const op = operationName ? operations.find((o) => o.name === operationName) : operations[0];
|
|
919
|
+
if (!op) throw new SyntaxError("The document has no operation");
|
|
920
|
+
return op.selections;
|
|
921
|
+
}
|
|
922
|
+
function fromAstSelections(set) {
|
|
923
|
+
return set.selections.map((s) => {
|
|
924
|
+
if (s.kind === "FragmentSpread")
|
|
925
|
+
return { kind: "spread", name: s.name.value };
|
|
926
|
+
if (s.kind === "InlineFragment") {
|
|
927
|
+
return {
|
|
928
|
+
kind: "inline",
|
|
929
|
+
on: s.typeCondition?.name.value ?? null,
|
|
930
|
+
selections: fromAstSelections(s.selectionSet)
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
const name = s.name.value;
|
|
934
|
+
return {
|
|
935
|
+
kind: "field",
|
|
936
|
+
name,
|
|
937
|
+
key: s.alias?.value ?? name,
|
|
938
|
+
hasArgs: Boolean(s.arguments?.length),
|
|
939
|
+
selections: s.selectionSet ? fromAstSelections(s.selectionSet) : null
|
|
940
|
+
};
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
function fromAst(doc, operationName) {
|
|
944
|
+
const fragments = /* @__PURE__ */ new Map();
|
|
945
|
+
const operations = [];
|
|
946
|
+
for (const def of doc.definitions ?? []) {
|
|
947
|
+
if (def.kind === "FragmentDefinition" && def.name && def.typeCondition && def.selectionSet) {
|
|
948
|
+
fragments.set(def.name.value, {
|
|
949
|
+
on: def.typeCondition.name.value,
|
|
950
|
+
selections: fromAstSelections(def.selectionSet)
|
|
951
|
+
});
|
|
952
|
+
} else if (def.kind === "OperationDefinition" && def.selectionSet) {
|
|
953
|
+
operations.push({
|
|
954
|
+
name: def.name?.value ?? null,
|
|
955
|
+
selections: fromAstSelections(def.selectionSet)
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return { operation: pickOperation(operations, operationName), fragments };
|
|
960
|
+
}
|
|
961
|
+
var astCache = /* @__PURE__ */ new WeakMap();
|
|
962
|
+
var sourceCache = /* @__PURE__ */ new Map();
|
|
963
|
+
var SOURCE_CACHE_LIMIT = 200;
|
|
964
|
+
function readQueryDocument(query, operationName) {
|
|
965
|
+
const cacheKey = operationName ?? "";
|
|
966
|
+
if (typeof query === "object" && query !== null && query.kind === "Document") {
|
|
967
|
+
let byName = astCache.get(query);
|
|
968
|
+
if (!byName) astCache.set(query, byName = /* @__PURE__ */ new Map());
|
|
969
|
+
let parsed2 = byName.get(cacheKey);
|
|
970
|
+
if (!parsed2) {
|
|
971
|
+
parsed2 = fromAst(
|
|
972
|
+
query,
|
|
973
|
+
operationName
|
|
974
|
+
);
|
|
975
|
+
byName.set(cacheKey, parsed2);
|
|
976
|
+
}
|
|
977
|
+
return parsed2;
|
|
978
|
+
}
|
|
979
|
+
const source = String(query);
|
|
980
|
+
const sourceKey = `${cacheKey}
|
|
981
|
+
${source}`;
|
|
982
|
+
let parsed = sourceCache.get(sourceKey);
|
|
983
|
+
if (!parsed) {
|
|
984
|
+
parsed = parseSource(source, operationName);
|
|
985
|
+
if (sourceCache.size >= SOURCE_CACHE_LIMIT) {
|
|
986
|
+
sourceCache.delete(sourceCache.keys().next().value);
|
|
987
|
+
}
|
|
988
|
+
sourceCache.set(sourceKey, parsed);
|
|
989
|
+
}
|
|
990
|
+
return parsed;
|
|
991
|
+
}
|
|
992
|
+
var fieldsCache = /* @__PURE__ */ new WeakMap();
|
|
993
|
+
function collectFields(selections, typename, doc, options = {}) {
|
|
994
|
+
const cacheKey = `${options.strict ? "s" : "l"}:${typename ?? ""}`;
|
|
995
|
+
let cached = fieldsCache.get(selections);
|
|
996
|
+
if (!cached) {
|
|
997
|
+
cached = /* @__PURE__ */ new Map();
|
|
998
|
+
fieldsCache.set(selections, cached);
|
|
999
|
+
}
|
|
1000
|
+
const hit = cached.get(cacheKey);
|
|
1001
|
+
if (hit) return hit;
|
|
1002
|
+
const fields = resolveFields(selections, typename, doc, options);
|
|
1003
|
+
cached.set(cacheKey, fields);
|
|
1004
|
+
return fields;
|
|
1005
|
+
}
|
|
1006
|
+
function resolveFields(selections, typename, doc, options) {
|
|
1007
|
+
const exact = /* @__PURE__ */ new Map();
|
|
1008
|
+
const loose = /* @__PURE__ */ new Map();
|
|
1009
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1010
|
+
const walkedMatched = /* @__PURE__ */ new Set();
|
|
1011
|
+
const walkedLoose = /* @__PURE__ */ new Set();
|
|
1012
|
+
const add = (target, s) => {
|
|
1013
|
+
let field = target.get(s.key);
|
|
1014
|
+
if (!field) {
|
|
1015
|
+
field = { name: s.name, key: s.key, hasArgs: s.hasArgs, selections: [] };
|
|
1016
|
+
target.set(s.key, field);
|
|
1017
|
+
merged.set(field, /* @__PURE__ */ new Set());
|
|
1018
|
+
}
|
|
1019
|
+
const done = merged.get(field);
|
|
1020
|
+
if (s.selections && !done.has(s.selections)) {
|
|
1021
|
+
done.add(s.selections);
|
|
1022
|
+
field.selections = [...field.selections, ...s.selections];
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
const applies = (on, list) => conditionMatches(on, typename) || typename !== void 0 && typeConditions(list, doc).includes(typename);
|
|
1026
|
+
const walk = (list, matched) => {
|
|
1027
|
+
for (const s of list) {
|
|
1028
|
+
if (s.kind === "field") {
|
|
1029
|
+
add(matched ? exact : loose, s);
|
|
1030
|
+
} else if (s.kind === "inline") {
|
|
1031
|
+
walk(s.selections, matched && applies(s.on, s.selections));
|
|
1032
|
+
} else {
|
|
1033
|
+
const fragment = doc.fragments.get(s.name);
|
|
1034
|
+
if (!fragment) continue;
|
|
1035
|
+
const asMatched = matched && applies(fragment.on, fragment.selections);
|
|
1036
|
+
if (walkedMatched.has(fragment)) continue;
|
|
1037
|
+
if (!asMatched && walkedLoose.has(fragment)) continue;
|
|
1038
|
+
(asMatched ? walkedMatched : walkedLoose).add(fragment);
|
|
1039
|
+
walk(fragment.selections, asMatched);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
walk(selections, true);
|
|
1044
|
+
if (!options.strict) {
|
|
1045
|
+
for (const [key, field] of loose) {
|
|
1046
|
+
if (!exact.has(key)) exact.set(key, field);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return exact;
|
|
1050
|
+
}
|
|
1051
|
+
function conditionMatches(on, typename) {
|
|
1052
|
+
return on === null || typename === void 0 || on === typename;
|
|
1053
|
+
}
|
|
1054
|
+
var conditionsCache = /* @__PURE__ */ new WeakMap();
|
|
1055
|
+
function typeConditions(selections, doc) {
|
|
1056
|
+
const hit = conditionsCache.get(selections);
|
|
1057
|
+
if (hit) return hit;
|
|
1058
|
+
const names = /* @__PURE__ */ new Set();
|
|
1059
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1060
|
+
const walk = (list) => {
|
|
1061
|
+
for (const s of list) {
|
|
1062
|
+
if (s.kind === "inline") {
|
|
1063
|
+
if (s.on) names.add(s.on);
|
|
1064
|
+
walk(s.selections);
|
|
1065
|
+
} else if (s.kind === "spread") {
|
|
1066
|
+
const fragment = doc.fragments.get(s.name);
|
|
1067
|
+
if (!fragment || seen.has(fragment)) continue;
|
|
1068
|
+
seen.add(fragment);
|
|
1069
|
+
names.add(fragment.on);
|
|
1070
|
+
walk(fragment.selections);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
walk(selections);
|
|
1075
|
+
const conditions = [...names];
|
|
1076
|
+
conditionsCache.set(selections, conditions);
|
|
1077
|
+
return conditions;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// src/core/live-preview/normalize-query.ts
|
|
1081
|
+
var debug5 = createScopedLogger("live-preview:normalize");
|
|
1082
|
+
var MAX_LOG_ENTRIES2 = 500;
|
|
1083
|
+
var MAX_LOG_SOURCE_LENGTH = 1e6;
|
|
1084
|
+
var INDEX_SEGMENT2 = /^\d+$/;
|
|
1085
|
+
var FORBIDDEN_SEGMENTS2 = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
1086
|
+
var ENVELOPE_KEYS = /* @__PURE__ */ new Set(["data", "errors", "extensions"]);
|
|
1087
|
+
function parsePatchLog(raw) {
|
|
1088
|
+
let value = raw;
|
|
1089
|
+
if (typeof value === "string") {
|
|
1090
|
+
if (!value || value.length > MAX_LOG_SOURCE_LENGTH) return [];
|
|
1091
|
+
try {
|
|
1092
|
+
value = JSON.parse(value);
|
|
1093
|
+
} catch {
|
|
1094
|
+
return [];
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
if (!Array.isArray(value)) return [];
|
|
1098
|
+
const log = [];
|
|
1099
|
+
for (const entry of value.slice(-MAX_LOG_ENTRIES2)) {
|
|
1100
|
+
if (!isPlainRecord(entry)) continue;
|
|
1101
|
+
const { type, itemId, field } = entry;
|
|
1102
|
+
if (typeof itemId !== "string" || !itemId) continue;
|
|
1103
|
+
if (typeof field !== "string" || !field) continue;
|
|
1104
|
+
if (type === "field" && isLiveFieldChange(field, entry.value)) {
|
|
1105
|
+
const edit = parseEditTarget(entry.edit);
|
|
1106
|
+
log.push(
|
|
1107
|
+
edit ? { type, itemId, field, value: entry.value, edit } : { type, itemId, field, value: entry.value }
|
|
1108
|
+
);
|
|
1109
|
+
} else if (type === "list") {
|
|
1110
|
+
const entries = parseListEntries(entry.entries);
|
|
1111
|
+
if (entries) log.push({ type, itemId, field, entries });
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
return log;
|
|
1115
|
+
}
|
|
1116
|
+
function normalizeQuery(query, result, options = {}) {
|
|
1117
|
+
return applyPatchLog(
|
|
1118
|
+
query,
|
|
1119
|
+
result,
|
|
1120
|
+
parsePatchLog(options.log),
|
|
1121
|
+
options.operationName
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
function applyPatchLog(query, result, log, operationName) {
|
|
1125
|
+
if (!log.length || result === null || typeof result !== "object") {
|
|
1126
|
+
return result;
|
|
1127
|
+
}
|
|
1128
|
+
if (isEnvelope(result)) {
|
|
1129
|
+
const data = applyPatchLog(query, result.data, log, operationName);
|
|
1130
|
+
return data === result.data ? result : { ...result, data };
|
|
1131
|
+
}
|
|
1132
|
+
let doc = null;
|
|
1133
|
+
try {
|
|
1134
|
+
doc = readQueryDocument(query, operationName);
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
debug5.log("could not read the query; applying fields by literal path", {
|
|
1137
|
+
error: String(error)
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
let root = result;
|
|
1141
|
+
let index = null;
|
|
1142
|
+
for (const patch of log) {
|
|
1143
|
+
try {
|
|
1144
|
+
index ?? (index = indexSnapshot(root));
|
|
1145
|
+
const next = applyPatch(root, index, patch, doc, result);
|
|
1146
|
+
if (next !== root) {
|
|
1147
|
+
root = next;
|
|
1148
|
+
if (patch.type === "list" || !isScalarValue(patch.value)) {
|
|
1149
|
+
index = null;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
debug5.log("could not apply a change", {
|
|
1154
|
+
itemId: patch.itemId,
|
|
1155
|
+
field: patch.field,
|
|
1156
|
+
error: String(error)
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
return root;
|
|
1161
|
+
}
|
|
1162
|
+
function isScalarValue(value) {
|
|
1163
|
+
return value === null || typeof value !== "object";
|
|
1164
|
+
}
|
|
1165
|
+
function applyPatch(root, index, patch, doc, result) {
|
|
1166
|
+
const ownerPaths = index.get(patch.itemId);
|
|
1167
|
+
if (!ownerPaths?.length) return root;
|
|
1168
|
+
const segments = patch.field.split(".");
|
|
1169
|
+
if (segments.some((s) => !s || FORBIDDEN_SEGMENTS2.has(s))) return root;
|
|
1170
|
+
const targets = [];
|
|
1171
|
+
const plain = /* @__PURE__ */ new Set();
|
|
1172
|
+
for (const ownerPath of ownerPaths) {
|
|
1173
|
+
if (doc) {
|
|
1174
|
+
const resolved = resolveTargets(root, doc, ownerPath, segments);
|
|
1175
|
+
root = resolved.root;
|
|
1176
|
+
targets.push(...resolved.targets);
|
|
1177
|
+
resolved.plain.forEach((target) => plain.add(target));
|
|
1178
|
+
} else {
|
|
1179
|
+
targets.push([...ownerPath, ...segments.map(toPathSegment)]);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
if (!targets.length) {
|
|
1183
|
+
debug5.log("no field in the query matches", {
|
|
1184
|
+
itemId: patch.itemId,
|
|
1185
|
+
field: patch.field
|
|
1186
|
+
});
|
|
1187
|
+
return root;
|
|
1188
|
+
}
|
|
1189
|
+
if (patch.type === "field") {
|
|
1190
|
+
for (const target of targets) {
|
|
1191
|
+
const pushed = plain.has(target) && typeof patch.value === "string" ? plainTextFromHtml(patch.value) : patch.value;
|
|
1192
|
+
let value = keepEditLink(readAtPath(root, target), pushed);
|
|
1193
|
+
if (patch.edit && typeof value === "string" && !vercelStegaSplit2(value).encoded) {
|
|
1194
|
+
value = editLinksFor(result, patch.edit.item)?.encode(
|
|
1195
|
+
value,
|
|
1196
|
+
patch.edit.field
|
|
1197
|
+
) ?? value;
|
|
1198
|
+
}
|
|
1199
|
+
root = writeAtPath(root, target, value);
|
|
1200
|
+
}
|
|
1201
|
+
} else {
|
|
1202
|
+
for (const target of targets) {
|
|
1203
|
+
const selections = doc ? selectionsAt(root, doc, target) : null;
|
|
1204
|
+
const build = doc && selections ? (entry) => buildEntry(doc, selections, entry, {
|
|
1205
|
+
links: editLinks(result, entry),
|
|
1206
|
+
lists: listKeysIn(result)
|
|
1207
|
+
}) : void 0;
|
|
1208
|
+
root = syncListAtPath(root, target, patch.entries, build);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
return root;
|
|
1212
|
+
}
|
|
1213
|
+
function readAtPath(root, path) {
|
|
1214
|
+
let node = root;
|
|
1215
|
+
for (const step of path) {
|
|
1216
|
+
if (node === null || typeof node !== "object") return void 0;
|
|
1217
|
+
node = node[step];
|
|
1218
|
+
}
|
|
1219
|
+
return node;
|
|
1220
|
+
}
|
|
1221
|
+
function isEnvelope(value) {
|
|
1222
|
+
if (Array.isArray(value) || !("data" in value)) return false;
|
|
1223
|
+
return Object.keys(value).every((key) => ENVELOPE_KEYS.has(key));
|
|
1224
|
+
}
|
|
1225
|
+
function toPathSegment(segment) {
|
|
1226
|
+
return INDEX_SEGMENT2.test(segment) ? Number(segment) : segment;
|
|
1227
|
+
}
|
|
1228
|
+
function typenameOf(node) {
|
|
1229
|
+
const typename = node.__typename;
|
|
1230
|
+
return typeof typename === "string" ? typename : void 0;
|
|
1231
|
+
}
|
|
1232
|
+
function selectionsAt(root, doc, path) {
|
|
1233
|
+
let node = root;
|
|
1234
|
+
let selections = doc.operation;
|
|
1235
|
+
for (const step of path) {
|
|
1236
|
+
if (Array.isArray(node)) {
|
|
1237
|
+
node = node[step];
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
if (node === null || typeof node !== "object") return null;
|
|
1241
|
+
const field = collectFields(selections, typenameOf(node), doc).get(
|
|
1242
|
+
String(step)
|
|
1243
|
+
);
|
|
1244
|
+
if (!field) return null;
|
|
1245
|
+
selections = field.selections;
|
|
1246
|
+
node = node[String(step)];
|
|
1247
|
+
}
|
|
1248
|
+
return selections;
|
|
1249
|
+
}
|
|
1250
|
+
function resolveTargets(root, doc, ownerPath, segments) {
|
|
1251
|
+
let owner = root;
|
|
1252
|
+
for (const step of ownerPath) {
|
|
1253
|
+
owner = owner[step];
|
|
1254
|
+
}
|
|
1255
|
+
const selections = selectionsAt(root, doc, ownerPath);
|
|
1256
|
+
if (!selections) return { root, targets: [], plain: /* @__PURE__ */ new Set() };
|
|
1257
|
+
let current = root;
|
|
1258
|
+
const out = [];
|
|
1259
|
+
const plain = /* @__PURE__ */ new Set();
|
|
1260
|
+
const walk = (node, sel, i, path) => {
|
|
1261
|
+
if (i === segments.length) {
|
|
1262
|
+
out.push(path);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
const segment = segments[i];
|
|
1266
|
+
if (Array.isArray(node)) {
|
|
1267
|
+
if (INDEX_SEGMENT2.test(segment)) {
|
|
1268
|
+
const position = Number(segment);
|
|
1269
|
+
if (position < node.length) {
|
|
1270
|
+
walk(node[position], sel, i + 1, [...path, position]);
|
|
1271
|
+
}
|
|
1272
|
+
} else if (segment === "items") {
|
|
1273
|
+
walk(node, sel, i + 1, path);
|
|
1274
|
+
}
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (node === null || typeof node !== "object") return;
|
|
1278
|
+
const record = node;
|
|
1279
|
+
const wanted = graphqlFieldName(segment);
|
|
1280
|
+
const textBody = i === segments.length - 1 && segment === "body" && isTextEntry(record) ? TEXT_BODY_FIELDS : null;
|
|
1281
|
+
let matched = false;
|
|
1282
|
+
for (const field of collectFields(sel, typenameOf(node), doc).values()) {
|
|
1283
|
+
const alias = textBody?.find((f) => f.name === field.name);
|
|
1284
|
+
if (field.name !== segment && field.name !== wanted && !alias) continue;
|
|
1285
|
+
if (!Object.prototype.hasOwnProperty.call(record, field.key)) continue;
|
|
1286
|
+
matched = true;
|
|
1287
|
+
const fieldPath = [...path, field.key];
|
|
1288
|
+
if (alias?.plain) plain.add(fieldPath);
|
|
1289
|
+
let value = record[field.key];
|
|
1290
|
+
const next = segments[i + 1];
|
|
1291
|
+
if (value === null && field.selections.length && next !== void 0 && next !== "items" && !INDEX_SEGMENT2.test(next)) {
|
|
1292
|
+
const typename = resolveTypename(
|
|
1293
|
+
doc,
|
|
1294
|
+
field.selections,
|
|
1295
|
+
void 0,
|
|
1296
|
+
void 0
|
|
1297
|
+
);
|
|
1298
|
+
if (typename !== void 0) {
|
|
1299
|
+
value = fillObject(doc, field.selections, typename, {}, void 0, {
|
|
1300
|
+
links: null,
|
|
1301
|
+
lists: listKeysIn(root)
|
|
1302
|
+
});
|
|
1303
|
+
current = writeAtPath(current, fieldPath, value);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
walk(value, field.selections, i + 1, fieldPath);
|
|
1307
|
+
}
|
|
1308
|
+
if (!matched && segment === "items") walk(node, sel, i + 1, path);
|
|
1309
|
+
};
|
|
1310
|
+
walk(owner, selections, 0, ownerPath);
|
|
1311
|
+
return { root: current, targets: out, plain };
|
|
1312
|
+
}
|
|
1313
|
+
var NON_ALPHANUMERIC = /[^a-z0-9]/gi;
|
|
1314
|
+
var typeKey = (name) => name.replace(NON_ALPHANUMERIC, "").toLowerCase();
|
|
1315
|
+
function resolveTypename(doc, selections, typename, typeName) {
|
|
1316
|
+
const known = typeConditions(selections, doc);
|
|
1317
|
+
const explicit = typeof typename === "string" ? typename : void 0;
|
|
1318
|
+
if (!known.length) return explicit ?? null;
|
|
1319
|
+
if (explicit && known.includes(explicit)) return explicit;
|
|
1320
|
+
for (const candidate of [explicit, typeName]) {
|
|
1321
|
+
if (typeof candidate !== "string" || !candidate) continue;
|
|
1322
|
+
const match = known.find((name) => typeKey(name) === typeKey(candidate));
|
|
1323
|
+
if (match) return match;
|
|
1324
|
+
}
|
|
1325
|
+
const named = explicit !== void 0 || typeof typeName === "string";
|
|
1326
|
+
return known.length === 1 && !named ? known[0] : void 0;
|
|
1327
|
+
}
|
|
1328
|
+
function buildEntry(doc, selections, entry, ctx) {
|
|
1329
|
+
const typename = resolveTypename(
|
|
1330
|
+
doc,
|
|
1331
|
+
selections,
|
|
1332
|
+
entry.__typename,
|
|
1333
|
+
entry.typeName
|
|
1334
|
+
);
|
|
1335
|
+
if (typename === void 0) return void 0;
|
|
1336
|
+
return fillObject(
|
|
1337
|
+
doc,
|
|
1338
|
+
selections,
|
|
1339
|
+
typename,
|
|
1340
|
+
entry.fields ?? {},
|
|
1341
|
+
entry._id,
|
|
1342
|
+
ctx
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
function fillObject(doc, selections, typename, values, id, ctx) {
|
|
1346
|
+
const out = {};
|
|
1347
|
+
const { links } = ctx;
|
|
1348
|
+
const lists = ctx.lists(typename);
|
|
1349
|
+
const edit = isPlainRecord(values.__edit) ? values.__edit : null;
|
|
1350
|
+
const apiIdOf = /* @__PURE__ */ new Map();
|
|
1351
|
+
for (const key of Object.keys(values)) {
|
|
1352
|
+
apiIdOf.set(key, key);
|
|
1353
|
+
const name = graphqlFieldName(key);
|
|
1354
|
+
if (!apiIdOf.has(name)) apiIdOf.set(name, key);
|
|
1355
|
+
}
|
|
1356
|
+
if (edit && isPlainRecord(edit.apiIds)) {
|
|
1357
|
+
for (const [name, apiId] of Object.entries(edit.apiIds)) {
|
|
1358
|
+
if (typeof apiId === "string") apiIdOf.set(name, apiId);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
const valueOf = (name) => {
|
|
1362
|
+
if (Object.prototype.hasOwnProperty.call(values, name)) return values[name];
|
|
1363
|
+
const key = apiIdOf.get(name);
|
|
1364
|
+
return key !== void 0 && key in values ? values[key] : void 0;
|
|
1365
|
+
};
|
|
1366
|
+
const editField = (name) => {
|
|
1367
|
+
if (!edit) return null;
|
|
1368
|
+
if (typeof edit.field === "string")
|
|
1369
|
+
return name === "body" ? edit.field : null;
|
|
1370
|
+
if (typeof edit.path !== "string") return null;
|
|
1371
|
+
const apiId = apiIdOf.get(name) ?? name;
|
|
1372
|
+
const text = Array.isArray(edit.text) ? edit.text : [];
|
|
1373
|
+
if (!text.includes(apiId) && !text.includes(name)) return null;
|
|
1374
|
+
return `${edit.path}.items.${apiId}`;
|
|
1375
|
+
};
|
|
1376
|
+
const fields = collectFields(selections, typename ?? void 0, doc, {
|
|
1377
|
+
strict: true
|
|
1378
|
+
});
|
|
1379
|
+
for (const field of fields.values()) {
|
|
1380
|
+
if (field.name === "__typename") {
|
|
1381
|
+
out[field.key] = typename;
|
|
1382
|
+
} else if (field.name === "_id") {
|
|
1383
|
+
out[field.key] = typeof id === "string" ? id : values._id ?? null;
|
|
1384
|
+
} else if (field.selections.length) {
|
|
1385
|
+
const value = valueOf(field.name);
|
|
1386
|
+
out[field.key] = value == null && lists.has(field.key) ? [] : fillNested(doc, field.selections, value, ctx);
|
|
1387
|
+
} else {
|
|
1388
|
+
const value = valueOf(field.name);
|
|
1389
|
+
if (Array.isArray(value) && value.every(isScalarItem)) {
|
|
1390
|
+
out[field.key] = value;
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
if (value == null && lists.has(field.key)) {
|
|
1394
|
+
out[field.key] = [];
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
const scalar = value === void 0 || value !== null && typeof value === "object" ? null : value;
|
|
1398
|
+
const target = typeof scalar === "string" ? editField(field.name) : null;
|
|
1399
|
+
out[field.key] = target && links ? links.encode(scalar, target) : scalar;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
return out;
|
|
1403
|
+
}
|
|
1404
|
+
function fillNested(doc, selections, value, ctx) {
|
|
1405
|
+
const one = (item) => {
|
|
1406
|
+
if (!isPlainRecord(item)) return null;
|
|
1407
|
+
const typename = resolveTypename(
|
|
1408
|
+
doc,
|
|
1409
|
+
selections,
|
|
1410
|
+
item.__typename,
|
|
1411
|
+
item.typeName
|
|
1412
|
+
);
|
|
1413
|
+
if (typename === void 0) return null;
|
|
1414
|
+
return fillObject(doc, selections, typename, item, item._id, ctx);
|
|
1415
|
+
};
|
|
1416
|
+
if (Array.isArray(value)) return value.map(one).filter((v) => v !== null);
|
|
1417
|
+
return value === void 0 ? null : one(value);
|
|
1418
|
+
}
|
|
1419
|
+
function isScalarItem(value) {
|
|
1420
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
1421
|
+
}
|
|
1422
|
+
var NO_KEYS = /* @__PURE__ */ new Set();
|
|
1423
|
+
var listKeysCache = /* @__PURE__ */ new WeakMap();
|
|
1424
|
+
function listKeysIn(result) {
|
|
1425
|
+
return (typename) => {
|
|
1426
|
+
if (!typename || result === null || typeof result !== "object") {
|
|
1427
|
+
return NO_KEYS;
|
|
1428
|
+
}
|
|
1429
|
+
let byType = listKeysCache.get(result);
|
|
1430
|
+
if (!byType) {
|
|
1431
|
+
byType = /* @__PURE__ */ new Map();
|
|
1432
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
1433
|
+
const walk = (node) => {
|
|
1434
|
+
if (node === null || typeof node !== "object" || seen.has(node)) return;
|
|
1435
|
+
seen.add(node);
|
|
1436
|
+
if (Array.isArray(node)) {
|
|
1437
|
+
node.forEach(walk);
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
const record = node;
|
|
1441
|
+
const type = typeof record.__typename === "string" ? record.__typename : null;
|
|
1442
|
+
for (const [key, value] of Object.entries(record)) {
|
|
1443
|
+
if (type && Array.isArray(value)) {
|
|
1444
|
+
let keys = byType.get(type);
|
|
1445
|
+
if (!keys) byType.set(type, keys = /* @__PURE__ */ new Set());
|
|
1446
|
+
keys.add(key);
|
|
1447
|
+
}
|
|
1448
|
+
walk(value);
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
walk(result);
|
|
1452
|
+
listKeysCache.set(result, byType);
|
|
1453
|
+
}
|
|
1454
|
+
return byType.get(typename) ?? NO_KEYS;
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
var EDIT_PATH = /\/content\/edit\/[^/?#]+/;
|
|
1458
|
+
function editLinks(result, entry) {
|
|
1459
|
+
const edit = entry.fields?.__edit;
|
|
1460
|
+
if (!isPlainRecord(edit) || typeof edit.item !== "string") return null;
|
|
1461
|
+
return editLinksFor(result, edit.item);
|
|
1462
|
+
}
|
|
1463
|
+
var ITEM_ID = /^[A-Za-z0-9_-]+$/;
|
|
1464
|
+
function editLinksFor(result, item) {
|
|
1465
|
+
if (!ITEM_ID.test(item)) return null;
|
|
1466
|
+
const template = findEditLink(result);
|
|
1467
|
+
if (!template) return null;
|
|
1468
|
+
return {
|
|
1469
|
+
encode(text, field) {
|
|
1470
|
+
if (vercelStegaSplit2(text).encoded) return text;
|
|
1471
|
+
let href;
|
|
1472
|
+
try {
|
|
1473
|
+
href = new URL(template.href);
|
|
1474
|
+
} catch {
|
|
1475
|
+
return text;
|
|
1476
|
+
}
|
|
1477
|
+
href.pathname = href.pathname.replace(
|
|
1478
|
+
EDIT_PATH,
|
|
1479
|
+
() => `/content/edit/${item}`
|
|
1480
|
+
);
|
|
1481
|
+
href.searchParams.set("field", field);
|
|
1482
|
+
const link = href.toString().replace(/%2E/gi, ".").replace(/%2F/gi, "/");
|
|
1483
|
+
return vercelStegaCombine(text, { origin: template.origin, href: link });
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
var linkCache = /* @__PURE__ */ new WeakMap();
|
|
1488
|
+
function findEditLink(root) {
|
|
1489
|
+
if (root === null || typeof root !== "object") return null;
|
|
1490
|
+
if (linkCache.has(root)) return linkCache.get(root) ?? null;
|
|
1491
|
+
let found = null;
|
|
1492
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
1493
|
+
const walk = (node) => {
|
|
1494
|
+
if (found) return;
|
|
1495
|
+
if (typeof node === "string") {
|
|
1496
|
+
if (!vercelStegaSplit2(node).encoded) return;
|
|
1497
|
+
try {
|
|
1498
|
+
const decoded = vercelStegaDecode(node);
|
|
1499
|
+
if (typeof decoded?.origin === "string" && typeof decoded.href === "string" && EDIT_PATH.test(decoded.href)) {
|
|
1500
|
+
found = { origin: decoded.origin, href: decoded.href };
|
|
1501
|
+
}
|
|
1502
|
+
} catch {
|
|
1503
|
+
}
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
if (node === null || typeof node !== "object" || seen.has(node)) return;
|
|
1507
|
+
seen.add(node);
|
|
1508
|
+
for (const value of Object.values(node)) walk(value);
|
|
1509
|
+
};
|
|
1510
|
+
walk(root);
|
|
1511
|
+
linkCache.set(root, found);
|
|
1512
|
+
return found;
|
|
1513
|
+
}
|
|
1514
|
+
export {
|
|
1515
|
+
applyPatchLog,
|
|
1516
|
+
createLiveBridge,
|
|
1517
|
+
createLiveStore,
|
|
1518
|
+
normalizeQuery,
|
|
1519
|
+
parsePatchLog
|
|
1520
|
+
};
|