@preprio/toolkit 0.4.3 → 0.5.0-rc.10
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 +416 -23
- package/dist/astro/index.cjs +1290 -1
- package/dist/astro/index.d.cts +47 -1
- package/dist/astro/index.d.ts +47 -1
- package/dist/astro/index.js +1297 -1
- package/dist/chunk-F46GJS6Y.js +26 -0
- package/dist/{chunk-H7VA7NGY.js → chunk-IDEC77LZ.js} +1480 -621
- package/dist/chunk-IPMB32AZ.js +246 -0
- package/dist/chunk-KD3GK6TO.js +1221 -0
- package/dist/chunk-S3VT4UWM.js +75 -0
- package/dist/chunk-TBBU3LDD.js +184 -0
- package/dist/chunk-URXSDJOU.js +356 -0
- package/dist/codegen/index.cjs +82 -0
- package/dist/codegen/index.d.cts +70 -0
- package/dist/codegen/index.d.ts +70 -0
- package/dist/codegen/index.js +64 -0
- package/dist/index.cjs +3412 -409
- package/dist/index.d.cts +287 -5
- package/dist/index.d.ts +287 -5
- package/dist/index.js +307 -144
- package/dist/live-preview-DLXGXRE5.js +18 -0
- package/dist/nextjs/components.cjs +1662 -388
- 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 +1181 -6
- package/dist/nextjs/index.d.cts +83 -3
- package/dist/nextjs/index.d.ts +83 -3
- package/dist/nextjs/index.js +1179 -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 +1899 -0
- package/dist/nextjs/live.d.cts +54 -0
- package/dist/nextjs/live.d.ts +54 -0
- package/dist/nextjs/live.js +268 -0
- package/dist/nuxt/chunk-BWPGO25E.js +340 -0
- package/dist/nuxt/index.cjs +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/nuxt/live-preview-2RCU35W5.js +1189 -0
- package/dist/nuxt/live.cjs +1816 -0
- package/dist/nuxt/live.d.cts +43 -0
- package/dist/nuxt/live.d.ts +43 -0
- package/dist/nuxt/live.js +198 -0
- package/dist/{pixel-e1fUWl2Y.d.cts → pixel-CX422dCO.d.cts} +18 -3
- package/dist/{pixel-e1fUWl2Y.d.ts → pixel-CX422dCO.d.ts} +18 -3
- package/dist/react/components.cjs +1662 -388
- package/dist/react/components.d.cts +17 -2
- package/dist/react/components.d.ts +17 -2
- package/dist/react/components.js +1662 -388
- package/dist/sveltekit/chunk-BWPGO25E.js +340 -0
- package/dist/sveltekit/index.cjs +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/dist/sveltekit/live-preview-2RCU35W5.js +1189 -0
- package/dist/sveltekit/live.cjs +1830 -0
- package/dist/sveltekit/live.d.cts +59 -0
- package/dist/sveltekit/live.d.ts +59 -0
- package/dist/sveltekit/live.js +204 -0
- package/package.json +37 -11
- package/src/astro/PreprToolbar.astro +7 -1
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(src_exports, {
|
|
|
23
23
|
PreprError: () => PreprError,
|
|
24
24
|
PreprToolbarElement: () => PreprToolbarElement,
|
|
25
25
|
VERSION: () => VERSION,
|
|
26
|
+
annotateEditLinks: () => annotateEditLinks,
|
|
26
27
|
createPreprPreview: () => createPreprPreview,
|
|
27
28
|
createToolbarStore: () => createToolbarStore,
|
|
28
29
|
definePreprToolbar: () => definePreprToolbar,
|
|
@@ -34,17 +35,23 @@ __export(src_exports, {
|
|
|
34
35
|
getPreprUUIDFromHeaders: () => getPreprUUIDFromHeaders,
|
|
35
36
|
getToolbarPropsFromHeaders: () => getToolbarPropsFromHeaders,
|
|
36
37
|
loadTrackingPixel: () => loadTrackingPixel,
|
|
38
|
+
normalizeQuery: () => normalizeQuery,
|
|
39
|
+
parsePatchLog: () => parsePatchLog,
|
|
40
|
+
previewAttrs: () => previewAttrs,
|
|
37
41
|
processPreprRequest: () => processPreprRequest,
|
|
42
|
+
readLiveLogFromRequest: () => readLiveLogFromRequest,
|
|
38
43
|
resolveFeatures: () => resolveFeatures,
|
|
39
44
|
setTrackingParam: () => setTrackingParam,
|
|
45
|
+
startHtmlLivePreview: () => startHtmlLivePreview,
|
|
40
46
|
stegaClean: () => stegaClean,
|
|
47
|
+
stegaTarget: () => stegaTarget,
|
|
41
48
|
trackEvent: () => trackEvent,
|
|
42
49
|
validatePreprToken: () => validatePreprToken
|
|
43
50
|
});
|
|
44
51
|
module.exports = __toCommonJS(src_exports);
|
|
45
52
|
|
|
46
53
|
// src/version.ts
|
|
47
|
-
var VERSION = "0.
|
|
54
|
+
var VERSION = "0.5.0-rc.10";
|
|
48
55
|
|
|
49
56
|
// src/core/features.ts
|
|
50
57
|
function isEnabled(config) {
|
|
@@ -75,6 +82,7 @@ var PARAM_SEGMENT = "prepr_preview_segment";
|
|
|
75
82
|
var PARAM_VARIANT = "prepr_preview_ab";
|
|
76
83
|
var PARAM_HIDE_BAR = "prepr_hide_bar";
|
|
77
84
|
var ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
|
|
85
|
+
var HEADER_LIVE_RENDER = "x-prepr-live";
|
|
78
86
|
|
|
79
87
|
// src/core/cookies.ts
|
|
80
88
|
function getCookie(name) {
|
|
@@ -116,6 +124,40 @@ function crossSiteCookieOptions() {
|
|
|
116
124
|
return { sameSite: "None", secure: true };
|
|
117
125
|
}
|
|
118
126
|
|
|
127
|
+
// src/core/locales/de.json
|
|
128
|
+
var de_default = {
|
|
129
|
+
common: {
|
|
130
|
+
viewingAs: "Ansicht als:",
|
|
131
|
+
user: "Benutzer",
|
|
132
|
+
off: "Aus",
|
|
133
|
+
on: "An",
|
|
134
|
+
reset: "Zur\xFCcksetzen",
|
|
135
|
+
toolbar: "Symbolleiste"
|
|
136
|
+
},
|
|
137
|
+
adaptiveContent: {
|
|
138
|
+
adaptiveContent: "Adaptive Inhalte",
|
|
139
|
+
enablePreview: "Vorschaumodus",
|
|
140
|
+
segment: "Segment",
|
|
141
|
+
ABVariant: "A/B-Variante",
|
|
142
|
+
chooseSegment: "Segment w\xE4hlen",
|
|
143
|
+
searchSegments: "Segmente suchen",
|
|
144
|
+
none: "Keine Segmente",
|
|
145
|
+
allOtherUsers: "Alle anderen Benutzer",
|
|
146
|
+
offDescription: "Die Website als echter Benutzer ansehen (Cookies verwenden)",
|
|
147
|
+
onDescription: "Mit der Symbolleiste Segmente und A/B-Varianten simulieren"
|
|
148
|
+
},
|
|
149
|
+
editingTools: {
|
|
150
|
+
editingTools: "Bearbeitungswerkzeuge",
|
|
151
|
+
editMode: "Bearbeitungsmodus",
|
|
152
|
+
ariaCloseEditMode: "Bearbeitungsmodus schlie\xDFen"
|
|
153
|
+
},
|
|
154
|
+
visualEditor: {
|
|
155
|
+
edit: "Bearbeiten",
|
|
156
|
+
otherItem: "anderes Element",
|
|
157
|
+
linkedItem: "verkn\xFCpftes Element"
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
119
161
|
// src/core/locales/en.json
|
|
120
162
|
var en_default = {
|
|
121
163
|
common: {
|
|
@@ -142,6 +184,45 @@ var en_default = {
|
|
|
142
184
|
editingTools: "Editing tools",
|
|
143
185
|
editMode: "Edit mode",
|
|
144
186
|
ariaCloseEditMode: "Close edit mode"
|
|
187
|
+
},
|
|
188
|
+
visualEditor: {
|
|
189
|
+
edit: "Edit",
|
|
190
|
+
otherItem: "other item",
|
|
191
|
+
linkedItem: "linked item"
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// src/core/locales/fr.json
|
|
196
|
+
var fr_default = {
|
|
197
|
+
common: {
|
|
198
|
+
viewingAs: "Affichage en tant que :",
|
|
199
|
+
user: "Utilisateur",
|
|
200
|
+
off: "D\xE9sactiv\xE9",
|
|
201
|
+
on: "Activ\xE9",
|
|
202
|
+
reset: "R\xE9initialiser",
|
|
203
|
+
toolbar: "Barre d'outils"
|
|
204
|
+
},
|
|
205
|
+
adaptiveContent: {
|
|
206
|
+
adaptiveContent: "Contenu adaptatif",
|
|
207
|
+
enablePreview: "Mode aper\xE7u",
|
|
208
|
+
segment: "Segment",
|
|
209
|
+
ABVariant: "Variante A/B",
|
|
210
|
+
chooseSegment: "Choisir un segment",
|
|
211
|
+
searchSegments: "Rechercher des segments",
|
|
212
|
+
none: "Aucun segment",
|
|
213
|
+
allOtherUsers: "Tous les autres utilisateurs",
|
|
214
|
+
offDescription: "Voir le site comme un vrai utilisateur (utiliser les cookies)",
|
|
215
|
+
onDescription: "Utilisez la barre d'outils pour simuler des segments et des variantes A/B"
|
|
216
|
+
},
|
|
217
|
+
editingTools: {
|
|
218
|
+
editingTools: "Outils d'\xE9dition",
|
|
219
|
+
editMode: "Mode \xE9dition",
|
|
220
|
+
ariaCloseEditMode: "Fermer le mode \xE9dition"
|
|
221
|
+
},
|
|
222
|
+
visualEditor: {
|
|
223
|
+
edit: "Modifier",
|
|
224
|
+
otherItem: "autre \xE9l\xE9ment",
|
|
225
|
+
linkedItem: "\xE9l\xE9ment li\xE9"
|
|
145
226
|
}
|
|
146
227
|
};
|
|
147
228
|
|
|
@@ -171,13 +252,20 @@ var nl_default = {
|
|
|
171
252
|
editingTools: "Editing Tools",
|
|
172
253
|
editMode: "Edit mode",
|
|
173
254
|
ariaCloseEditMode: "Edit mode sluiten"
|
|
255
|
+
},
|
|
256
|
+
visualEditor: {
|
|
257
|
+
edit: "Bewerken",
|
|
258
|
+
otherItem: "ander item",
|
|
259
|
+
linkedItem: "gekoppeld item"
|
|
174
260
|
}
|
|
175
261
|
};
|
|
176
262
|
|
|
177
263
|
// src/core/i18n.ts
|
|
178
264
|
var dictionaries = {
|
|
179
265
|
en: en_default,
|
|
180
|
-
nl: nl_default
|
|
266
|
+
nl: nl_default,
|
|
267
|
+
fr: fr_default,
|
|
268
|
+
de: de_default
|
|
181
269
|
};
|
|
182
270
|
var LOCALES = Object.keys(dictionaries);
|
|
183
271
|
function isLocale(value) {
|
|
@@ -211,6 +299,9 @@ var trustedParentOrigin = null;
|
|
|
211
299
|
function setTrustedParentOrigin(origin) {
|
|
212
300
|
trustedParentOrigin = origin;
|
|
213
301
|
}
|
|
302
|
+
function getTrustedParentOrigin() {
|
|
303
|
+
return trustedParentOrigin;
|
|
304
|
+
}
|
|
214
305
|
function sendPreprEvent(event, data, options) {
|
|
215
306
|
if (typeof window !== "undefined") {
|
|
216
307
|
const message = {
|
|
@@ -270,51 +361,53 @@ function createScopedLogger(scopeName) {
|
|
|
270
361
|
prefix: `[Prepr][${scopeName}]`
|
|
271
362
|
});
|
|
272
363
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
clearTimeout(timeoutId);
|
|
285
|
-
}
|
|
286
|
-
timeoutId = setTimeout(() => {
|
|
287
|
-
func(...args);
|
|
288
|
-
lastExecTime = Date.now();
|
|
289
|
-
timeoutId = null;
|
|
290
|
-
}, delay - timeSinceLastExec);
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
throttledFunc.cancel = () => {
|
|
294
|
-
if (timeoutId) {
|
|
295
|
-
clearTimeout(timeoutId);
|
|
296
|
-
timeoutId = null;
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
return throttledFunc;
|
|
364
|
+
|
|
365
|
+
// src/core/iframe-bridge.ts
|
|
366
|
+
var debug = createScopedLogger("iframe-bridge");
|
|
367
|
+
var MAX_REFERENCES = 500;
|
|
368
|
+
var MAX_HREF_LENGTH = 8192;
|
|
369
|
+
var MAX_ASSETS = 500;
|
|
370
|
+
var MAX_ASSET_KEY_LENGTH = 2048;
|
|
371
|
+
var MAX_ASSET_FIELD_LENGTH = 512;
|
|
372
|
+
function parseIds(value) {
|
|
373
|
+
if (!Array.isArray(value)) return void 0;
|
|
374
|
+
return value.slice(0, MAX_REFERENCES).filter((id) => typeof id === "string" && id.length > 0);
|
|
300
375
|
}
|
|
301
|
-
function
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
376
|
+
function parseReferences(value) {
|
|
377
|
+
if (!Array.isArray(value)) return void 0;
|
|
378
|
+
const list = [];
|
|
379
|
+
for (const entry of value.slice(0, MAX_REFERENCES)) {
|
|
380
|
+
const id = entry?.id;
|
|
381
|
+
const field = entry?.field;
|
|
382
|
+
if (typeof id === "string" && id && typeof field === "string" && field) {
|
|
383
|
+
list.push({ id, field });
|
|
309
384
|
}
|
|
310
|
-
|
|
311
|
-
|
|
385
|
+
}
|
|
386
|
+
return list;
|
|
387
|
+
}
|
|
388
|
+
function boundedString(value, max) {
|
|
389
|
+
return typeof value === "string" && value && value.length <= max ? value : void 0;
|
|
390
|
+
}
|
|
391
|
+
function parseAssets(value) {
|
|
392
|
+
if (!Array.isArray(value)) return void 0;
|
|
393
|
+
const list = [];
|
|
394
|
+
for (const entry of value.slice(0, MAX_ASSETS)) {
|
|
395
|
+
if (!entry || typeof entry !== "object") continue;
|
|
396
|
+
const raw = entry;
|
|
397
|
+
const key = boundedString(raw.key, MAX_ASSET_KEY_LENGTH);
|
|
398
|
+
const field = boundedString(raw.field, MAX_ASSET_FIELD_LENGTH);
|
|
399
|
+
if (!key || !field) continue;
|
|
400
|
+
const entryId = boundedString(raw.entryId, MAX_ASSET_FIELD_LENGTH);
|
|
401
|
+
list.push(entryId ? { key, field, entryId } : { key, field });
|
|
402
|
+
}
|
|
403
|
+
return list;
|
|
312
404
|
}
|
|
313
|
-
|
|
314
|
-
// src/core/iframe-bridge.ts
|
|
315
405
|
var EDITOR_BASE_DOMAIN = "prepr.io";
|
|
316
406
|
function isAllowedEditorOrigin(origin, allowed) {
|
|
317
|
-
if (allowed)
|
|
407
|
+
if (allowed) {
|
|
408
|
+
if (allowed.includes("*")) return origin !== "null";
|
|
409
|
+
return allowed.includes(origin);
|
|
410
|
+
}
|
|
318
411
|
let url;
|
|
319
412
|
try {
|
|
320
413
|
url = new URL(origin);
|
|
@@ -329,6 +422,11 @@ function isAllowedEditorOrigin(origin, allowed) {
|
|
|
329
422
|
}
|
|
330
423
|
function createIframeBridge(store, options = {}) {
|
|
331
424
|
const allowedOrigins = options.allowedEditorOrigins;
|
|
425
|
+
const onScrollToField = options.onScrollToField;
|
|
426
|
+
const onHandshake = options.onHandshake;
|
|
427
|
+
const onReferences = options.onReferences;
|
|
428
|
+
const onAssets = options.onAssets;
|
|
429
|
+
const onAllowNavigation = options.onAllowNavigation;
|
|
332
430
|
let parentOrigin = null;
|
|
333
431
|
const onKeyDown = (event) => {
|
|
334
432
|
const key = event.key.toLowerCase();
|
|
@@ -338,7 +436,10 @@ function createIframeBridge(store, options = {}) {
|
|
|
338
436
|
const onMessage = (evt) => {
|
|
339
437
|
const data = evt?.data;
|
|
340
438
|
if (data?.event === "prepr:initVE" && !parentOrigin) {
|
|
341
|
-
if (!isAllowedEditorOrigin(evt.origin, allowedOrigins))
|
|
439
|
+
if (!isAllowedEditorOrigin(evt.origin, allowedOrigins)) {
|
|
440
|
+
debug.warn("ignored editor handshake from origin", evt.origin);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
342
443
|
parentOrigin = evt.origin;
|
|
343
444
|
setTrustedParentOrigin(parentOrigin);
|
|
344
445
|
if (data.scrollPosition != null) {
|
|
@@ -346,12 +447,76 @@ function createIframeBridge(store, options = {}) {
|
|
|
346
447
|
setTimeout(() => window.scrollTo(0, top), 1);
|
|
347
448
|
}
|
|
348
449
|
store?.set({ previewMode: true, editMode: data.editMode ?? true });
|
|
450
|
+
const itemId = typeof data.itemId === "string" && data.itemId ? data.itemId : void 0;
|
|
451
|
+
const referencedIds = parseReferences(data.referencedIds);
|
|
452
|
+
const componentIds = parseIds(data.componentIds);
|
|
453
|
+
const uiLocale = isLocale(data.uiLocale) ? data.uiLocale : void 0;
|
|
454
|
+
const navigationGuard = data.navigationGuard === true;
|
|
455
|
+
const assets = parseAssets(data.assets);
|
|
456
|
+
try {
|
|
457
|
+
onHandshake?.({
|
|
458
|
+
...itemId && { itemId },
|
|
459
|
+
...referencedIds && { referencedIds },
|
|
460
|
+
...componentIds && { componentIds },
|
|
461
|
+
...uiLocale && { uiLocale },
|
|
462
|
+
...navigationGuard && { navigationGuard },
|
|
463
|
+
...assets && { assets }
|
|
464
|
+
});
|
|
465
|
+
} catch (error) {
|
|
466
|
+
debug.warn("handshake handler failed", error);
|
|
467
|
+
}
|
|
349
468
|
}
|
|
350
469
|
if (!parentOrigin || evt.origin !== parentOrigin) return;
|
|
470
|
+
if (data?.event === "prepr:references") {
|
|
471
|
+
const assets = parseAssets(data.assets);
|
|
472
|
+
if (assets) {
|
|
473
|
+
try {
|
|
474
|
+
onAssets?.(assets);
|
|
475
|
+
} catch (error) {
|
|
476
|
+
debug.warn("assets handler failed", error);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const referencedIds = parseReferences(data.referencedIds);
|
|
480
|
+
if (!referencedIds) return;
|
|
481
|
+
try {
|
|
482
|
+
onReferences?.(referencedIds, parseIds(data.componentIds));
|
|
483
|
+
} catch (error) {
|
|
484
|
+
debug.warn("references handler failed", error);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
351
487
|
if (data?.event === "prepr:getScrollPosition") {
|
|
352
488
|
const currentScrollY = window.scrollY || document.documentElement.scrollTop;
|
|
353
489
|
sendPreprEvent("getScrollPosition", { value: currentScrollY });
|
|
354
490
|
}
|
|
491
|
+
if (data?.event === "prepr:scrollToField") {
|
|
492
|
+
if (typeof data.field !== "string" || !data.field) return;
|
|
493
|
+
if (data.id !== void 0 && typeof data.id !== "string") return;
|
|
494
|
+
const block = typeof data.block === "number" && Number.isInteger(data.block) ? data.block : void 0;
|
|
495
|
+
const entryId = typeof data.entryId === "string" && data.entryId ? data.entryId : void 0;
|
|
496
|
+
const fieldById = typeof data.fieldById === "string" && data.fieldById ? data.fieldById : void 0;
|
|
497
|
+
try {
|
|
498
|
+
onScrollToField?.({
|
|
499
|
+
field: data.field,
|
|
500
|
+
id: data.id,
|
|
501
|
+
block,
|
|
502
|
+
entryId,
|
|
503
|
+
fieldById
|
|
504
|
+
});
|
|
505
|
+
} catch (error) {
|
|
506
|
+
debug.warn("scrollToField handler failed", error);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (data?.event === "prepr:allowNavigation") {
|
|
510
|
+
const href = data.href;
|
|
511
|
+
if (typeof href !== "string" || !href || href.length > MAX_HREF_LENGTH) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
onAllowNavigation?.(href);
|
|
516
|
+
} catch (error) {
|
|
517
|
+
debug.warn("allowNavigation handler failed", error);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
355
520
|
};
|
|
356
521
|
return {
|
|
357
522
|
start() {
|
|
@@ -376,7 +541,7 @@ var import_stega2 = require("@vercel/stega");
|
|
|
376
541
|
|
|
377
542
|
// src/core/stega/clean.ts
|
|
378
543
|
var import_stega = require("@vercel/stega");
|
|
379
|
-
var
|
|
544
|
+
var debug2 = createScopedLogger("stega:clean");
|
|
380
545
|
function stegaClean(text) {
|
|
381
546
|
if (!text) return text;
|
|
382
547
|
return (0, import_stega.vercelStegaClean)(text);
|
|
@@ -389,7 +554,7 @@ function decodeStega(str) {
|
|
|
389
554
|
return decoded;
|
|
390
555
|
}
|
|
391
556
|
} catch (error) {
|
|
392
|
-
|
|
557
|
+
debug2.log("error decoding stega data", error);
|
|
393
558
|
const match = str.match(/{"origin.*?}/);
|
|
394
559
|
if (match) {
|
|
395
560
|
try {
|
|
@@ -398,7 +563,7 @@ function decodeStega(str) {
|
|
|
398
563
|
return decodedMatch;
|
|
399
564
|
}
|
|
400
565
|
} catch (innerError) {
|
|
401
|
-
|
|
566
|
+
debug2.log("error decoding stega regex match", innerError);
|
|
402
567
|
}
|
|
403
568
|
}
|
|
404
569
|
}
|
|
@@ -407,6 +572,7 @@ function decodeStega(str) {
|
|
|
407
572
|
|
|
408
573
|
// src/core/stega/dom.ts
|
|
409
574
|
var IGNORED_ANCESTORS = "script, style, noscript";
|
|
575
|
+
var AUTO_ENTRY_ATTR = "data-prepr-entry-auto";
|
|
410
576
|
function isIgnoredTextNode(node) {
|
|
411
577
|
if (node.parentElement?.closest(IGNORED_ANCESTORS)) return true;
|
|
412
578
|
return !node.textContent?.trim();
|
|
@@ -434,42 +600,90 @@ function isRenderedElement(element) {
|
|
|
434
600
|
if (!style) return true;
|
|
435
601
|
return style.visibility !== "hidden" && style.display !== "none";
|
|
436
602
|
}
|
|
603
|
+
function parseEditHref(href) {
|
|
604
|
+
try {
|
|
605
|
+
const url = new URL(href, window.location.origin);
|
|
606
|
+
return {
|
|
607
|
+
id: url.pathname.split("/").pop() || void 0,
|
|
608
|
+
field: url.searchParams.get("field") || void 0,
|
|
609
|
+
locale: url.searchParams.get("locale") || void 0,
|
|
610
|
+
// Added by the toolkit's `annotateEditLinks`, not by Prepr.
|
|
611
|
+
entry: url.searchParams.get("entry") || void 0
|
|
612
|
+
};
|
|
613
|
+
} catch {
|
|
614
|
+
return {};
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
function setOrRemove(element, name, value) {
|
|
618
|
+
if (value) {
|
|
619
|
+
element.setAttribute(name, value);
|
|
620
|
+
} else {
|
|
621
|
+
element.removeAttribute(name);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
437
624
|
function tagEncodedElement(element, decoded) {
|
|
625
|
+
if (isSiteTagged(element)) return false;
|
|
438
626
|
element.setAttribute("data-prepr-encoded", "");
|
|
439
627
|
element.setAttribute("data-prepr-href", decoded.href);
|
|
440
628
|
element.setAttribute("data-prepr-origin", decoded.origin);
|
|
629
|
+
const { id, field, locale, entry } = parseEditHref(decoded.href);
|
|
630
|
+
setOrRemove(element, "data-prepr-id", id);
|
|
631
|
+
const fieldHint = element.getAttribute("data-prepr-edit-target")?.trim() || void 0;
|
|
632
|
+
setOrRemove(element, "data-prepr-field", field ?? fieldHint);
|
|
633
|
+
setOrRemove(element, "data-prepr-locale", locale);
|
|
634
|
+
const siteEntry = element.hasAttribute("data-prepr-entry-id") && !element.hasAttribute(AUTO_ENTRY_ATTR);
|
|
635
|
+
if (!siteEntry) {
|
|
636
|
+
setOrRemove(element, "data-prepr-entry-id", entry);
|
|
637
|
+
setOrRemove(element, AUTO_ENTRY_ATTR, entry && "true");
|
|
638
|
+
}
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
function isSiteTagged(element) {
|
|
642
|
+
return element.hasAttribute("data-prepr-encoded") && !element.hasAttribute("data-prepr-href");
|
|
643
|
+
}
|
|
644
|
+
function readEditPayload(element) {
|
|
645
|
+
return {
|
|
646
|
+
href: element.getAttribute("data-prepr-href") ?? "",
|
|
647
|
+
origin: element.getAttribute("data-prepr-origin") ?? "",
|
|
648
|
+
id: element.getAttribute("data-prepr-id") ?? void 0,
|
|
649
|
+
field: element.getAttribute("data-prepr-field") ?? void 0,
|
|
650
|
+
locale: element.getAttribute("data-prepr-locale") ?? void 0,
|
|
651
|
+
entryId: element.getAttribute("data-prepr-entry-id") || void 0
|
|
652
|
+
};
|
|
441
653
|
}
|
|
442
654
|
|
|
443
655
|
// src/core/stega/auto-clean.ts
|
|
444
|
-
var
|
|
656
|
+
var debug3 = createScopedLogger("stega:auto-clean");
|
|
657
|
+
var cleanedNodes = /* @__PURE__ */ new WeakMap();
|
|
445
658
|
function createStegaAutoClean(options) {
|
|
446
659
|
const stripText = options?.enabled ?? true;
|
|
447
660
|
let observer = null;
|
|
448
661
|
let processing = false;
|
|
449
|
-
let cleaned = /* @__PURE__ */ new WeakMap();
|
|
450
662
|
let pendingHidden = /* @__PURE__ */ new Set();
|
|
451
663
|
let pendingDirty = /* @__PURE__ */ new Set();
|
|
452
664
|
let debounceTimeout = null;
|
|
453
665
|
function cleanTextNode(textNode) {
|
|
454
666
|
if (isIgnoredTextNode(textNode)) return false;
|
|
455
667
|
const textContent = textNode.textContent;
|
|
456
|
-
const info =
|
|
668
|
+
const info = cleanedNodes.get(textNode);
|
|
457
669
|
const settled = stripText ? info?.cleaned : info?.original;
|
|
458
670
|
if (info && textContent === settled) {
|
|
459
671
|
if (!info.decoded) return false;
|
|
460
672
|
const shown = resolveEditTarget(textNode);
|
|
461
|
-
if (!shown)
|
|
673
|
+
if (!shown) {
|
|
674
|
+
pendingHidden.add(textNode);
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
462
677
|
pendingHidden.delete(textNode);
|
|
463
678
|
if (shown.hasAttribute("data-prepr-encoded")) return false;
|
|
464
|
-
tagEncodedElement(shown, info.decoded);
|
|
465
|
-
return true;
|
|
679
|
+
return tagEncodedElement(shown, info.decoded);
|
|
466
680
|
}
|
|
467
681
|
try {
|
|
468
682
|
const { cleaned: stripped, encoded } = (0, import_stega2.vercelStegaSplit)(textContent);
|
|
469
683
|
if (!encoded || stripped === textContent) return false;
|
|
470
684
|
const decoded = decodeStega(textContent);
|
|
471
685
|
if (!decoded?.href && !stripText) return false;
|
|
472
|
-
|
|
686
|
+
cleanedNodes.set(textNode, {
|
|
473
687
|
original: textContent,
|
|
474
688
|
cleaned: stripped,
|
|
475
689
|
decoded: decoded?.href ? decoded : null
|
|
@@ -479,12 +693,11 @@ function createStegaAutoClean(options) {
|
|
|
479
693
|
const target = resolveEditTarget(textNode);
|
|
480
694
|
if (target) {
|
|
481
695
|
pendingHidden.delete(textNode);
|
|
482
|
-
tagEncodedElement(target, decoded);
|
|
483
|
-
return true;
|
|
696
|
+
return tagEncodedElement(target, decoded);
|
|
484
697
|
}
|
|
485
698
|
pendingHidden.add(textNode);
|
|
486
699
|
} catch (error) {
|
|
487
|
-
|
|
700
|
+
debug3.log("error cleaning text node", error);
|
|
488
701
|
}
|
|
489
702
|
return false;
|
|
490
703
|
}
|
|
@@ -511,7 +724,7 @@ function createStegaAutoClean(options) {
|
|
|
511
724
|
}
|
|
512
725
|
if (mutation.type === "characterData" && mutation.target.nodeType === Node.TEXT_NODE) {
|
|
513
726
|
const textNode = mutation.target;
|
|
514
|
-
const info =
|
|
727
|
+
const info = cleanedNodes.get(textNode);
|
|
515
728
|
const text = textNode.textContent ?? "";
|
|
516
729
|
const settled = stripText ? info?.cleaned : info?.original;
|
|
517
730
|
if (text !== settled && (0, import_stega2.vercelStegaSplit)(text).encoded) {
|
|
@@ -554,7 +767,16 @@ function createStegaAutoClean(options) {
|
|
|
554
767
|
retryPendingHidden();
|
|
555
768
|
}, 50);
|
|
556
769
|
}
|
|
770
|
+
let resizeTimeout = null;
|
|
771
|
+
function handleResize() {
|
|
772
|
+
if (pendingHidden.size === 0 || resizeTimeout) return;
|
|
773
|
+
resizeTimeout = setTimeout(() => {
|
|
774
|
+
resizeTimeout = null;
|
|
775
|
+
retryPendingHidden();
|
|
776
|
+
}, 100);
|
|
777
|
+
}
|
|
557
778
|
function setupObserver() {
|
|
779
|
+
window.addEventListener("resize", handleResize, { passive: true });
|
|
558
780
|
observer = new MutationObserver(handleMutations);
|
|
559
781
|
observer.observe(document.body, {
|
|
560
782
|
childList: true,
|
|
@@ -570,27 +792,464 @@ function createStegaAutoClean(options) {
|
|
|
570
792
|
if (observer || typeof window === "undefined") return;
|
|
571
793
|
scanAndClean();
|
|
572
794
|
setupObserver();
|
|
573
|
-
|
|
795
|
+
debug3.log("stega auto-clean started");
|
|
574
796
|
}
|
|
575
797
|
function stop() {
|
|
576
798
|
if (debounceTimeout) {
|
|
577
799
|
clearTimeout(debounceTimeout);
|
|
578
800
|
debounceTimeout = null;
|
|
579
801
|
}
|
|
802
|
+
if (resizeTimeout) {
|
|
803
|
+
clearTimeout(resizeTimeout);
|
|
804
|
+
resizeTimeout = null;
|
|
805
|
+
}
|
|
580
806
|
if (observer) {
|
|
581
807
|
observer.disconnect();
|
|
582
808
|
observer = null;
|
|
809
|
+
window.removeEventListener("resize", handleResize);
|
|
583
810
|
}
|
|
584
|
-
cleaned = /* @__PURE__ */ new WeakMap();
|
|
585
811
|
pendingHidden = /* @__PURE__ */ new Set();
|
|
586
812
|
pendingDirty = /* @__PURE__ */ new Set();
|
|
587
|
-
|
|
813
|
+
debug3.log("stega auto-clean stopped");
|
|
588
814
|
}
|
|
589
815
|
return { start, stop };
|
|
590
816
|
}
|
|
591
817
|
|
|
818
|
+
// src/core/visual-editor/overlay.ts
|
|
819
|
+
var HOST_TAG = "prepr-visual-editor";
|
|
820
|
+
var SCROLL_SETTLE_MS = 120;
|
|
821
|
+
var LABEL_SPACE_PX = 22;
|
|
822
|
+
var BOX_PADDING_PX = 3;
|
|
823
|
+
var LABEL_MARGIN_PX = 4;
|
|
824
|
+
var PROBE_POINTS = [
|
|
825
|
+
[0.5, 0.5],
|
|
826
|
+
[0.15, 0.2],
|
|
827
|
+
[0.85, 0.2],
|
|
828
|
+
[0.15, 0.8],
|
|
829
|
+
[0.85, 0.8]
|
|
830
|
+
];
|
|
831
|
+
function overlapsBox(area, rect) {
|
|
832
|
+
return area.left < rect.right + BOX_PADDING_PX && area.right > rect.left - BOX_PADDING_PX && area.top < rect.bottom + BOX_PADDING_PX && area.bottom > rect.top - BOX_PADDING_PX;
|
|
833
|
+
}
|
|
834
|
+
function overlaps(a2, b2) {
|
|
835
|
+
return a2.left < b2.right && b2.left < a2.right && a2.top < b2.bottom && b2.top < a2.bottom;
|
|
836
|
+
}
|
|
837
|
+
function coverOf(hit, target, x2, y2) {
|
|
838
|
+
let cover = hit.getBoundingClientRect();
|
|
839
|
+
for (let el = hit.parentElement; el && !el.contains(target) && el !== document.body; el = el.parentElement) {
|
|
840
|
+
const r2 = el.getBoundingClientRect();
|
|
841
|
+
if (x2 < r2.left || x2 >= r2.right || y2 < r2.top || y2 >= r2.bottom) break;
|
|
842
|
+
cover = r2;
|
|
843
|
+
}
|
|
844
|
+
return cover;
|
|
845
|
+
}
|
|
846
|
+
function mergeAreas(areas) {
|
|
847
|
+
const merged = areas.slice();
|
|
848
|
+
for (let i2 = 0; i2 < merged.length; i2++) {
|
|
849
|
+
for (let j2 = i2 + 1; j2 < merged.length; j2++) {
|
|
850
|
+
if (!overlaps(merged[i2], merged[j2])) continue;
|
|
851
|
+
merged[i2] = {
|
|
852
|
+
left: Math.min(merged[i2].left, merged[j2].left),
|
|
853
|
+
top: Math.min(merged[i2].top, merged[j2].top),
|
|
854
|
+
right: Math.max(merged[i2].right, merged[j2].right),
|
|
855
|
+
bottom: Math.max(merged[i2].bottom, merged[j2].bottom)
|
|
856
|
+
};
|
|
857
|
+
merged.splice(j2, 1);
|
|
858
|
+
j2 = i2;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return merged;
|
|
862
|
+
}
|
|
863
|
+
function findCovers(target, rect) {
|
|
864
|
+
const width = window.innerWidth;
|
|
865
|
+
const height = window.innerHeight;
|
|
866
|
+
const covers = [];
|
|
867
|
+
const seen = /* @__PURE__ */ new Set();
|
|
868
|
+
let probed = false;
|
|
869
|
+
let reached = false;
|
|
870
|
+
for (const [fx, fy] of PROBE_POINTS) {
|
|
871
|
+
const x2 = rect.left + rect.width * fx;
|
|
872
|
+
const y2 = rect.top + rect.height * fy;
|
|
873
|
+
if (x2 < 0 || y2 < 0 || x2 >= width || y2 >= height) continue;
|
|
874
|
+
probed = true;
|
|
875
|
+
const hit = document.elementFromPoint(x2, y2);
|
|
876
|
+
if (!hit || hit === target || target.contains(hit)) {
|
|
877
|
+
reached = true;
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
if (seen.has(hit)) continue;
|
|
881
|
+
seen.add(hit);
|
|
882
|
+
if (covers.some(
|
|
883
|
+
(c2) => x2 >= c2.left && x2 < c2.right && y2 >= c2.top && y2 < c2.bottom
|
|
884
|
+
)) {
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
covers.push(coverOf(hit, target, x2, y2));
|
|
888
|
+
}
|
|
889
|
+
if (!probed) return [];
|
|
890
|
+
return reached ? mergeAreas(covers) : null;
|
|
891
|
+
}
|
|
892
|
+
function clipPath(x2, y2, covers) {
|
|
893
|
+
const far = 1e5;
|
|
894
|
+
const points = [
|
|
895
|
+
`${-far}px ${-far}px`,
|
|
896
|
+
`${far}px ${-far}px`,
|
|
897
|
+
`${far}px ${far}px`,
|
|
898
|
+
`${-far}px ${far}px`,
|
|
899
|
+
`${-far}px ${-far}px`
|
|
900
|
+
];
|
|
901
|
+
for (const c2 of covers) {
|
|
902
|
+
const l2 = c2.left - x2;
|
|
903
|
+
const t3 = c2.top - y2;
|
|
904
|
+
const r2 = c2.right - x2;
|
|
905
|
+
const b2 = c2.bottom - y2;
|
|
906
|
+
points.push(
|
|
907
|
+
`${l2}px ${t3}px`,
|
|
908
|
+
`${r2}px ${t3}px`,
|
|
909
|
+
`${r2}px ${b2}px`,
|
|
910
|
+
`${l2}px ${b2}px`,
|
|
911
|
+
`${l2}px ${t3}px`,
|
|
912
|
+
`${-far}px ${-far}px`
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
return `polygon(evenodd, ${points.join(", ")})`;
|
|
916
|
+
}
|
|
917
|
+
var STYLES = `
|
|
918
|
+
:host {
|
|
919
|
+
all: initial;
|
|
920
|
+
position: fixed;
|
|
921
|
+
inset: 0;
|
|
922
|
+
pointer-events: none;
|
|
923
|
+
z-index: 2147483646;
|
|
924
|
+
contain: strict;
|
|
925
|
+
}
|
|
926
|
+
.box {
|
|
927
|
+
position: absolute;
|
|
928
|
+
top: 0;
|
|
929
|
+
left: 0;
|
|
930
|
+
box-sizing: border-box;
|
|
931
|
+
border-radius: 6px;
|
|
932
|
+
opacity: 0;
|
|
933
|
+
will-change: transform, width, height;
|
|
934
|
+
transition:
|
|
935
|
+
transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
|
|
936
|
+
width 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
|
|
937
|
+
height 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
|
|
938
|
+
opacity 140ms ease;
|
|
939
|
+
}
|
|
940
|
+
.box[data-visible] { opacity: 1; }
|
|
941
|
+
.box[data-instant], :host([data-scrolling]) .box {
|
|
942
|
+
transition: opacity 140ms ease;
|
|
943
|
+
}
|
|
944
|
+
.hover {
|
|
945
|
+
border: 1.5px solid rgba(79, 70, 229, 0.9);
|
|
946
|
+
background: rgba(79, 70, 229, 0.06);
|
|
947
|
+
}
|
|
948
|
+
.selected {
|
|
949
|
+
border: 2px solid #4f46e5;
|
|
950
|
+
box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.18);
|
|
951
|
+
}
|
|
952
|
+
.label {
|
|
953
|
+
position: absolute;
|
|
954
|
+
left: -2px;
|
|
955
|
+
bottom: 100%;
|
|
956
|
+
margin-bottom: 4px;
|
|
957
|
+
padding: 3px 7px;
|
|
958
|
+
border-radius: 5px;
|
|
959
|
+
background: #4f46e5;
|
|
960
|
+
color: #fff;
|
|
961
|
+
font: 500 11px/1.3 ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
962
|
+
letter-spacing: 0.01em;
|
|
963
|
+
white-space: nowrap;
|
|
964
|
+
display: flex;
|
|
965
|
+
max-width: 240px;
|
|
966
|
+
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.25);
|
|
967
|
+
}
|
|
968
|
+
.text {
|
|
969
|
+
min-width: 0;
|
|
970
|
+
overflow: hidden;
|
|
971
|
+
text-overflow: ellipsis;
|
|
972
|
+
}
|
|
973
|
+
.hover .label { background: rgba(79, 70, 229, 0.92); }
|
|
974
|
+
/* Content of another item than the one open in the editor: still clickable,
|
|
975
|
+
but visibly not part of the item being edited. */
|
|
976
|
+
.hover[data-item-kind='other'] {
|
|
977
|
+
border: 1.5px dashed #a5b4fc;
|
|
978
|
+
background: rgba(100, 116, 139, 0.05);
|
|
979
|
+
}
|
|
980
|
+
.hover[data-item-kind='other'] .label { background: #64748b; }
|
|
981
|
+
.hover[data-item-kind='referenced'] {
|
|
982
|
+
border: 1.5px solid #a5b4fc;
|
|
983
|
+
background: rgba(79, 70, 229, 0.03);
|
|
984
|
+
}
|
|
985
|
+
.hover[data-item-kind='referenced'] .label { background: #818cf8; }
|
|
986
|
+
.box[data-label-covered] .label { visibility: hidden; }
|
|
987
|
+
.box[data-label-inside] .label {
|
|
988
|
+
bottom: auto;
|
|
989
|
+
top: 2px;
|
|
990
|
+
left: 2px;
|
|
991
|
+
margin: 0;
|
|
992
|
+
}
|
|
993
|
+
/* A clickable hover label (edit mode on the live site). Only while shown, so
|
|
994
|
+
a fading box never leaves an invisible click target behind. */
|
|
995
|
+
:host([data-label-action]) .hover[data-visible] .label {
|
|
996
|
+
pointer-events: auto;
|
|
997
|
+
cursor: pointer;
|
|
998
|
+
}
|
|
999
|
+
:host([data-label-action]) .hover .label::after {
|
|
1000
|
+
content: '\\2197';
|
|
1001
|
+
margin-left: 5px;
|
|
1002
|
+
}
|
|
1003
|
+
:host([data-label-action]) .hover .label:hover { background: #4338ca; }
|
|
1004
|
+
/* Bridges the gap between the label and its element, so the pointer can
|
|
1005
|
+
travel from one to the other without the hover ending on the way. */
|
|
1006
|
+
:host([data-label-action]) .hover .label::before {
|
|
1007
|
+
content: '';
|
|
1008
|
+
position: absolute;
|
|
1009
|
+
left: 0;
|
|
1010
|
+
right: 0;
|
|
1011
|
+
top: 100%;
|
|
1012
|
+
height: ${LABEL_MARGIN_PX + BOX_PADDING_PX + 2}px;
|
|
1013
|
+
}
|
|
1014
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1015
|
+
.box { transition: opacity 80ms linear; }
|
|
1016
|
+
}
|
|
1017
|
+
`;
|
|
1018
|
+
var VisualEditorOverlay = class {
|
|
1019
|
+
constructor(options = {}) {
|
|
1020
|
+
this.host = null;
|
|
1021
|
+
this.hoverBox = null;
|
|
1022
|
+
this.selectedBox = null;
|
|
1023
|
+
this.frame = null;
|
|
1024
|
+
this.scrollTimeout = null;
|
|
1025
|
+
this.resizeObserver = null;
|
|
1026
|
+
this.onLabelClick = options.onLabelClick;
|
|
1027
|
+
}
|
|
1028
|
+
mount() {
|
|
1029
|
+
if (this.host) return;
|
|
1030
|
+
const host = document.createElement(HOST_TAG);
|
|
1031
|
+
const shadow = host.attachShadow({ mode: "open" });
|
|
1032
|
+
const style = document.createElement("style");
|
|
1033
|
+
style.textContent = STYLES;
|
|
1034
|
+
shadow.appendChild(style);
|
|
1035
|
+
this.hoverBox = this.createBox(shadow, "hover");
|
|
1036
|
+
this.selectedBox = this.createBox(shadow, "selected");
|
|
1037
|
+
const onLabelClick = this.onLabelClick;
|
|
1038
|
+
if (onLabelClick) {
|
|
1039
|
+
host.setAttribute("data-label-action", "");
|
|
1040
|
+
const hoverBox = this.hoverBox;
|
|
1041
|
+
hoverBox.label.addEventListener("click", () => {
|
|
1042
|
+
if (hoverBox.target) onLabelClick(hoverBox.target);
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
document.documentElement.appendChild(host);
|
|
1046
|
+
this.host = host;
|
|
1047
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1048
|
+
this.resizeObserver = new ResizeObserver(() => this.schedule());
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
unmount() {
|
|
1052
|
+
if (this.frame !== null) cancelAnimationFrame(this.frame);
|
|
1053
|
+
if (this.scrollTimeout) clearTimeout(this.scrollTimeout);
|
|
1054
|
+
this.frame = null;
|
|
1055
|
+
this.scrollTimeout = null;
|
|
1056
|
+
this.resizeObserver?.disconnect();
|
|
1057
|
+
this.resizeObserver = null;
|
|
1058
|
+
this.host?.remove();
|
|
1059
|
+
this.host = null;
|
|
1060
|
+
this.hoverBox = null;
|
|
1061
|
+
this.selectedBox = null;
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Whether a pointer event target is this chrome: events from inside the
|
|
1065
|
+
* shadow root reach the page retargeted to its host.
|
|
1066
|
+
*/
|
|
1067
|
+
owns(node) {
|
|
1068
|
+
return node !== null && node === this.host;
|
|
1069
|
+
}
|
|
1070
|
+
getHoverTarget() {
|
|
1071
|
+
return this.hoverBox?.target ?? null;
|
|
1072
|
+
}
|
|
1073
|
+
getSelectedTarget() {
|
|
1074
|
+
return this.selectedBox?.target ?? null;
|
|
1075
|
+
}
|
|
1076
|
+
setHover(target, label, options = {}) {
|
|
1077
|
+
if (target) {
|
|
1078
|
+
this.hoverBox?.root.setAttribute(
|
|
1079
|
+
"data-item-kind",
|
|
1080
|
+
options.kind ?? "current"
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
this.retarget(this.hoverBox, target, label);
|
|
1084
|
+
}
|
|
1085
|
+
setSelected(target, label) {
|
|
1086
|
+
this.retarget(this.selectedBox, target, label);
|
|
1087
|
+
}
|
|
1088
|
+
/** Call on every scroll; positions follow in the next frame, untransitioned. */
|
|
1089
|
+
onScroll() {
|
|
1090
|
+
if (!this.host) return;
|
|
1091
|
+
this.host.setAttribute("data-scrolling", "");
|
|
1092
|
+
if (this.scrollTimeout) clearTimeout(this.scrollTimeout);
|
|
1093
|
+
this.scrollTimeout = setTimeout(() => {
|
|
1094
|
+
this.host?.removeAttribute("data-scrolling");
|
|
1095
|
+
this.scrollTimeout = null;
|
|
1096
|
+
}, SCROLL_SETTLE_MS);
|
|
1097
|
+
this.schedule();
|
|
1098
|
+
}
|
|
1099
|
+
/** Re-measure both boxes in the next animation frame. */
|
|
1100
|
+
schedule() {
|
|
1101
|
+
if (this.frame !== null || !this.host) return;
|
|
1102
|
+
this.frame = requestAnimationFrame(() => {
|
|
1103
|
+
this.frame = null;
|
|
1104
|
+
this.render();
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
createBox(shadow, kind) {
|
|
1108
|
+
const root = document.createElement("div");
|
|
1109
|
+
root.className = `box ${kind}`;
|
|
1110
|
+
const label = document.createElement("div");
|
|
1111
|
+
label.className = "label";
|
|
1112
|
+
const text = document.createElement("span");
|
|
1113
|
+
text.className = "text";
|
|
1114
|
+
label.appendChild(text);
|
|
1115
|
+
root.appendChild(label);
|
|
1116
|
+
shadow.appendChild(root);
|
|
1117
|
+
return { root, label, text, target: null };
|
|
1118
|
+
}
|
|
1119
|
+
retarget(box, target, label) {
|
|
1120
|
+
if (!box || box.target === target) return;
|
|
1121
|
+
const previous = box.target;
|
|
1122
|
+
if (previous) this.resizeObserver?.unobserve(previous);
|
|
1123
|
+
box.target = target;
|
|
1124
|
+
if (target) {
|
|
1125
|
+
this.resizeObserver?.observe(target);
|
|
1126
|
+
box.text.textContent = label;
|
|
1127
|
+
if (!box.root.hasAttribute("data-visible")) {
|
|
1128
|
+
box.root.setAttribute("data-instant", "");
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
this.schedule();
|
|
1132
|
+
}
|
|
1133
|
+
render() {
|
|
1134
|
+
const hover = this.hoverBox ? this.measure(this.hoverBox) : null;
|
|
1135
|
+
const selected = this.selectedBox ? this.measure(this.selectedBox) : null;
|
|
1136
|
+
if (this.hoverBox) this.paint(this.hoverBox, hover, selected);
|
|
1137
|
+
if (this.selectedBox) this.paint(this.selectedBox, selected, hover);
|
|
1138
|
+
}
|
|
1139
|
+
measure(box) {
|
|
1140
|
+
const target = box.target;
|
|
1141
|
+
if (!target) return null;
|
|
1142
|
+
if (!target.isConnected) {
|
|
1143
|
+
this.resizeObserver?.unobserve(target);
|
|
1144
|
+
box.target = null;
|
|
1145
|
+
return null;
|
|
1146
|
+
}
|
|
1147
|
+
const rect = target.getBoundingClientRect();
|
|
1148
|
+
if (rect.width === 0 && rect.height === 0) return null;
|
|
1149
|
+
const covers = findCovers(target, rect);
|
|
1150
|
+
if (!covers) return null;
|
|
1151
|
+
return {
|
|
1152
|
+
rect,
|
|
1153
|
+
covers,
|
|
1154
|
+
labelWidth: covers.length ? box.label.offsetWidth : 0,
|
|
1155
|
+
labelHeight: covers.length ? box.label.offsetHeight : 0
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
paint(box, measurement, other = null) {
|
|
1159
|
+
const root = box.root;
|
|
1160
|
+
if (!measurement) {
|
|
1161
|
+
root.removeAttribute("data-visible");
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
const { rect, covers } = measurement;
|
|
1165
|
+
const x2 = rect.left - BOX_PADDING_PX;
|
|
1166
|
+
const y2 = rect.top - BOX_PADDING_PX;
|
|
1167
|
+
const labelAbove = {
|
|
1168
|
+
left: x2 - 2,
|
|
1169
|
+
top: y2 - LABEL_SPACE_PX,
|
|
1170
|
+
right: x2 - 2 + (measurement.labelWidth || Math.min(240, rect.width)),
|
|
1171
|
+
bottom: y2
|
|
1172
|
+
};
|
|
1173
|
+
const labelInside = y2 < LABEL_SPACE_PX || other !== null && other.rect !== rect && overlapsBox(labelAbove, other.rect);
|
|
1174
|
+
root.style.transform = `translate3d(${x2}px, ${y2}px, 0)`;
|
|
1175
|
+
root.style.width = `${rect.width + BOX_PADDING_PX * 2}px`;
|
|
1176
|
+
root.style.height = `${rect.height + BOX_PADDING_PX * 2}px`;
|
|
1177
|
+
root.toggleAttribute("data-label-inside", labelInside);
|
|
1178
|
+
root.style.clipPath = covers.length ? clipPath(x2, y2, covers) : "";
|
|
1179
|
+
const labelLeft = labelInside ? x2 + 2 : x2 - 2;
|
|
1180
|
+
const labelTop = labelInside ? y2 + 2 : y2 - LABEL_MARGIN_PX - measurement.labelHeight;
|
|
1181
|
+
const label = {
|
|
1182
|
+
left: labelLeft,
|
|
1183
|
+
top: labelTop,
|
|
1184
|
+
right: labelLeft + measurement.labelWidth,
|
|
1185
|
+
bottom: labelTop + measurement.labelHeight
|
|
1186
|
+
};
|
|
1187
|
+
root.toggleAttribute(
|
|
1188
|
+
"data-label-covered",
|
|
1189
|
+
covers.some((c2) => overlaps(c2, label))
|
|
1190
|
+
);
|
|
1191
|
+
if (root.hasAttribute("data-instant")) {
|
|
1192
|
+
root.setAttribute("data-visible", "");
|
|
1193
|
+
requestAnimationFrame(() => root.removeAttribute("data-instant"));
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
root.setAttribute("data-visible", "");
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
// src/core/visual-editor/field-path.ts
|
|
1201
|
+
var INDEX_SEGMENT = /^\d+$/;
|
|
1202
|
+
var WORD_SEPARATORS = /[_-]+/g;
|
|
1203
|
+
function normalizeFieldPath(path) {
|
|
1204
|
+
return path.split(".").map(
|
|
1205
|
+
(segment) => INDEX_SEGMENT.test(segment) ? String(Number.parseInt(segment, 10)) : segment
|
|
1206
|
+
).join(".");
|
|
1207
|
+
}
|
|
1208
|
+
var GENERIC_LEAVES = /* @__PURE__ */ new Set(["value", "text", "body", "content", "label"]);
|
|
1209
|
+
function humanize(name) {
|
|
1210
|
+
const words = name.replace(WORD_SEPARATORS, " ").trim();
|
|
1211
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
1212
|
+
}
|
|
1213
|
+
function fieldLabel(path, locale = "en") {
|
|
1214
|
+
if (!path) return t("visualEditor.edit", locale);
|
|
1215
|
+
const segments = path.split(".").filter((s2) => s2 && s2 !== "items" && !INDEX_SEGMENT.test(s2));
|
|
1216
|
+
const name = segments[segments.length - 1];
|
|
1217
|
+
if (!name) return t("visualEditor.edit", locale);
|
|
1218
|
+
const parent = segments[segments.length - 2];
|
|
1219
|
+
if (parent && GENERIC_LEAVES.has(name)) {
|
|
1220
|
+
return `${humanize(parent)} \u203A ${humanize(name)}`;
|
|
1221
|
+
}
|
|
1222
|
+
return humanize(name);
|
|
1223
|
+
}
|
|
1224
|
+
function findFieldElement(lookup) {
|
|
1225
|
+
if (!lookup.field) return null;
|
|
1226
|
+
const candidates = document.querySelectorAll(
|
|
1227
|
+
"[data-prepr-encoded]"
|
|
1228
|
+
);
|
|
1229
|
+
if (lookup.entryId) {
|
|
1230
|
+
for (const candidate of candidates) {
|
|
1231
|
+
if (candidate.getAttribute("data-prepr-entry-id") === lookup.entryId) {
|
|
1232
|
+
return candidate;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
for (const path of [lookup.field, lookup.fieldById]) {
|
|
1237
|
+
if (!path) continue;
|
|
1238
|
+
const wanted = normalizeFieldPath(path);
|
|
1239
|
+
for (const candidate of candidates) {
|
|
1240
|
+
const field = candidate.getAttribute("data-prepr-field");
|
|
1241
|
+
if (!field || normalizeFieldPath(field) !== wanted) continue;
|
|
1242
|
+
if (lookup.id && candidate.getAttribute("data-prepr-id") !== lookup.id) {
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1245
|
+
return candidate;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
return null;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
592
1251
|
// src/core/stega/elements.ts
|
|
593
|
-
var
|
|
1252
|
+
var debug4 = createScopedLogger("stega:elements");
|
|
594
1253
|
var StegaElements = class {
|
|
595
1254
|
constructor(decode = decodeStega) {
|
|
596
1255
|
this.observer = null;
|
|
@@ -635,7 +1294,7 @@ var StegaElements = class {
|
|
|
635
1294
|
walkTextNodes(document.body, (textNode) => {
|
|
636
1295
|
if (this.tagFromTextNode(textNode)) encodedCount++;
|
|
637
1296
|
});
|
|
638
|
-
|
|
1297
|
+
debug4.log("document scan complete, encoded", encodedCount, "elements");
|
|
639
1298
|
this.elements = document.querySelectorAll("[data-prepr-encoded]");
|
|
640
1299
|
}
|
|
641
1300
|
/** `onUpdate` fires after each debounced batch, for refreshing derived state. */
|
|
@@ -678,231 +1337,22 @@ var StegaElements = class {
|
|
|
678
1337
|
attributeFilter: ["class", "style", "hidden", "open", "aria-expanded"]
|
|
679
1338
|
});
|
|
680
1339
|
}
|
|
681
|
-
/**
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
1340
|
+
/**
|
|
1341
|
+
* Stops watching for new content. Tags stay on their elements: the page's
|
|
1342
|
+
* stega characters are stripped once they are read, so a tag removed here
|
|
1343
|
+
* could never be rebuilt when edit mode is turned back on.
|
|
1344
|
+
*/
|
|
1345
|
+
disconnect() {
|
|
686
1346
|
if (this.observer) {
|
|
687
1347
|
this.observer.disconnect();
|
|
688
1348
|
this.observer = null;
|
|
689
1349
|
}
|
|
690
|
-
document.querySelectorAll("[data-prepr-encoded]").forEach((element) => {
|
|
691
|
-
element.removeAttribute("data-prepr-encoded");
|
|
692
|
-
element.removeAttribute("data-prepr-href");
|
|
693
|
-
element.removeAttribute("data-prepr-origin");
|
|
694
|
-
element.classList.remove("prepr-overlay-active");
|
|
695
|
-
});
|
|
696
1350
|
this.elements = void 0;
|
|
697
1351
|
}
|
|
698
1352
|
};
|
|
699
1353
|
|
|
700
|
-
// src/core/stega/overlay.ts
|
|
701
|
-
var debug4 = createScopedLogger("stega:overlay");
|
|
702
|
-
var StegaOverlay = class {
|
|
703
|
-
constructor(onEdit) {
|
|
704
|
-
this.overlay = null;
|
|
705
|
-
this.tooltip = null;
|
|
706
|
-
this.hideTimeout = null;
|
|
707
|
-
this.currentElement = null;
|
|
708
|
-
this.onEdit = onEdit;
|
|
709
|
-
}
|
|
710
|
-
create() {
|
|
711
|
-
const overlay = document.createElement("div");
|
|
712
|
-
overlay.className = "prepr-overlay";
|
|
713
|
-
overlay.style.display = "none";
|
|
714
|
-
const tooltip = document.createElement("div");
|
|
715
|
-
tooltip.className = "prepr-tooltip";
|
|
716
|
-
tooltip.style.display = "none";
|
|
717
|
-
document.body.appendChild(overlay);
|
|
718
|
-
document.body.appendChild(tooltip);
|
|
719
|
-
this.overlay = overlay;
|
|
720
|
-
this.tooltip = tooltip;
|
|
721
|
-
return { overlay, tooltip };
|
|
722
|
-
}
|
|
723
|
-
getTooltip() {
|
|
724
|
-
return this.tooltip;
|
|
725
|
-
}
|
|
726
|
-
hasCurrentElement() {
|
|
727
|
-
return this.currentElement !== null;
|
|
728
|
-
}
|
|
729
|
-
clearHideTimeout() {
|
|
730
|
-
if (this.hideTimeout) {
|
|
731
|
-
clearTimeout(this.hideTimeout);
|
|
732
|
-
this.hideTimeout = null;
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
show(element) {
|
|
736
|
-
if (!this.overlay || !this.tooltip) return;
|
|
737
|
-
this.clearHideTimeout();
|
|
738
|
-
if (this.currentElement && this.currentElement !== element) {
|
|
739
|
-
this.currentElement.classList.remove("prepr-overlay-active");
|
|
740
|
-
}
|
|
741
|
-
const rect = element.getBoundingClientRect();
|
|
742
|
-
const href = element.getAttribute("data-prepr-href");
|
|
743
|
-
const origin = element.getAttribute("data-prepr-origin");
|
|
744
|
-
const id = element.getAttribute("data-prepr-id") ?? void 0;
|
|
745
|
-
const field = element.getAttribute("data-prepr-field") ?? void 0;
|
|
746
|
-
debug4.log("showing overlay", { href, origin });
|
|
747
|
-
const overlay = this.overlay;
|
|
748
|
-
overlay.style.display = "block";
|
|
749
|
-
overlay.style.top = `${rect.top + window.scrollY - 2}px`;
|
|
750
|
-
overlay.style.left = `${rect.left + window.scrollX - 4}px`;
|
|
751
|
-
overlay.style.width = `${rect.width + 8}px`;
|
|
752
|
-
overlay.style.height = `${rect.height + 4}px`;
|
|
753
|
-
const tooltip = this.tooltip;
|
|
754
|
-
if (href && origin) {
|
|
755
|
-
const MIN_WIDTH_FOR_TEXT = 80;
|
|
756
|
-
const isCompact = rect.width < MIN_WIDTH_FOR_TEXT;
|
|
757
|
-
tooltip.textContent = isCompact ? "\u2197" : `${origin} \u2197`;
|
|
758
|
-
tooltip.style.display = "block";
|
|
759
|
-
tooltip.style.minWidth = isCompact ? "auto" : "80px";
|
|
760
|
-
requestAnimationFrame(() => {
|
|
761
|
-
let top = rect.top + window.scrollY - tooltip.clientHeight - 2;
|
|
762
|
-
let left = rect.right + 4 - tooltip.clientWidth;
|
|
763
|
-
const minTop = window.scrollY + 4;
|
|
764
|
-
const maxTop = window.scrollY + window.innerHeight - tooltip.clientHeight - 4;
|
|
765
|
-
const minLeft = window.scrollX + 4;
|
|
766
|
-
const maxLeft = window.scrollX + window.innerWidth - tooltip.clientWidth - 4;
|
|
767
|
-
const below = top < minTop;
|
|
768
|
-
if (below) {
|
|
769
|
-
top = rect.bottom + window.scrollY + 2;
|
|
770
|
-
}
|
|
771
|
-
top = Math.max(minTop, Math.min(top, maxTop));
|
|
772
|
-
left = Math.max(minLeft, Math.min(left, maxLeft));
|
|
773
|
-
tooltip.dataset.preprPlacement = below ? "below" : "above";
|
|
774
|
-
tooltip.style.top = `${top}px`;
|
|
775
|
-
tooltip.style.left = `${left}px`;
|
|
776
|
-
});
|
|
777
|
-
tooltip.onclick = () => {
|
|
778
|
-
this.onEdit({ href, origin, id, field });
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
this.currentElement = element;
|
|
782
|
-
element.classList.add("prepr-overlay-active");
|
|
783
|
-
}
|
|
784
|
-
hideImmediate() {
|
|
785
|
-
this.clearHideTimeout();
|
|
786
|
-
if (this.overlay) this.overlay.style.display = "none";
|
|
787
|
-
if (this.tooltip) this.tooltip.style.display = "none";
|
|
788
|
-
if (this.currentElement) {
|
|
789
|
-
this.currentElement.classList.remove("prepr-overlay-active");
|
|
790
|
-
this.currentElement = null;
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
hide() {
|
|
794
|
-
if (!this.overlay || !this.tooltip) return;
|
|
795
|
-
this.clearHideTimeout();
|
|
796
|
-
this.hideTimeout = setTimeout(() => this.hideImmediate(), 100);
|
|
797
|
-
}
|
|
798
|
-
cleanup() {
|
|
799
|
-
this.clearHideTimeout();
|
|
800
|
-
if (this.overlay) {
|
|
801
|
-
this.overlay.style.display = "none";
|
|
802
|
-
this.overlay.parentNode?.removeChild(this.overlay);
|
|
803
|
-
this.overlay = null;
|
|
804
|
-
}
|
|
805
|
-
if (this.tooltip) {
|
|
806
|
-
this.tooltip.style.display = "none";
|
|
807
|
-
this.tooltip.parentNode?.removeChild(this.tooltip);
|
|
808
|
-
this.tooltip = null;
|
|
809
|
-
}
|
|
810
|
-
if (this.currentElement) {
|
|
811
|
-
this.currentElement.classList.remove("prepr-overlay-active");
|
|
812
|
-
this.currentElement = null;
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
};
|
|
816
|
-
|
|
817
|
-
// src/core/stega/proximity.ts
|
|
818
|
-
var debug5 = createScopedLogger("stega:proximity");
|
|
819
|
-
var StegaProximity = class {
|
|
820
|
-
constructor() {
|
|
821
|
-
this.highlighted = /* @__PURE__ */ new Set();
|
|
822
|
-
this.visible = /* @__PURE__ */ new Set();
|
|
823
|
-
this.observer = null;
|
|
824
|
-
this.getEncodedElements = createElementCache(
|
|
825
|
-
"[data-prepr-encoded]",
|
|
826
|
-
200
|
|
827
|
-
);
|
|
828
|
-
}
|
|
829
|
-
/** Call after the encoded element set changes — rebuilds the observer. */
|
|
830
|
-
refreshObserving() {
|
|
831
|
-
try {
|
|
832
|
-
if (typeof IntersectionObserver === "undefined") return;
|
|
833
|
-
if (this.observer) {
|
|
834
|
-
this.observer.disconnect();
|
|
835
|
-
this.observer = null;
|
|
836
|
-
}
|
|
837
|
-
this.visible = /* @__PURE__ */ new Set();
|
|
838
|
-
this.observer = new IntersectionObserver(
|
|
839
|
-
(entries) => {
|
|
840
|
-
entries.forEach((entry) => {
|
|
841
|
-
const el = entry.target;
|
|
842
|
-
if (entry.isIntersecting) {
|
|
843
|
-
this.visible.add(el);
|
|
844
|
-
} else {
|
|
845
|
-
this.visible.delete(el);
|
|
846
|
-
}
|
|
847
|
-
});
|
|
848
|
-
},
|
|
849
|
-
{ root: null, rootMargin: "0px", threshold: 0 }
|
|
850
|
-
);
|
|
851
|
-
const nodes = this.getEncodedElements();
|
|
852
|
-
nodes.forEach((el) => this.observer.observe(el));
|
|
853
|
-
debug5.log("observing", nodes.length, "encoded elements");
|
|
854
|
-
} catch (error) {
|
|
855
|
-
debug5.log("error setting up IntersectionObserver", error);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
stopObserving() {
|
|
859
|
-
if (this.observer) {
|
|
860
|
-
this.observer.disconnect();
|
|
861
|
-
this.observer = null;
|
|
862
|
-
this.visible.clear();
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
updateElementGradients(cursorX, cursorY) {
|
|
866
|
-
const candidates = this.visible.size > 0 ? Array.from(this.visible) : Array.from(this.getEncodedElements());
|
|
867
|
-
const newHighlighted = /* @__PURE__ */ new Set();
|
|
868
|
-
candidates.forEach((element) => {
|
|
869
|
-
const rect = element.getBoundingClientRect();
|
|
870
|
-
const distance = Math.min(
|
|
871
|
-
Math.abs(cursorX - rect.left),
|
|
872
|
-
Math.abs(cursorX - rect.right),
|
|
873
|
-
Math.abs(cursorY - rect.top),
|
|
874
|
-
Math.abs(cursorY - rect.bottom)
|
|
875
|
-
);
|
|
876
|
-
if (distance < 150) {
|
|
877
|
-
const relativeX = cursorX - rect.left;
|
|
878
|
-
const relativeY = cursorY - rect.top;
|
|
879
|
-
element.style.setProperty("--cursor-x", `${relativeX}px`);
|
|
880
|
-
element.style.setProperty("--cursor-y", `${relativeY}px`);
|
|
881
|
-
const baseGradientSize = Math.max(
|
|
882
|
-
150,
|
|
883
|
-
Math.max(rect.width, rect.height) * 1.1
|
|
884
|
-
);
|
|
885
|
-
const distanceScale = Math.max(0, (400 - distance) / 400);
|
|
886
|
-
const gradientSize = baseGradientSize * distanceScale;
|
|
887
|
-
element.style.setProperty("--gradient-size", `${gradientSize}px`);
|
|
888
|
-
element.classList.add("prepr-proximity-highlight");
|
|
889
|
-
newHighlighted.add(element);
|
|
890
|
-
} else {
|
|
891
|
-
element.classList.remove("prepr-proximity-highlight");
|
|
892
|
-
}
|
|
893
|
-
});
|
|
894
|
-
this.highlighted = newHighlighted;
|
|
895
|
-
}
|
|
896
|
-
clearAllHighlights() {
|
|
897
|
-
this.highlighted.forEach(
|
|
898
|
-
(element) => element.classList.remove("prepr-proximity-highlight")
|
|
899
|
-
);
|
|
900
|
-
this.highlighted.clear();
|
|
901
|
-
}
|
|
902
|
-
};
|
|
903
|
-
|
|
904
1354
|
// src/core/stega/stega.generated.ts
|
|
905
|
-
var STEGA_STYLES = '
|
|
1355
|
+
var STEGA_STYLES = '@property --prepr-presence{syntax:"<number>";inherits:true;initial-value:1}[data-prepr-encoded]{outline:1px dashed rgb(79 70 229/calc(.3 * var(--prepr-presence,1)));outline-offset:2px}';
|
|
906
1356
|
|
|
907
1357
|
// src/core/stega/styles.ts
|
|
908
1358
|
var STEGA_STYLE_MARKER = "data-prepr-stega";
|
|
@@ -922,45 +1372,80 @@ function removeStegaStyles() {
|
|
|
922
1372
|
}
|
|
923
1373
|
|
|
924
1374
|
// src/core/stega/scan.ts
|
|
925
|
-
var
|
|
1375
|
+
var debug5 = createScopedLogger("stega:scan");
|
|
1376
|
+
var ENCODED_SELECTOR = "[data-prepr-encoded]";
|
|
1377
|
+
var PRESENCE_FADE_MS = 400;
|
|
926
1378
|
var EDITOR_MODE_STYLES = "[data-prepr-encoded]{cursor:pointer}";
|
|
1379
|
+
function fadeInPresence() {
|
|
1380
|
+
const root = document.documentElement;
|
|
1381
|
+
if (typeof root.animate !== "function") return;
|
|
1382
|
+
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;
|
|
1383
|
+
root.animate(
|
|
1384
|
+
{ "--prepr-presence": ["0", "1"] },
|
|
1385
|
+
{ duration: PRESENCE_FADE_MS, easing: "ease-out" }
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
927
1388
|
function createStegaController(opts) {
|
|
928
1389
|
const tooltipEnabled = opts.tooltip ?? true;
|
|
929
|
-
const
|
|
930
|
-
const proximity = new StegaProximity();
|
|
1390
|
+
const locale = opts.locale ?? "en";
|
|
931
1391
|
const elements = new StegaElements();
|
|
1392
|
+
const overlay = new VisualEditorOverlay({
|
|
1393
|
+
onLabelClick: (target) => opts.onEdit(readEditPayload(target))
|
|
1394
|
+
});
|
|
932
1395
|
let initialized = false;
|
|
933
|
-
let
|
|
934
|
-
|
|
935
|
-
|
|
1396
|
+
let pointerFrame = null;
|
|
1397
|
+
let pointerTarget = null;
|
|
1398
|
+
let pointerX = 0;
|
|
1399
|
+
let pointerY = 0;
|
|
1400
|
+
let hitTestPending = false;
|
|
1401
|
+
const updateHover = () => {
|
|
1402
|
+
pointerFrame = null;
|
|
1403
|
+
if (hitTestPending) {
|
|
1404
|
+
hitTestPending = false;
|
|
1405
|
+
pointerTarget = document.elementFromPoint(pointerX, pointerY);
|
|
1406
|
+
}
|
|
1407
|
+
if (overlay.owns(pointerTarget)) return;
|
|
1408
|
+
const encoded = pointerTarget instanceof Element ? pointerTarget.closest(ENCODED_SELECTOR) : null;
|
|
1409
|
+
overlay.setHover(
|
|
1410
|
+
encoded,
|
|
1411
|
+
encoded ? fieldLabel(encoded.getAttribute("data-prepr-field"), locale) : ""
|
|
1412
|
+
);
|
|
936
1413
|
};
|
|
937
|
-
const
|
|
938
|
-
if (
|
|
939
|
-
|
|
1414
|
+
const scheduleHover = () => {
|
|
1415
|
+
if (pointerFrame === null) {
|
|
1416
|
+
pointerFrame = requestAnimationFrame(updateHover);
|
|
940
1417
|
}
|
|
941
1418
|
};
|
|
1419
|
+
const handlePointer = (e2) => {
|
|
1420
|
+
pointerTarget = e2.target;
|
|
1421
|
+
pointerX = e2.clientX;
|
|
1422
|
+
pointerY = e2.clientY;
|
|
1423
|
+
hitTestPending = false;
|
|
1424
|
+
scheduleHover();
|
|
1425
|
+
};
|
|
1426
|
+
const handlePointerLeave = () => {
|
|
1427
|
+
pointerTarget = null;
|
|
1428
|
+
hitTestPending = false;
|
|
1429
|
+
overlay.setHover(null, "");
|
|
1430
|
+
};
|
|
942
1431
|
const handleScroll = () => {
|
|
943
|
-
overlay.
|
|
944
|
-
|
|
1432
|
+
overlay.onScroll();
|
|
1433
|
+
if (pointerTarget === null) return;
|
|
1434
|
+
hitTestPending = true;
|
|
1435
|
+
scheduleHover();
|
|
945
1436
|
};
|
|
1437
|
+
const handleResize = () => overlay.schedule();
|
|
946
1438
|
const handleClick = (e2) => {
|
|
947
1439
|
const target = e2.target;
|
|
948
|
-
const encoded = target?.closest(
|
|
949
|
-
"[data-prepr-encoded]"
|
|
950
|
-
);
|
|
1440
|
+
const encoded = target?.closest(ENCODED_SELECTOR);
|
|
951
1441
|
if (!encoded) return;
|
|
952
1442
|
e2.preventDefault();
|
|
953
1443
|
e2.stopPropagation();
|
|
954
|
-
opts.onEdit(
|
|
955
|
-
href: encoded.getAttribute("data-prepr-href") ?? "",
|
|
956
|
-
origin: encoded.getAttribute("data-prepr-origin") ?? "",
|
|
957
|
-
id: encoded.getAttribute("data-prepr-id") ?? void 0,
|
|
958
|
-
field: encoded.getAttribute("data-prepr-field") ?? void 0
|
|
959
|
-
});
|
|
1444
|
+
opts.onEdit(readEditPayload(encoded));
|
|
960
1445
|
};
|
|
961
1446
|
const start = () => {
|
|
962
1447
|
if (initialized) {
|
|
963
|
-
|
|
1448
|
+
debug5.log("already initialized, skipping setup");
|
|
964
1449
|
return;
|
|
965
1450
|
}
|
|
966
1451
|
if (!tooltipEnabled) {
|
|
@@ -970,71 +1455,94 @@ function createStegaController(opts) {
|
|
|
970
1455
|
});
|
|
971
1456
|
document.addEventListener("click", handleClick, { capture: true });
|
|
972
1457
|
initialized = true;
|
|
973
|
-
|
|
1458
|
+
debug5.log("stega controller started (headless)");
|
|
974
1459
|
return;
|
|
975
1460
|
}
|
|
976
1461
|
injectStegaStyles();
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
tooltip.addEventListener("mouseleave", handleTooltipMouseLeave);
|
|
1462
|
+
fadeInPresence();
|
|
1463
|
+
overlay.mount();
|
|
980
1464
|
elements.scanDocument(true);
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
const target = mouseEvent.target;
|
|
986
|
-
if (!target) return;
|
|
987
|
-
if (target.closest(".prepr-tooltip")) return;
|
|
988
|
-
proximity.updateElementGradients(mouseEvent.clientX, mouseEvent.clientY);
|
|
989
|
-
const encoded = target.closest("[data-prepr-encoded]");
|
|
990
|
-
if (encoded) {
|
|
991
|
-
overlay.show(encoded);
|
|
992
|
-
} else {
|
|
993
|
-
overlay.hide();
|
|
994
|
-
}
|
|
995
|
-
}, 16);
|
|
996
|
-
document.addEventListener("mousemove", throttledMouseMove, {
|
|
997
|
-
passive: true
|
|
1465
|
+
elements.setupMutationObserver(() => {
|
|
1466
|
+
if (pointerTarget === null) return;
|
|
1467
|
+
hitTestPending = true;
|
|
1468
|
+
scheduleHover();
|
|
998
1469
|
});
|
|
1470
|
+
document.addEventListener("pointermove", handlePointer, { passive: true });
|
|
1471
|
+
document.addEventListener("pointerdown", handlePointer, { passive: true });
|
|
1472
|
+
document.documentElement.addEventListener(
|
|
1473
|
+
"pointerleave",
|
|
1474
|
+
handlePointerLeave
|
|
1475
|
+
);
|
|
999
1476
|
window.addEventListener("scroll", handleScroll, {
|
|
1000
1477
|
capture: true,
|
|
1001
1478
|
passive: true
|
|
1002
1479
|
});
|
|
1480
|
+
window.addEventListener("resize", handleResize, { passive: true });
|
|
1003
1481
|
initialized = true;
|
|
1004
|
-
|
|
1482
|
+
debug5.log("stega controller started");
|
|
1005
1483
|
};
|
|
1006
1484
|
const stop = () => {
|
|
1007
1485
|
if (!initialized) return;
|
|
1486
|
+
initialized = false;
|
|
1008
1487
|
if (!tooltipEnabled) {
|
|
1009
1488
|
document.removeEventListener("click", handleClick, { capture: true });
|
|
1010
|
-
elements.
|
|
1489
|
+
elements.disconnect();
|
|
1011
1490
|
removeStegaStyles();
|
|
1012
|
-
|
|
1013
|
-
debug6.log("stega controller stopped (editor mode)");
|
|
1491
|
+
debug5.log("stega controller stopped (headless)");
|
|
1014
1492
|
return;
|
|
1015
1493
|
}
|
|
1016
|
-
if (
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1494
|
+
if (pointerFrame !== null) cancelAnimationFrame(pointerFrame);
|
|
1495
|
+
pointerFrame = null;
|
|
1496
|
+
pointerTarget = null;
|
|
1497
|
+
hitTestPending = false;
|
|
1498
|
+
document.removeEventListener("pointermove", handlePointer);
|
|
1499
|
+
document.removeEventListener("pointerdown", handlePointer);
|
|
1500
|
+
document.documentElement.removeEventListener(
|
|
1501
|
+
"pointerleave",
|
|
1502
|
+
handlePointerLeave
|
|
1503
|
+
);
|
|
1021
1504
|
window.removeEventListener("scroll", handleScroll, { capture: true });
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
tooltip.removeEventListener("mouseleave", handleTooltipMouseLeave);
|
|
1026
|
-
}
|
|
1027
|
-
overlay.cleanup();
|
|
1028
|
-
proximity.clearAllHighlights();
|
|
1029
|
-
proximity.stopObserving();
|
|
1030
|
-
elements.cleanup();
|
|
1505
|
+
window.removeEventListener("resize", handleResize);
|
|
1506
|
+
overlay.unmount();
|
|
1507
|
+
elements.disconnect();
|
|
1031
1508
|
removeStegaStyles();
|
|
1032
|
-
|
|
1033
|
-
debug6.log("stega controller stopped");
|
|
1509
|
+
debug5.log("stega controller stopped");
|
|
1034
1510
|
};
|
|
1035
1511
|
return { start, stop };
|
|
1036
1512
|
}
|
|
1037
1513
|
|
|
1514
|
+
// src/core/stega/scroll-to-field.ts
|
|
1515
|
+
var debug6 = createScopedLogger("stega:scroll");
|
|
1516
|
+
var FLASH_DURATION_MS = 1200;
|
|
1517
|
+
var flashTimeout = null;
|
|
1518
|
+
function scrollToField(request) {
|
|
1519
|
+
const { field, id } = request;
|
|
1520
|
+
if (!field) return false;
|
|
1521
|
+
const candidates = document.querySelectorAll(
|
|
1522
|
+
"[data-prepr-encoded]"
|
|
1523
|
+
);
|
|
1524
|
+
let target = null;
|
|
1525
|
+
for (const candidate of candidates) {
|
|
1526
|
+
if (candidate.getAttribute("data-prepr-field") !== field) continue;
|
|
1527
|
+
if (id && candidate.getAttribute("data-prepr-id") !== id) continue;
|
|
1528
|
+
target = candidate;
|
|
1529
|
+
break;
|
|
1530
|
+
}
|
|
1531
|
+
if (!target) {
|
|
1532
|
+
debug6.log("no element found for field", field);
|
|
1533
|
+
return false;
|
|
1534
|
+
}
|
|
1535
|
+
target.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1536
|
+
if (flashTimeout) clearTimeout(flashTimeout);
|
|
1537
|
+
document.querySelectorAll(".prepr-overlay-active").forEach((el) => el.classList.remove("prepr-overlay-active"));
|
|
1538
|
+
target.classList.add("prepr-overlay-active");
|
|
1539
|
+
flashTimeout = setTimeout(() => {
|
|
1540
|
+
target.classList.remove("prepr-overlay-active");
|
|
1541
|
+
flashTimeout = null;
|
|
1542
|
+
}, FLASH_DURATION_MS);
|
|
1543
|
+
return true;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1038
1546
|
// src/core/store.ts
|
|
1039
1547
|
var DEFAULT_STATE = {
|
|
1040
1548
|
locale: "en",
|
|
@@ -1169,7 +1677,7 @@ function splitPath(full) {
|
|
|
1169
1677
|
return [full.slice(0, index), full.slice(index + 1)];
|
|
1170
1678
|
}
|
|
1171
1679
|
|
|
1172
|
-
// ../../node_modules/.pnpm/preact@10.29.
|
|
1680
|
+
// ../../node_modules/.pnpm/preact@10.29.8/node_modules/preact/dist/preact.mjs
|
|
1173
1681
|
var n;
|
|
1174
1682
|
var l;
|
|
1175
1683
|
var u;
|
|
@@ -1240,8 +1748,8 @@ function H() {
|
|
|
1240
1748
|
}
|
|
1241
1749
|
}
|
|
1242
1750
|
function L(n2, l2, u3, t3, i2, r2, o2, e2, f3, c2, a2) {
|
|
1243
|
-
var s2, h2, p2, v2, y2, _2, g2
|
|
1244
|
-
for (f3 = T(u3, l2,
|
|
1751
|
+
var s2, h2, p2, v2, y2, _2, g2 = t3 && t3.__k || w, m2 = l2.length;
|
|
1752
|
+
for (f3 = T(u3, l2, g2, f3, m2), s2 = 0; s2 < m2; s2++) null != (p2 = u3.__k[s2]) && (h2 = -1 != p2.__i && g2[p2.__i] || d, p2.__i = s2, _2 = q(n2, p2, h2, i2, r2, o2, e2, f3, c2, a2), v2 = p2.__e, p2.ref && h2.ref != p2.ref && (h2.ref && J(h2.ref, null, p2), a2.push(p2.ref, p2.__c || v2, p2)), null == y2 && null != v2 && (y2 = v2), 4 & p2.__u ? (f3 = j(p2, f3, n2), h2.__e && (h2.__e = null)) : "function" == typeof p2.type && void 0 !== _2 ? f3 = _2 : v2 && (f3 = v2.nextSibling), p2.__u &= -7);
|
|
1245
1753
|
return u3.__e = y2, f3;
|
|
1246
1754
|
}
|
|
1247
1755
|
function T(n2, l2, u3, t3, i2) {
|
|
@@ -1250,13 +1758,13 @@ function T(n2, l2, u3, t3, i2) {
|
|
|
1250
1758
|
if (s2) for (r2 = 0; r2 < a2; r2++) null != (e2 = u3[r2]) && 0 == (2 & e2.__u) && (e2.__e == t3 && (t3 = $(e2)), K(e2, e2));
|
|
1251
1759
|
return t3;
|
|
1252
1760
|
}
|
|
1253
|
-
function j(n2, l2, u3
|
|
1254
|
-
var
|
|
1761
|
+
function j(n2, l2, u3) {
|
|
1762
|
+
var t3, i2;
|
|
1255
1763
|
if ("function" == typeof n2.type) {
|
|
1256
|
-
for (
|
|
1764
|
+
for (t3 = n2.__k, i2 = 0; t3 && i2 < t3.length; i2++) t3[i2] && (t3[i2].__ = n2, l2 = j(t3[i2], l2, u3));
|
|
1257
1765
|
return l2;
|
|
1258
1766
|
}
|
|
1259
|
-
n2.__e != l2 && (
|
|
1767
|
+
n2.__e != l2 && (l2 && n2.type && !l2.parentNode && (l2 = $(n2)), l2 = u3.insertBefore(n2.__e, l2 || null));
|
|
1260
1768
|
do {
|
|
1261
1769
|
l2 = l2 && l2.nextSibling;
|
|
1262
1770
|
} while (null != l2 && 8 == l2.nodeType);
|
|
@@ -1302,35 +1810,35 @@ function V(n2) {
|
|
|
1302
1810
|
};
|
|
1303
1811
|
}
|
|
1304
1812
|
function q(n2, u3, t3, i2, r2, o2, e2, f3, c2, a2) {
|
|
1305
|
-
var s2, h2, p2, v2, y2, d2, _2, k2, x2, M,
|
|
1813
|
+
var s2, h2, p2, v2, y2, d2, _2, k2, x2, M, I2, P2, A2, H2, T2, j2, F = u3.type;
|
|
1306
1814
|
if (void 0 !== u3.constructor) return null;
|
|
1307
1815
|
128 & t3.__u && (c2 = !!(32 & t3.__u), o2 = [f3 = u3.__e = t3.__e]), (s2 = l.__b) && s2(u3);
|
|
1308
|
-
n: if ("function" == typeof
|
|
1816
|
+
n: if ("function" == typeof F) {
|
|
1309
1817
|
h2 = e2.length;
|
|
1310
1818
|
try {
|
|
1311
|
-
if (x2 = u3.props, M =
|
|
1819
|
+
if (x2 = u3.props, M = F.prototype && F.prototype.render, I2 = (s2 = F.contextType) && i2[s2.__c], P2 = s2 ? I2 ? I2.props.value : s2.__ : i2, t3.__c ? k2 = (p2 = u3.__c = t3.__c).__ = p2.__E : (M ? u3.__c = p2 = new F(x2, P2) : (u3.__c = p2 = new C(x2, P2), p2.constructor = F, p2.render = Q), I2 && I2.sub(p2), p2.state || (p2.state = {}), p2.__n = i2, v2 = p2.__d = true, p2.__h = [], p2._sb = []), M && null == p2.__s && (p2.__s = p2.state), M && null != F.getDerivedStateFromProps && (p2.__s == p2.state && (p2.__s = m({}, p2.__s)), m(p2.__s, F.getDerivedStateFromProps(x2, p2.__s))), y2 = p2.props, d2 = p2.state, p2.__v = u3, v2) M && null == F.getDerivedStateFromProps && null != p2.componentWillMount && p2.componentWillMount(), M && null != p2.componentDidMount && p2.__h.push(p2.componentDidMount);
|
|
1312
1820
|
else {
|
|
1313
|
-
if (M && null ==
|
|
1821
|
+
if (M && null == F.getDerivedStateFromProps && x2 !== y2 && null != p2.componentWillReceiveProps && p2.componentWillReceiveProps(x2, P2), u3.__v == t3.__v || !p2.__e && null != p2.shouldComponentUpdate && false === p2.shouldComponentUpdate(x2, p2.__s, P2)) {
|
|
1314
1822
|
u3.__v != t3.__v && (p2.props = x2, p2.state = p2.__s, p2.__d = false), u3.__e = t3.__e, u3.__k = t3.__k, u3.__k.some(function(n3) {
|
|
1315
1823
|
n3 && (n3.__ = u3);
|
|
1316
|
-
}), w.push.apply(p2.__h, p2._sb), p2._sb = [], p2.__h.length && e2.push(p2);
|
|
1824
|
+
}), w.push.apply(p2.__h, p2._sb), p2._sb = [], p2.__h.length && e2.push(p2), f3 = $(t3);
|
|
1317
1825
|
break n;
|
|
1318
1826
|
}
|
|
1319
|
-
null != p2.componentWillUpdate && p2.componentWillUpdate(x2, p2.__s,
|
|
1827
|
+
null != p2.componentWillUpdate && p2.componentWillUpdate(x2, p2.__s, P2), M && null != p2.componentDidUpdate && p2.__h.push(function() {
|
|
1320
1828
|
p2.componentDidUpdate(y2, d2, _2);
|
|
1321
1829
|
});
|
|
1322
1830
|
}
|
|
1323
|
-
if (p2.context =
|
|
1831
|
+
if (p2.context = P2, p2.props = x2, p2.__P = n2, p2.__e = false, A2 = l.__r, H2 = 0, M) p2.state = p2.__s, p2.__d = false, A2 && A2(u3), s2 = p2.render(p2.props, p2.state, p2.context), w.push.apply(p2.__h, p2._sb), p2._sb = [];
|
|
1324
1832
|
else do {
|
|
1325
|
-
p2.__d = false,
|
|
1326
|
-
} while (p2.__d && ++
|
|
1327
|
-
p2.state = p2.__s, null != p2.getChildContext && (i2 = m(m({}, i2), p2.getChildContext())), M && !v2 && null != p2.getSnapshotBeforeUpdate && (_2 = p2.getSnapshotBeforeUpdate(y2, d2)),
|
|
1833
|
+
p2.__d = false, A2 && A2(u3), s2 = p2.render(p2.props, p2.state, p2.context), p2.state = p2.__s;
|
|
1834
|
+
} while (p2.__d && ++H2 < 25);
|
|
1835
|
+
p2.state = p2.__s, null != p2.getChildContext && (i2 = m(m({}, i2), p2.getChildContext())), M && !v2 && null != p2.getSnapshotBeforeUpdate && (_2 = p2.getSnapshotBeforeUpdate(y2, d2)), T2 = null != s2 && s2.type === S && null == s2.key ? E(s2.props.children) : s2, f3 = L(n2, g(T2) ? T2 : [T2], u3, t3, i2, r2, o2, e2, f3, c2, a2), p2.base = u3.__e, u3.__u &= -161, p2.__h.length && e2.push(p2), k2 && (p2.__E = p2.__ = null);
|
|
1328
1836
|
} catch (n3) {
|
|
1329
1837
|
if (e2.length = h2, u3.__v = null, c2 || null != o2) {
|
|
1330
1838
|
if (n3.then) {
|
|
1331
1839
|
for (u3.__u |= c2 ? 160 : 128; f3 && 8 == f3.nodeType && f3.nextSibling; ) f3 = f3.nextSibling;
|
|
1332
1840
|
null != o2 && (o2[o2.indexOf(f3)] = null), u3.__e = f3;
|
|
1333
|
-
} else if (null != o2) for (
|
|
1841
|
+
} else if (null != o2) for (j2 = o2.length; j2--; ) b(o2[j2]);
|
|
1334
1842
|
} else u3.__e = t3.__e;
|
|
1335
1843
|
null == u3.__k && (u3.__k = t3.__k || []), n3.then || B(u3), l.__e(n3, u3, t3);
|
|
1336
1844
|
}
|
|
@@ -1451,9 +1959,9 @@ var CLOSE_ICON_SVG = `<svg width="10" height="10" viewBox="0 0 10 10" fill="none
|
|
|
1451
1959
|
</svg>`;
|
|
1452
1960
|
|
|
1453
1961
|
// src/core/ui/toolbar.generated.ts
|
|
1454
|
-
var TOOLBAR_CSS = ":host{--prepr-primary:#4338ca;--prepr-bg:#eef2ff;--prepr-text:#1f2937;--prepr-radius:8px;--prepr-shadow:0px 0px 40px 0px #1f29373d;--prepr-z-index:10000;--prepr-primary-50:#eef2ff;--prepr-primary-100:#e0e7ff;--prepr-primary-800:#3730a3;--prepr-indigo-600:#4f46e5;--prepr-indigo-50:#eef2ff;--prepr-indigo-700:#4338ca;--prepr-secondary-400:#fb923c;--prepr-secondary-500:#f97316;--prepr-gray-100:#f3f4f6;--prepr-gray-200:#e5e7eb;--prepr-gray-300:#d1d5db;--prepr-gray-400:#9ca3af;--prepr-gray-500:#6b7280;--prepr-gray-700:#374151;--prepr-gray-800:#1f2937;--prepr-gray-900:#111827;--prepr-shadow-sm:0px 0px 12px 0px #c3c3c3cc;color:var(--prepr-text);box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.5}:host *,:host :before,:host :after,*,:before,:after{box-sizing:border-box}button{font-family:inherit;font-size:inherit;margin:0}.prepr-regular-text{font-size:.875rem;font-weight:500;line-height:1.5}.prepr-backdrop{z-index:calc(var(--prepr-z-index) - 1);opacity:0;pointer-events:none;background:#0000001a;transition:opacity .2s ease-in-out;position:fixed;top:0;bottom:0;left:0;right:0}.prepr-backdrop[data-open=true]{opacity:1;pointer-events:auto;animation:.2s ease-in-out prepr-fade-in}.prepr-container{z-index:var(--prepr-z-index);justify-content:flex-end;align-items:center;display:flex;position:fixed;top:calc(50% - 64px);right:0}.prepr-toggle-holder{padding:.5rem}.prepr-toggle{cursor:pointer;background:var(--prepr-primary);color:#fff;border:none;border-radius:9999px;justify-content:center;align-items:center;width:2.25rem;height:2.25rem;display:flex}.prepr-toggle>span{display:flex}.prepr-toggle svg{color:#fff}.prepr-panel{z-index:var(--prepr-z-index);opacity:0;pointer-events:none;justify-content:flex-end;padding:.75rem;transition:opacity .2s ease-in-out;display:flex;position:fixed;top:50%;right:0;transform:translateY(-50%)}.prepr-panel[data-open=true]{opacity:1;pointer-events:auto;animation:.2s ease-in-out prepr-fade-in}.prepr-content{box-shadow:var(--prepr-shadow);border-radius:var(--prepr-radius);background:var(--prepr-bg);flex-direction:column;gap:2.5rem;width:100%;padding:1.5rem;display:flex}@media (min-width:640px){.prepr-panel{margin-right:4rem;padding:0}.prepr-content{width:502px;padding:2.5rem}}.prepr-header{justify-content:space-between;align-items:center;gap:.5rem;display:flex}.prepr-header-left{align-items:flex-start;gap:.75rem;display:flex}.prepr-badge{background:var(--prepr-primary-100);height:1.5rem;color:var(--prepr-primary);border-radius:4px;align-items:center;padding:.25rem .75rem;font-size:.875rem;display:flex}.prepr-header-close{cursor:pointer;background:var(--prepr-primary-100);width:2rem;height:2rem;color:var(--prepr-gray-700);border:none;border-radius:6px;justify-content:center;align-items:center;display:flex}.prepr-section{flex-direction:column;gap:.5rem;display:flex}.prepr-section-label{color:var(--prepr-gray-400);font-size:.875rem}.prepr-row{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:.5rem 1.5rem;display:flex}.prepr-row-title{color:var(--prepr-gray-800);margin:0;font-size:1rem;font-weight:600}.prepr-radiogroup{border-radius:var(--prepr-radius);border:1px solid var(--prepr-gray-300);background:#fff;align-items:center;gap:.25rem;height:2.5rem;padding:.25rem;display:flex}.prepr-radiogroup[data-disabled=true]{cursor:not-allowed;opacity:.5}.prepr-radiogroup[data-disabled=true] .prepr-radio{pointer-events:none}.prepr-radio{cursor:pointer;white-space:nowrap;text-align:center;height:2rem;color:var(--prepr-gray-900);background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:.5rem 1.125rem;font-size:.875rem;font-weight:500;transition:all .2s ease-in-out;display:flex}.prepr-radio[data-checked=true]{background:var(--prepr-indigo-600);color:#fff;box-shadow:var(--prepr-shadow-sm)}.prepr-radio[data-checked=true][data-off=true]{background:var(--prepr-gray-800);color:#fff}.prepr-radio-variant{width:82px}.prepr-listbox{position:relative}.prepr-segment-button{border-radius:var(--prepr-radius);border:1px solid var(--prepr-gray-300);width:240px;height:2.5rem;color:var(--prepr-gray-500);cursor:pointer;background:#fff;flex-shrink:0;align-items:center;gap:.5rem;padding:0 .5rem;font-size:.875rem;font-weight:500;display:flex;overflow:hidden}@media (min-width:768px){.prepr-segment-button{padding:0 1rem}}.prepr-segment-button:disabled{cursor:not-allowed;background:var(--prepr-gray-200);color:var(--prepr-gray-400)}.prepr-segment-button[aria-expanded=true]{border-bottom-color:#fff;border-bottom-right-radius:0;border-bottom-left-radius:0}.prepr-segment-label{white-space:nowrap;text-overflow:ellipsis;text-align:left;width:100%;margin-right:auto;overflow:hidden}.prepr-segment-caret{color:var(--prepr-gray-800);display:flex}.prepr-options{z-index:calc(var(--prepr-z-index) + 1);border-left:1px solid var(--prepr-gray-300);border-right:1px solid var(--prepr-gray-300);border-bottom:1px solid var(--prepr-gray-300);-ms-overflow-style:none;scrollbar-width:none;background:#fff;border-radius:0 0 6px 6px;width:240px;max-height:300px;margin:0;padding:0;list-style:none;position:absolute;top:100%;left:0;overflow-y:auto;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.prepr-options::-webkit-scrollbar{display:none}.prepr-options[hidden]{display:none}.prepr-option-search{background:#fff;padding:.5rem;position
|
|
1962
|
+
var TOOLBAR_CSS = ":host{--prepr-primary:#4338ca;--prepr-bg:#eef2ff;--prepr-text:#1f2937;--prepr-radius:8px;--prepr-shadow:0px 0px 40px 0px #1f29373d;--prepr-z-index:10000;--prepr-primary-50:#eef2ff;--prepr-primary-100:#e0e7ff;--prepr-primary-800:#3730a3;--prepr-indigo-600:#4f46e5;--prepr-indigo-50:#eef2ff;--prepr-indigo-700:#4338ca;--prepr-secondary-400:#fb923c;--prepr-secondary-500:#f97316;--prepr-gray-100:#f3f4f6;--prepr-gray-200:#e5e7eb;--prepr-gray-300:#d1d5db;--prepr-gray-400:#9ca3af;--prepr-gray-500:#6b7280;--prepr-gray-700:#374151;--prepr-gray-800:#1f2937;--prepr-gray-900:#111827;--prepr-shadow-sm:0px 0px 12px 0px #c3c3c3cc;color:var(--prepr-text);box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.5}:host *,:host :before,:host :after,*,:before,:after{box-sizing:border-box}button{font-family:inherit;font-size:inherit;margin:0}.prepr-regular-text{font-size:.875rem;font-weight:500;line-height:1.5}.prepr-backdrop{z-index:calc(var(--prepr-z-index) - 1);opacity:0;pointer-events:none;background:#0000001a;transition:opacity .2s ease-in-out;position:fixed;top:0;bottom:0;left:0;right:0}.prepr-backdrop[data-open=true]{opacity:1;pointer-events:auto;animation:.2s ease-in-out prepr-fade-in}.prepr-container{z-index:var(--prepr-z-index);justify-content:flex-end;align-items:center;display:flex;position:fixed;top:calc(50% - 64px);right:0}.prepr-toggle-holder{padding:.5rem}.prepr-toggle{cursor:pointer;background:var(--prepr-primary);color:#fff;border:none;border-radius:9999px;justify-content:center;align-items:center;width:2.25rem;height:2.25rem;display:flex}.prepr-toggle>span{display:flex}.prepr-toggle svg{color:#fff}.prepr-panel{z-index:var(--prepr-z-index);opacity:0;pointer-events:none;justify-content:flex-end;padding:.75rem;transition:opacity .2s ease-in-out;display:flex;position:fixed;top:50%;right:0;transform:translateY(-50%)}.prepr-panel[data-open=true]{opacity:1;pointer-events:auto;animation:.2s ease-in-out prepr-fade-in}.prepr-content{box-shadow:var(--prepr-shadow);border-radius:var(--prepr-radius);background:var(--prepr-bg);flex-direction:column;gap:2.5rem;width:100%;padding:1.5rem;display:flex}@media (min-width:640px){.prepr-panel{margin-right:4rem;padding:0}.prepr-content{width:502px;padding:2.5rem}}.prepr-header{justify-content:space-between;align-items:center;gap:.5rem;display:flex}.prepr-header-left{align-items:flex-start;gap:.75rem;display:flex}.prepr-badge{background:var(--prepr-primary-100);height:1.5rem;color:var(--prepr-primary);border-radius:4px;align-items:center;padding:.25rem .75rem;font-size:.875rem;display:flex}.prepr-header-close{cursor:pointer;background:var(--prepr-primary-100);width:2rem;height:2rem;color:var(--prepr-gray-700);border:none;border-radius:6px;justify-content:center;align-items:center;display:flex}.prepr-section{flex-direction:column;gap:.5rem;display:flex}.prepr-section-label{color:var(--prepr-gray-400);font-size:.875rem}.prepr-row{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:.5rem 1.5rem;display:flex}.prepr-row-title{color:var(--prepr-gray-800);margin:0;font-size:1rem;font-weight:600}.prepr-radiogroup{border-radius:var(--prepr-radius);border:1px solid var(--prepr-gray-300);background:#fff;align-items:center;gap:.25rem;height:2.5rem;padding:.25rem;display:flex}.prepr-radiogroup[data-disabled=true]{cursor:not-allowed;opacity:.5}.prepr-radiogroup[data-disabled=true] .prepr-radio{pointer-events:none}.prepr-radio{cursor:pointer;white-space:nowrap;text-align:center;height:2rem;color:var(--prepr-gray-900);background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:.5rem 1.125rem;font-size:.875rem;font-weight:500;transition:all .2s ease-in-out;display:flex}.prepr-radio[data-checked=true]{background:var(--prepr-indigo-600);color:#fff;box-shadow:var(--prepr-shadow-sm)}.prepr-radio[data-checked=true][data-off=true]{background:var(--prepr-gray-800);color:#fff}.prepr-radio-variant{width:82px}.prepr-listbox{position:relative}.prepr-segment-button{border-radius:var(--prepr-radius);border:1px solid var(--prepr-gray-300);width:240px;height:2.5rem;color:var(--prepr-gray-500);cursor:pointer;background:#fff;flex-shrink:0;align-items:center;gap:.5rem;padding:0 .5rem;font-size:.875rem;font-weight:500;display:flex;overflow:hidden}@media (min-width:768px){.prepr-segment-button{padding:0 1rem}}.prepr-segment-button:disabled{cursor:not-allowed;background:var(--prepr-gray-200);color:var(--prepr-gray-400)}.prepr-segment-button[aria-expanded=true]{border-bottom-color:#fff;border-bottom-right-radius:0;border-bottom-left-radius:0}.prepr-segment-label{white-space:nowrap;text-overflow:ellipsis;text-align:left;width:100%;margin-right:auto;overflow:hidden}.prepr-segment-caret{color:var(--prepr-gray-800);display:flex}.prepr-options{z-index:calc(var(--prepr-z-index) + 1);border-left:1px solid var(--prepr-gray-300);border-right:1px solid var(--prepr-gray-300);border-bottom:1px solid var(--prepr-gray-300);-ms-overflow-style:none;scrollbar-width:none;background:#fff;border-radius:0 0 6px 6px;width:240px;max-height:300px;margin:0;padding:0;list-style:none;position:absolute;top:100%;left:0;overflow-y:auto;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.prepr-options::-webkit-scrollbar{display:none}.prepr-options[hidden]{display:none}.prepr-option-search{background:#fff;padding:.5rem;position:sticky;top:0}.prepr-segment-search{border:1px solid var(--prepr-gray-300);border-radius:var(--prepr-radius);width:100%;height:2rem;font:inherit;color:var(--prepr-gray-900);box-sizing:border-box;background:#fff;padding:0 .5rem;font-size:.875rem}@media (pointer:coarse){.prepr-segment-search{font-size:16px}}.prepr-segment-search::placeholder{color:var(--prepr-gray-400)}.prepr-segment-search:focus{border-color:var(--prepr-gray-400);outline:none}.prepr-option{cursor:pointer;width:100%;color:var(--prepr-gray-900);background:#fff;align-items:center;padding:.5rem 1rem;font-size:.875rem;font-weight:500;display:flex}.prepr-option:hover{background:var(--prepr-gray-100)}.prepr-option[aria-selected=true]{background:var(--prepr-indigo-50);color:var(--prepr-indigo-700)}.prepr-option-label{white-space:nowrap;text-overflow:ellipsis;text-align:left;width:100%;margin-right:auto;overflow:hidden}.prepr-reset{background:var(--prepr-secondary-400);color:#fff;cursor:pointer;border:none;border-radius:6px;justify-content:center;align-items:center;gap:.5rem;width:100%;height:2.5rem;padding:.5rem .75rem;font-size:.875rem;font-weight:500;display:flex}.prepr-reset:hover:not(:disabled){background:var(--prepr-secondary-500)}.prepr-reset:disabled{background:var(--prepr-gray-400);color:var(--prepr-gray-500);cursor:not-allowed}@media (min-width:768px){.prepr-reset{width:108px}}.prepr-indicators{z-index:var(--prepr-z-index);gap:.75rem;display:flex;position:fixed;bottom:1.5rem;right:1.5rem}.prepr-status-pill{background:var(--prepr-primary);color:#fff;cursor:pointer;border:none;border-radius:9999px;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.75rem;font-weight:500;transition:background-color .15s;display:flex;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.prepr-status-pill[data-interactive=false]{cursor:default;opacity:1;color:#fff}.prepr-status-pill[data-interactive=true]:hover{background:var(--prepr-primary-800)}.prepr-status-pill[hidden]{display:none}.prepr-status-viewing{color:#fff9;font-size:10px}.prepr-status-segment{text-overflow:ellipsis;white-space:nowrap;max-width:120px;display:inline-block;overflow:hidden}.prepr-status-variant{text-overflow:ellipsis;white-space:nowrap;background:#fff3;border-radius:4px;max-width:80px;padding:0 .5rem;display:inline-block;overflow:hidden}.prepr-status-x{color:#fff;width:.75rem;height:.75rem}.prepr-close-edit-pill{background:var(--prepr-primary-50);color:var(--prepr-gray-800);cursor:pointer;border:none;border-radius:9999px;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.75rem;font-weight:500;transition:background-color .15s;display:flex;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.prepr-close-edit-pill:hover{background:var(--prepr-primary-100)}.prepr-close-edit-pill[hidden]{display:none}.prepr-close-edit-x{width:1rem;height:1rem;color:var(--prepr-gray-500);transition:color .15s}.prepr-close-edit-pill:hover .prepr-close-edit-x{color:var(--prepr-gray-700)}.prepr-tip{z-index:calc(var(--prepr-z-index) + 2);overflow-wrap:break-word;background:var(--prepr-gray-900);color:#fff;pointer-events:none;border-radius:6px;width:auto;max-width:min(420px,100vw - 16px);padding:.5rem .75rem;font-size:.75rem;font-weight:500;position:fixed;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.prepr-tip-arrow{border-left:6px solid #0000;border-right:6px solid #0000;border-top:6px solid var(--prepr-gray-900);width:0;height:0;position:absolute;top:100%;transform:translate(-50%)}[hidden]{display:none!important}@keyframes prepr-fade-in{0%{opacity:0}to{opacity:1}}";
|
|
1455
1963
|
|
|
1456
|
-
// ../../node_modules/.pnpm/preact@10.29.
|
|
1964
|
+
// ../../node_modules/.pnpm/preact@10.29.8/node_modules/preact/jsx-runtime/dist/jsxRuntime.mjs
|
|
1457
1965
|
var f2 = 0;
|
|
1458
1966
|
function u2(e2, t3, n2, o2, i2, u3) {
|
|
1459
1967
|
t3 || (t3 = {});
|
|
@@ -2146,38 +2654,772 @@ function definePreprToolbar() {
|
|
|
2146
2654
|
}
|
|
2147
2655
|
}
|
|
2148
2656
|
|
|
2149
|
-
// src/core/
|
|
2150
|
-
var
|
|
2151
|
-
var
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2657
|
+
// src/core/visual-editor/asset-images.ts
|
|
2658
|
+
var MAX_URL_LENGTH = 4096;
|
|
2659
|
+
var MIN_KEY_SEGMENTS = 2;
|
|
2660
|
+
var TRANSFORM_OPTION = /^[a-z]{1,3}_[\w.-]*$/i;
|
|
2661
|
+
var FORMAT_PLACEHOLDER = "{format}";
|
|
2662
|
+
var PROXY_PARAMS = ["url", "href", "src", "image"];
|
|
2663
|
+
var ABSOLUTE_HTTP = /^https?:\/\//i;
|
|
2664
|
+
var SOURCE_URL_ATTRIBUTES = ["src", "data-src"];
|
|
2665
|
+
var SOURCE_SET_ATTRIBUTES = ["srcset", "data-srcset"];
|
|
2666
|
+
var ASSET_ATTR = "data-prepr-asset";
|
|
2667
|
+
var ASSET_SOURCE_ATTRIBUTES = [
|
|
2668
|
+
...SOURCE_URL_ATTRIBUTES,
|
|
2669
|
+
...SOURCE_SET_ATTRIBUTES
|
|
2670
|
+
];
|
|
2671
|
+
var TAG_ATTRIBUTES = [
|
|
2672
|
+
"data-prepr-encoded",
|
|
2673
|
+
"data-prepr-id",
|
|
2674
|
+
"data-prepr-field",
|
|
2675
|
+
"data-prepr-entry-id",
|
|
2676
|
+
ASSET_ATTR
|
|
2677
|
+
];
|
|
2678
|
+
function decodeSegment(segment) {
|
|
2679
|
+
try {
|
|
2680
|
+
return decodeURIComponent(segment);
|
|
2681
|
+
} catch {
|
|
2682
|
+
return segment;
|
|
2683
|
+
}
|
|
2157
2684
|
}
|
|
2158
|
-
function
|
|
2685
|
+
function isTransformSegment(segment) {
|
|
2686
|
+
return segment === FORMAT_PLACEHOLDER || segment.split(",").every((option) => TRANSFORM_OPTION.test(option));
|
|
2687
|
+
}
|
|
2688
|
+
function assetKey(url, depth = 0) {
|
|
2689
|
+
if (!url || url.length > MAX_URL_LENGTH) return null;
|
|
2690
|
+
let parsed;
|
|
2159
2691
|
try {
|
|
2160
|
-
|
|
2161
|
-
return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
|
|
2692
|
+
parsed = new URL(url, "https://base.invalid");
|
|
2162
2693
|
} catch {
|
|
2163
2694
|
return null;
|
|
2164
2695
|
}
|
|
2696
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
|
|
2697
|
+
if (depth === 0) {
|
|
2698
|
+
for (const name of PROXY_PARAMS) {
|
|
2699
|
+
const inner = parsed.searchParams.get(name);
|
|
2700
|
+
if (inner && ABSOLUTE_HTTP.test(inner)) return assetKey(inner, 1);
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
const segments = parsed.pathname.split("/").filter(Boolean).map(decodeSegment);
|
|
2704
|
+
const file = segments.pop();
|
|
2705
|
+
if (!file) return null;
|
|
2706
|
+
const kept = segments.filter((segment) => !isTransformSegment(segment));
|
|
2707
|
+
kept.push(file);
|
|
2708
|
+
return kept.length >= MIN_KEY_SEGMENTS ? kept.join("/") : null;
|
|
2165
2709
|
}
|
|
2166
|
-
function
|
|
2167
|
-
|
|
2710
|
+
function srcsetUrls(srcset) {
|
|
2711
|
+
const urls = [];
|
|
2712
|
+
let expectUrl = true;
|
|
2713
|
+
for (const token of srcset.split(/\s+/)) {
|
|
2714
|
+
if (!token) continue;
|
|
2715
|
+
if (!expectUrl) {
|
|
2716
|
+
const comma = token.indexOf(",");
|
|
2717
|
+
if (comma === -1) continue;
|
|
2718
|
+
expectUrl = true;
|
|
2719
|
+
const rest = token.slice(comma + 1);
|
|
2720
|
+
if (!rest) continue;
|
|
2721
|
+
urls.push(rest.replace(/,+$/, ""));
|
|
2722
|
+
expectUrl = rest.endsWith(",");
|
|
2723
|
+
continue;
|
|
2724
|
+
}
|
|
2725
|
+
const url = token.replace(/^,+|,+$/g, "");
|
|
2726
|
+
if (url) urls.push(url);
|
|
2727
|
+
expectUrl = token.endsWith(",");
|
|
2728
|
+
}
|
|
2729
|
+
return urls;
|
|
2168
2730
|
}
|
|
2169
|
-
function
|
|
2170
|
-
const
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
const match = candidates.filter(Boolean).map((l2) => l2.toLowerCase().split("-")[0]).find(isLocale);
|
|
2175
|
-
if (match) return match;
|
|
2731
|
+
function createAssetIndex(assets) {
|
|
2732
|
+
const index = /* @__PURE__ */ new Map();
|
|
2733
|
+
for (const asset of assets) {
|
|
2734
|
+
const key = assetKey(asset.key);
|
|
2735
|
+
if (key && !index.has(key)) index.set(key, asset);
|
|
2176
2736
|
}
|
|
2177
|
-
return
|
|
2737
|
+
return index;
|
|
2178
2738
|
}
|
|
2179
|
-
function
|
|
2180
|
-
const
|
|
2739
|
+
function urlsOf(img) {
|
|
2740
|
+
const urls = [];
|
|
2741
|
+
const collect = (el) => {
|
|
2742
|
+
for (const name of SOURCE_URL_ATTRIBUTES) {
|
|
2743
|
+
const value = el.getAttribute(name);
|
|
2744
|
+
if (value) urls.push(value);
|
|
2745
|
+
}
|
|
2746
|
+
for (const name of SOURCE_SET_ATTRIBUTES) {
|
|
2747
|
+
const value = el.getAttribute(name);
|
|
2748
|
+
if (value) urls.push(...srcsetUrls(value));
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
collect(img);
|
|
2752
|
+
const picture = img.parentElement;
|
|
2753
|
+
if (picture?.tagName === "PICTURE") {
|
|
2754
|
+
for (const source of picture.children) {
|
|
2755
|
+
if (source.tagName === "SOURCE") collect(source);
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
return urls;
|
|
2759
|
+
}
|
|
2760
|
+
function matchAsset(img, index, keys) {
|
|
2761
|
+
for (const url of urlsOf(img)) {
|
|
2762
|
+
let key = keys.get(url);
|
|
2763
|
+
if (key === void 0) {
|
|
2764
|
+
key = assetKey(url);
|
|
2765
|
+
keys.set(url, key);
|
|
2766
|
+
}
|
|
2767
|
+
const asset = key ? index.get(key) : void 0;
|
|
2768
|
+
if (asset) return asset;
|
|
2769
|
+
}
|
|
2770
|
+
return void 0;
|
|
2771
|
+
}
|
|
2772
|
+
function setIfChanged(el, name, value) {
|
|
2773
|
+
if (value === null) {
|
|
2774
|
+
if (el.hasAttribute(name)) el.removeAttribute(name);
|
|
2775
|
+
} else if (el.getAttribute(name) !== value) {
|
|
2776
|
+
el.setAttribute(name, value);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
function untagAssetImage(img) {
|
|
2780
|
+
for (const name of TAG_ATTRIBUTES) img.removeAttribute(name);
|
|
2781
|
+
}
|
|
2782
|
+
var TEXT_FIELD_SELECTOR = `[data-prepr-encoded]:not([${ASSET_ATTR}])`;
|
|
2783
|
+
function besideOtherItem(img, isCurrentItem) {
|
|
2784
|
+
for (let block = img.parentElement; block && block !== document.body; block = block.parentElement) {
|
|
2785
|
+
const texts = block.querySelectorAll(TEXT_FIELD_SELECTOR);
|
|
2786
|
+
if (texts.length === 0) continue;
|
|
2787
|
+
for (const text of texts) {
|
|
2788
|
+
if (isCurrentItem(text)) return false;
|
|
2789
|
+
}
|
|
2790
|
+
return true;
|
|
2791
|
+
}
|
|
2792
|
+
return false;
|
|
2793
|
+
}
|
|
2794
|
+
function tagAssetImages(index, itemId, keys, options = {}) {
|
|
2795
|
+
const { isCurrentItem } = options;
|
|
2796
|
+
for (const img of document.getElementsByTagName("img")) {
|
|
2797
|
+
const ours = img.hasAttribute(ASSET_ATTR);
|
|
2798
|
+
if (!ours && img.hasAttribute("data-prepr-encoded")) continue;
|
|
2799
|
+
const match = index.size ? matchAsset(img, index, keys) : void 0;
|
|
2800
|
+
const asset = match && isCurrentItem && besideOtherItem(img, isCurrentItem) ? void 0 : match;
|
|
2801
|
+
if (!asset) {
|
|
2802
|
+
if (ours) untagAssetImage(img);
|
|
2803
|
+
continue;
|
|
2804
|
+
}
|
|
2805
|
+
setIfChanged(img, "data-prepr-encoded", "");
|
|
2806
|
+
setIfChanged(img, ASSET_ATTR, "");
|
|
2807
|
+
setIfChanged(img, "data-prepr-id", itemId);
|
|
2808
|
+
setIfChanged(img, "data-prepr-field", asset.field);
|
|
2809
|
+
setIfChanged(img, "data-prepr-entry-id", asset.entryId ?? null);
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
function untagAssetImages() {
|
|
2813
|
+
document.querySelectorAll(`img[${ASSET_ATTR}]`).forEach(untagAssetImage);
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
// src/core/visual-editor/navigation-guard.ts
|
|
2817
|
+
var debug7 = createScopedLogger("navigation-guard");
|
|
2818
|
+
var trimSlash = (path) => path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
|
|
2819
|
+
function resolve(url) {
|
|
2820
|
+
try {
|
|
2821
|
+
return new URL(url, document.baseURI);
|
|
2822
|
+
} catch {
|
|
2823
|
+
return null;
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
function leavesPage(url) {
|
|
2827
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
2828
|
+
const here = window.location;
|
|
2829
|
+
return url.origin !== here.origin || trimSlash(url.pathname) !== trimSlash(here.pathname);
|
|
2830
|
+
}
|
|
2831
|
+
function leavingLinkHref(target) {
|
|
2832
|
+
if (!(target instanceof Element)) return null;
|
|
2833
|
+
const link = target.closest("a[href]");
|
|
2834
|
+
if (!link || link.hasAttribute("download")) return null;
|
|
2835
|
+
const linkTarget = link.getAttribute("target");
|
|
2836
|
+
if (linkTarget && linkTarget !== "_self") return null;
|
|
2837
|
+
const href = link.getAttribute("href") ?? "";
|
|
2838
|
+
if (href.startsWith("#")) return null;
|
|
2839
|
+
const url = resolve(href);
|
|
2840
|
+
return url && leavesPage(url) ? url.href : null;
|
|
2841
|
+
}
|
|
2842
|
+
function submitterAttr(submitter, attr) {
|
|
2843
|
+
if (!submitter?.hasAttribute(attr)) return null;
|
|
2844
|
+
if (attr === "formaction") return submitter.formAction;
|
|
2845
|
+
if (attr === "formmethod") return submitter.formMethod;
|
|
2846
|
+
return submitter.formTarget;
|
|
2847
|
+
}
|
|
2848
|
+
function submissionUrl(form, submitter) {
|
|
2849
|
+
const method = (submitterAttr(submitter, "formmethod") ?? form.method).toLowerCase();
|
|
2850
|
+
if (method === "dialog") return null;
|
|
2851
|
+
const target = submitterAttr(submitter, "formtarget") ?? form.target;
|
|
2852
|
+
if (target && target !== "_self") return null;
|
|
2853
|
+
const url = resolve(
|
|
2854
|
+
submitterAttr(submitter, "formaction") ?? (form.action || location.href)
|
|
2855
|
+
);
|
|
2856
|
+
if (!url) return null;
|
|
2857
|
+
if (method === "get") {
|
|
2858
|
+
let data;
|
|
2859
|
+
try {
|
|
2860
|
+
data = new FormData(form, submitter);
|
|
2861
|
+
} catch {
|
|
2862
|
+
data = new FormData(form);
|
|
2863
|
+
}
|
|
2864
|
+
const query = new URLSearchParams();
|
|
2865
|
+
data.forEach((value, key) => {
|
|
2866
|
+
query.append(key, typeof value === "string" ? value : value.name);
|
|
2867
|
+
});
|
|
2868
|
+
url.search = query.toString();
|
|
2869
|
+
}
|
|
2870
|
+
return url;
|
|
2871
|
+
}
|
|
2872
|
+
function createNavigationGuard(opts) {
|
|
2873
|
+
let active = false;
|
|
2874
|
+
let permitted = null;
|
|
2875
|
+
let pendingForm = null;
|
|
2876
|
+
let originalPush = null;
|
|
2877
|
+
let guardedPush = null;
|
|
2878
|
+
const isPermitted = (url) => permitted === url.href;
|
|
2879
|
+
const guarding = () => active && opts.showsEditedContent();
|
|
2880
|
+
const report = (href) => {
|
|
2881
|
+
debug7.log("blocked navigation to", href);
|
|
2882
|
+
opts.onBlocked(href);
|
|
2883
|
+
};
|
|
2884
|
+
const onSubmit = (e2) => {
|
|
2885
|
+
if (e2.defaultPrevented || !(e2.target instanceof HTMLFormElement)) return;
|
|
2886
|
+
if (!guarding()) return;
|
|
2887
|
+
const submitter = e2.submitter ?? null;
|
|
2888
|
+
const url = submissionUrl(e2.target, submitter);
|
|
2889
|
+
if (!url || !leavesPage(url) || isPermitted(url)) return;
|
|
2890
|
+
e2.preventDefault();
|
|
2891
|
+
pendingForm = { form: e2.target, submitter, href: url.href };
|
|
2892
|
+
report(url.href);
|
|
2893
|
+
};
|
|
2894
|
+
const onNavigate = (event) => {
|
|
2895
|
+
const e2 = event;
|
|
2896
|
+
if (e2.navigationType === "traverse" || e2.navigationType === "reload") {
|
|
2897
|
+
return;
|
|
2898
|
+
}
|
|
2899
|
+
if (!e2.cancelable || e2.hashChange || e2.downloadRequest !== null) return;
|
|
2900
|
+
if (!guarding()) return;
|
|
2901
|
+
if (e2.navigationType === "replace" && e2.destination.sameDocument) return;
|
|
2902
|
+
const url = resolve(e2.destination.url);
|
|
2903
|
+
if (!url || !leavesPage(url) || isPermitted(url)) return;
|
|
2904
|
+
e2.preventDefault();
|
|
2905
|
+
report(url.href);
|
|
2906
|
+
if (e2.destination.sameDocument) window.location.reload();
|
|
2907
|
+
};
|
|
2908
|
+
const navigation = () => window.navigation;
|
|
2909
|
+
function installPushGuard() {
|
|
2910
|
+
const original = history.pushState;
|
|
2911
|
+
const guard = function(data, unused, url) {
|
|
2912
|
+
if (guarding() && guardedPush === guard && url != null) {
|
|
2913
|
+
const target = resolve(url);
|
|
2914
|
+
if (target && leavesPage(target) && !isPermitted(target)) {
|
|
2915
|
+
report(target.href);
|
|
2916
|
+
window.location.reload();
|
|
2917
|
+
return;
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
return original.call(this, data, unused, url);
|
|
2921
|
+
};
|
|
2922
|
+
originalPush = original;
|
|
2923
|
+
guardedPush = guard;
|
|
2924
|
+
history.pushState = guard;
|
|
2925
|
+
}
|
|
2926
|
+
function uninstallPushGuard() {
|
|
2927
|
+
if (history.pushState === guardedPush && originalPush) {
|
|
2928
|
+
history.pushState = originalPush;
|
|
2929
|
+
}
|
|
2930
|
+
originalPush = null;
|
|
2931
|
+
guardedPush = null;
|
|
2932
|
+
}
|
|
2933
|
+
return {
|
|
2934
|
+
start() {
|
|
2935
|
+
if (active) return;
|
|
2936
|
+
active = true;
|
|
2937
|
+
installPushGuard();
|
|
2938
|
+
window.addEventListener("submit", onSubmit);
|
|
2939
|
+
navigation()?.addEventListener("navigate", onNavigate);
|
|
2940
|
+
},
|
|
2941
|
+
stop() {
|
|
2942
|
+
if (!active) return;
|
|
2943
|
+
active = false;
|
|
2944
|
+
uninstallPushGuard();
|
|
2945
|
+
window.removeEventListener("submit", onSubmit);
|
|
2946
|
+
navigation()?.removeEventListener("navigate", onNavigate);
|
|
2947
|
+
pendingForm = null;
|
|
2948
|
+
},
|
|
2949
|
+
isActive: guarding,
|
|
2950
|
+
block(href) {
|
|
2951
|
+
report(href);
|
|
2952
|
+
},
|
|
2953
|
+
permit(href) {
|
|
2954
|
+
permitted = resolve(href)?.href ?? null;
|
|
2955
|
+
},
|
|
2956
|
+
allow(href) {
|
|
2957
|
+
const url = resolve(href);
|
|
2958
|
+
if (!url || url.protocol !== "http:" && url.protocol !== "https:") {
|
|
2959
|
+
debug7.warn("ignored navigation to unsupported URL");
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
permitted = url.href;
|
|
2963
|
+
const form = pendingForm;
|
|
2964
|
+
pendingForm = null;
|
|
2965
|
+
if (form && form.href === url.href && form.form.isConnected) {
|
|
2966
|
+
try {
|
|
2967
|
+
form.form.requestSubmit(form.submitter ?? void 0);
|
|
2968
|
+
} catch {
|
|
2969
|
+
form.form.requestSubmit();
|
|
2970
|
+
}
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
window.location.assign(url.href);
|
|
2974
|
+
}
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
// src/core/visual-editor/index.ts
|
|
2979
|
+
var debug8 = createScopedLogger("visual-editor");
|
|
2980
|
+
var ENCODED_SELECTOR2 = "[data-prepr-encoded]";
|
|
2981
|
+
var REFERENCE_ATTR = "data-prepr-reference";
|
|
2982
|
+
var REFERENCE_SELECTOR = `[${REFERENCE_ATTR}]`;
|
|
2983
|
+
var CURSOR_STYLE_MARKER = "data-prepr-visual-editor";
|
|
2984
|
+
var CURSOR_STYLES = `${ENCODED_SELECTOR2}{cursor:pointer}html[data-prepr-ve-suspended] ${ENCODED_SELECTOR2}{cursor:auto}`;
|
|
2985
|
+
var SUSPENDED_ATTR = "data-prepr-ve-suspended";
|
|
2986
|
+
var FIND_RETRY_MS = 150;
|
|
2987
|
+
var FIND_RETRIES = 6;
|
|
2988
|
+
var MAX_CACHED_KEYS = 2e3;
|
|
2989
|
+
var LAYOUT_MUTATIONS = {
|
|
2990
|
+
childList: true,
|
|
2991
|
+
subtree: true,
|
|
2992
|
+
characterData: true,
|
|
2993
|
+
attributes: true,
|
|
2994
|
+
attributeFilter: [
|
|
2995
|
+
"class",
|
|
2996
|
+
"style",
|
|
2997
|
+
"hidden",
|
|
2998
|
+
"open",
|
|
2999
|
+
"data-prepr-encoded",
|
|
3000
|
+
// An image swapped by a carousel or lazy loader can show another asset.
|
|
3001
|
+
...ASSET_SOURCE_ATTRIBUTES
|
|
3002
|
+
]
|
|
3003
|
+
};
|
|
3004
|
+
function scrollIntoFrame(el) {
|
|
3005
|
+
const rect = el.getBoundingClientRect();
|
|
3006
|
+
const offset = Math.max(0, (window.innerHeight - rect.height) / 2);
|
|
3007
|
+
window.scrollTo({
|
|
3008
|
+
top: Math.max(0, window.scrollY + rect.top - offset),
|
|
3009
|
+
behavior: "smooth"
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
var isVisible = (el) => typeof el.checkVisibility === "function" ? el.checkVisibility() : el.getClientRects().length > 0;
|
|
3013
|
+
function createVisualEditor(opts) {
|
|
3014
|
+
const overlay = new VisualEditorOverlay();
|
|
3015
|
+
let running = false;
|
|
3016
|
+
let currentItemId = null;
|
|
3017
|
+
let locale = opts.locale ?? "en";
|
|
3018
|
+
let suspended = false;
|
|
3019
|
+
let pointerFrame = null;
|
|
3020
|
+
let lastPointerTarget = null;
|
|
3021
|
+
let lastPointerX = 0;
|
|
3022
|
+
let lastPointerY = 0;
|
|
3023
|
+
let mutationFrame = null;
|
|
3024
|
+
let mutationObserver = null;
|
|
3025
|
+
let selection = null;
|
|
3026
|
+
let findTimeout = null;
|
|
3027
|
+
let settlePendingFind = null;
|
|
3028
|
+
let guardEnabled = false;
|
|
3029
|
+
let assetIndex = /* @__PURE__ */ new Map();
|
|
3030
|
+
const assetKeys = /* @__PURE__ */ new Map();
|
|
3031
|
+
const guard = createNavigationGuard({
|
|
3032
|
+
onBlocked: (href) => opts.onNavigationBlocked?.(href),
|
|
3033
|
+
showsEditedContent: () => showsEditedContent()
|
|
3034
|
+
});
|
|
3035
|
+
const encodedFrom = (node) => node instanceof Element ? node.closest(ENCODED_SELECTOR2) : null;
|
|
3036
|
+
const labelFor = (el) => fieldLabel(el.getAttribute("data-prepr-field"), locale);
|
|
3037
|
+
let references = null;
|
|
3038
|
+
let ownComponents = /* @__PURE__ */ new Set();
|
|
3039
|
+
const isCurrentItem = (el) => {
|
|
3040
|
+
if (currentItemId === null) return true;
|
|
3041
|
+
const id = el.getAttribute("data-prepr-id");
|
|
3042
|
+
return !id || id === currentItemId || ownComponents.has(id);
|
|
3043
|
+
};
|
|
3044
|
+
const showsEditedContent = () => {
|
|
3045
|
+
for (const el of document.querySelectorAll(ENCODED_SELECTOR2)) {
|
|
3046
|
+
if (isCurrentItem(el) && isVisible(el)) return true;
|
|
3047
|
+
}
|
|
3048
|
+
return false;
|
|
3049
|
+
};
|
|
3050
|
+
const linkedIdOf = (el) => {
|
|
3051
|
+
if (!references) return null;
|
|
3052
|
+
const id = el.getAttribute("data-prepr-id") ?? "";
|
|
3053
|
+
if (references.has(id)) return id;
|
|
3054
|
+
let container = el.closest(REFERENCE_SELECTOR);
|
|
3055
|
+
while (container) {
|
|
3056
|
+
const linked = container.getAttribute(REFERENCE_ATTR) ?? "";
|
|
3057
|
+
if (references.has(linked)) return linked;
|
|
3058
|
+
container = container.parentElement?.closest(REFERENCE_SELECTOR) ?? null;
|
|
3059
|
+
}
|
|
3060
|
+
return null;
|
|
3061
|
+
};
|
|
3062
|
+
const itemKind = (el) => {
|
|
3063
|
+
if (isCurrentItem(el)) return "current";
|
|
3064
|
+
if (!references) return "other";
|
|
3065
|
+
return linkedIdOf(el) ? "referenced" : null;
|
|
3066
|
+
};
|
|
3067
|
+
const referenceFieldOf = (el) => {
|
|
3068
|
+
const linked = linkedIdOf(el);
|
|
3069
|
+
return linked ? references?.get(linked) : void 0;
|
|
3070
|
+
};
|
|
3071
|
+
const hoverLabel = (el, kind) => {
|
|
3072
|
+
if (kind === "other") {
|
|
3073
|
+
return `${labelFor(el)} \xB7 ${t("visualEditor.otherItem", locale)}`;
|
|
3074
|
+
}
|
|
3075
|
+
if (kind === "referenced") {
|
|
3076
|
+
const via = referenceFieldOf(el);
|
|
3077
|
+
const prefix = via ? `${fieldLabel(via, locale)} \u203A ` : "";
|
|
3078
|
+
return `${prefix}${labelFor(el)} \xB7 ${t("visualEditor.linkedItem", locale)}`;
|
|
3079
|
+
}
|
|
3080
|
+
return labelFor(el);
|
|
3081
|
+
};
|
|
3082
|
+
const setSuspended = (value) => {
|
|
3083
|
+
if (suspended === value) return;
|
|
3084
|
+
suspended = value;
|
|
3085
|
+
document.documentElement.toggleAttribute(SUSPENDED_ATTR, value);
|
|
3086
|
+
if (value) overlay.setHover(null, "");
|
|
3087
|
+
};
|
|
3088
|
+
const updateHover = () => {
|
|
3089
|
+
pointerFrame = null;
|
|
3090
|
+
overlay.schedule();
|
|
3091
|
+
if (suspended) return;
|
|
3092
|
+
const encoded = encodedFrom(lastPointerTarget);
|
|
3093
|
+
const target = encoded === overlay.getSelectedTarget() ? null : encoded;
|
|
3094
|
+
if (!target) {
|
|
3095
|
+
overlay.setHover(null, "");
|
|
3096
|
+
return;
|
|
3097
|
+
}
|
|
3098
|
+
const kind = itemKind(target);
|
|
3099
|
+
if (!kind) {
|
|
3100
|
+
overlay.setHover(null, "");
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
overlay.setHover(target, hoverLabel(target, kind), { kind });
|
|
3104
|
+
};
|
|
3105
|
+
const handlePointerMove = (e2) => {
|
|
3106
|
+
setSuspended(e2.altKey);
|
|
3107
|
+
lastPointerTarget = e2.target;
|
|
3108
|
+
lastPointerX = e2.clientX;
|
|
3109
|
+
lastPointerY = e2.clientY;
|
|
3110
|
+
if (pointerFrame === null) {
|
|
3111
|
+
pointerFrame = requestAnimationFrame(updateHover);
|
|
3112
|
+
}
|
|
3113
|
+
};
|
|
3114
|
+
const handlePointerLeave = () => {
|
|
3115
|
+
lastPointerTarget = null;
|
|
3116
|
+
overlay.setHover(null, "");
|
|
3117
|
+
};
|
|
3118
|
+
const handleClick = (e2) => {
|
|
3119
|
+
if (e2.altKey) {
|
|
3120
|
+
const href = leavingLinkHref(e2.target);
|
|
3121
|
+
if (href && guard.isActive()) guard.permit(href);
|
|
3122
|
+
return;
|
|
3123
|
+
}
|
|
3124
|
+
const encoded = encodedFrom(e2.target);
|
|
3125
|
+
const kind = encoded ? itemKind(encoded) : null;
|
|
3126
|
+
if (!encoded || !kind) {
|
|
3127
|
+
clearSelection();
|
|
3128
|
+
if (e2.ctrlKey || e2.metaKey || e2.shiftKey || !showsEditedContent()) return;
|
|
3129
|
+
const href = leavingLinkHref(e2.target);
|
|
3130
|
+
if (!href) return;
|
|
3131
|
+
e2.preventDefault();
|
|
3132
|
+
if (guard.isActive()) {
|
|
3133
|
+
e2.stopPropagation();
|
|
3134
|
+
guard.block(href);
|
|
3135
|
+
}
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
e2.preventDefault();
|
|
3139
|
+
e2.stopPropagation();
|
|
3140
|
+
const item = listItemIn(encoded, e2.target);
|
|
3141
|
+
if (kind === "current")
|
|
3142
|
+
select(item?.element ?? encoded, encoded, item?.index);
|
|
3143
|
+
const referenceField = kind === "referenced" ? referenceFieldOf(encoded) : void 0;
|
|
3144
|
+
const linkedId = kind === "referenced" ? linkedIdOf(encoded) : null;
|
|
3145
|
+
opts.onEdit({
|
|
3146
|
+
...readEditPayload(encoded),
|
|
3147
|
+
...linkedId && { id: linkedId },
|
|
3148
|
+
...item && { block: item.index },
|
|
3149
|
+
...referenceField && { referenceField }
|
|
3150
|
+
});
|
|
3151
|
+
};
|
|
3152
|
+
const handleKey = (e2) => {
|
|
3153
|
+
if (e2.key === "Alt") setSuspended(e2.type === "keydown");
|
|
3154
|
+
if (e2.type === "keydown" && e2.key === "Escape") {
|
|
3155
|
+
clearSelection();
|
|
3156
|
+
}
|
|
3157
|
+
};
|
|
3158
|
+
const handleBlur = () => setSuspended(false);
|
|
3159
|
+
const handleScroll = () => overlay.onScroll();
|
|
3160
|
+
const handleResize = () => overlay.schedule();
|
|
3161
|
+
const handleSettled = () => overlay.schedule();
|
|
3162
|
+
function listItemIn(encoded, target) {
|
|
3163
|
+
if (!(target instanceof Element)) return null;
|
|
3164
|
+
const li = target.closest("li");
|
|
3165
|
+
if (!li || !encoded.contains(li) || !li.parentElement) return null;
|
|
3166
|
+
const items = Array.from(li.parentElement.children).filter(
|
|
3167
|
+
(child) => child.tagName === "LI"
|
|
3168
|
+
);
|
|
3169
|
+
return { element: li, index: items.indexOf(li) };
|
|
3170
|
+
}
|
|
3171
|
+
function nthListItem(encoded, block) {
|
|
3172
|
+
const list = encoded.querySelector("ul, ol");
|
|
3173
|
+
if (!list) return null;
|
|
3174
|
+
const items = Array.from(list.children).filter(
|
|
3175
|
+
(child) => child.tagName === "LI"
|
|
3176
|
+
);
|
|
3177
|
+
return items[block] ?? null;
|
|
3178
|
+
}
|
|
3179
|
+
function select(el, field, block) {
|
|
3180
|
+
const entryId = field.getAttribute("data-prepr-entry-id");
|
|
3181
|
+
selection = {
|
|
3182
|
+
lookup: {
|
|
3183
|
+
field: field.getAttribute("data-prepr-field") ?? "",
|
|
3184
|
+
id: field.getAttribute("data-prepr-id") ?? void 0,
|
|
3185
|
+
...entryId && { entryId },
|
|
3186
|
+
...block !== void 0 && { block }
|
|
3187
|
+
},
|
|
3188
|
+
label: field
|
|
3189
|
+
};
|
|
3190
|
+
overlay.setSelected(el, labelFor(field));
|
|
3191
|
+
if (overlay.getHoverTarget() === el) overlay.setHover(null, "");
|
|
3192
|
+
}
|
|
3193
|
+
function clearSelection() {
|
|
3194
|
+
selection = null;
|
|
3195
|
+
overlay.setSelected(null, "");
|
|
3196
|
+
}
|
|
3197
|
+
function reselect() {
|
|
3198
|
+
if (!selection) return;
|
|
3199
|
+
const current = overlay.getSelectedTarget();
|
|
3200
|
+
if (current?.isConnected && selection.label.isConnected) return;
|
|
3201
|
+
const found = findFieldElement(selection.lookup);
|
|
3202
|
+
if (!found || !isCurrentItem(found)) {
|
|
3203
|
+
overlay.setSelected(null, "");
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3206
|
+
const { block } = selection.lookup;
|
|
3207
|
+
const el = block !== void 0 ? nthListItem(found, block) ?? found : found;
|
|
3208
|
+
select(el, found, block);
|
|
3209
|
+
}
|
|
3210
|
+
function tagAssets() {
|
|
3211
|
+
if (assetKeys.size > MAX_CACHED_KEYS) assetKeys.clear();
|
|
3212
|
+
tagAssetImages(assetIndex, currentItemId, assetKeys, {
|
|
3213
|
+
isCurrentItem: (el) => isCurrentItem(el)
|
|
3214
|
+
});
|
|
3215
|
+
}
|
|
3216
|
+
const handleLayoutFrame = () => {
|
|
3217
|
+
mutationFrame = null;
|
|
3218
|
+
if (assetIndex.size) tagAssets();
|
|
3219
|
+
reselect();
|
|
3220
|
+
const hovered = overlay.getHoverTarget();
|
|
3221
|
+
if (hovered && !hovered.isConnected) {
|
|
3222
|
+
lastPointerTarget = document.elementFromPoint(lastPointerX, lastPointerY);
|
|
3223
|
+
updateHover();
|
|
3224
|
+
return;
|
|
3225
|
+
}
|
|
3226
|
+
overlay.schedule();
|
|
3227
|
+
};
|
|
3228
|
+
const handleMutations = () => {
|
|
3229
|
+
if (mutationFrame !== null) return;
|
|
3230
|
+
if (!selection && !overlay.getHoverTarget() && !assetIndex.size) return;
|
|
3231
|
+
mutationFrame = requestAnimationFrame(handleLayoutFrame);
|
|
3232
|
+
};
|
|
3233
|
+
function injectCursorStyles() {
|
|
3234
|
+
if (document.querySelector(`style[${CURSOR_STYLE_MARKER}]`)) return;
|
|
3235
|
+
const style = document.createElement("style");
|
|
3236
|
+
style.setAttribute(CURSOR_STYLE_MARKER, "");
|
|
3237
|
+
style.textContent = CURSOR_STYLES;
|
|
3238
|
+
document.head.appendChild(style);
|
|
3239
|
+
}
|
|
3240
|
+
function start() {
|
|
3241
|
+
if (running) return;
|
|
3242
|
+
running = true;
|
|
3243
|
+
overlay.mount();
|
|
3244
|
+
injectCursorStyles();
|
|
3245
|
+
document.addEventListener("pointermove", handlePointerMove, {
|
|
3246
|
+
passive: true
|
|
3247
|
+
});
|
|
3248
|
+
document.documentElement.addEventListener(
|
|
3249
|
+
"pointerleave",
|
|
3250
|
+
handlePointerLeave
|
|
3251
|
+
);
|
|
3252
|
+
document.addEventListener("click", handleClick, { capture: true });
|
|
3253
|
+
window.addEventListener("keydown", handleKey);
|
|
3254
|
+
window.addEventListener("keyup", handleKey);
|
|
3255
|
+
window.addEventListener("blur", handleBlur);
|
|
3256
|
+
window.addEventListener("scroll", handleScroll, {
|
|
3257
|
+
capture: true,
|
|
3258
|
+
passive: true
|
|
3259
|
+
});
|
|
3260
|
+
window.addEventListener("resize", handleResize, { passive: true });
|
|
3261
|
+
document.addEventListener("transitionend", handleSettled, {
|
|
3262
|
+
capture: true,
|
|
3263
|
+
passive: true
|
|
3264
|
+
});
|
|
3265
|
+
document.addEventListener("animationend", handleSettled, {
|
|
3266
|
+
capture: true,
|
|
3267
|
+
passive: true
|
|
3268
|
+
});
|
|
3269
|
+
mutationObserver = new MutationObserver(handleMutations);
|
|
3270
|
+
mutationObserver.observe(document.body, LAYOUT_MUTATIONS);
|
|
3271
|
+
if (assetIndex.size) tagAssets();
|
|
3272
|
+
syncGuard();
|
|
3273
|
+
debug8.log("visual editor started");
|
|
3274
|
+
}
|
|
3275
|
+
function stop() {
|
|
3276
|
+
if (!running) return;
|
|
3277
|
+
running = false;
|
|
3278
|
+
if (pointerFrame !== null) cancelAnimationFrame(pointerFrame);
|
|
3279
|
+
if (mutationFrame !== null) cancelAnimationFrame(mutationFrame);
|
|
3280
|
+
mutationObserver?.disconnect();
|
|
3281
|
+
mutationObserver = null;
|
|
3282
|
+
cancelFind();
|
|
3283
|
+
pointerFrame = null;
|
|
3284
|
+
mutationFrame = null;
|
|
3285
|
+
selection = null;
|
|
3286
|
+
lastPointerTarget = null;
|
|
3287
|
+
setSuspended(false);
|
|
3288
|
+
document.removeEventListener("pointermove", handlePointerMove);
|
|
3289
|
+
document.documentElement.removeEventListener(
|
|
3290
|
+
"pointerleave",
|
|
3291
|
+
handlePointerLeave
|
|
3292
|
+
);
|
|
3293
|
+
document.removeEventListener("click", handleClick, { capture: true });
|
|
3294
|
+
window.removeEventListener("keydown", handleKey);
|
|
3295
|
+
window.removeEventListener("keyup", handleKey);
|
|
3296
|
+
window.removeEventListener("blur", handleBlur);
|
|
3297
|
+
window.removeEventListener("scroll", handleScroll, { capture: true });
|
|
3298
|
+
window.removeEventListener("resize", handleResize);
|
|
3299
|
+
document.removeEventListener("transitionend", handleSettled, {
|
|
3300
|
+
capture: true
|
|
3301
|
+
});
|
|
3302
|
+
document.removeEventListener("animationend", handleSettled, {
|
|
3303
|
+
capture: true
|
|
3304
|
+
});
|
|
3305
|
+
document.querySelectorAll(`style[${CURSOR_STYLE_MARKER}]`).forEach((node) => node.remove());
|
|
3306
|
+
overlay.unmount();
|
|
3307
|
+
untagAssetImages();
|
|
3308
|
+
guard.stop();
|
|
3309
|
+
debug8.log("visual editor stopped");
|
|
3310
|
+
}
|
|
3311
|
+
function cancelFind() {
|
|
3312
|
+
if (findTimeout) clearTimeout(findTimeout);
|
|
3313
|
+
findTimeout = null;
|
|
3314
|
+
settlePendingFind?.();
|
|
3315
|
+
settlePendingFind = null;
|
|
3316
|
+
}
|
|
3317
|
+
function focusField(lookup) {
|
|
3318
|
+
cancelFind();
|
|
3319
|
+
return new Promise((resolve2) => {
|
|
3320
|
+
let attempt = 0;
|
|
3321
|
+
const finish = (found) => {
|
|
3322
|
+
settlePendingFind = null;
|
|
3323
|
+
resolve2(found);
|
|
3324
|
+
};
|
|
3325
|
+
settlePendingFind = () => resolve2(false);
|
|
3326
|
+
const tryFind = () => {
|
|
3327
|
+
findTimeout = null;
|
|
3328
|
+
const found = findFieldElement(lookup);
|
|
3329
|
+
if (found) {
|
|
3330
|
+
const el = lookup.block !== void 0 ? nthListItem(found, lookup.block) ?? found : found;
|
|
3331
|
+
scrollIntoFrame(el);
|
|
3332
|
+
if (running && isCurrentItem(found)) select(el, found, lookup.block);
|
|
3333
|
+
finish(true);
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
if (++attempt >= FIND_RETRIES) {
|
|
3337
|
+
debug8.log("no element renders field", lookup.field);
|
|
3338
|
+
finish(false);
|
|
3339
|
+
return;
|
|
3340
|
+
}
|
|
3341
|
+
findTimeout = setTimeout(tryFind, FIND_RETRY_MS);
|
|
3342
|
+
};
|
|
3343
|
+
tryFind();
|
|
3344
|
+
});
|
|
3345
|
+
}
|
|
3346
|
+
function setCurrentItem(itemId, referenceList, componentIds) {
|
|
3347
|
+
currentItemId = itemId;
|
|
3348
|
+
if (referenceList) setReferences(referenceList, componentIds);
|
|
3349
|
+
else if (componentIds) ownComponents = new Set(componentIds);
|
|
3350
|
+
if (running && assetIndex.size) tagAssets();
|
|
3351
|
+
const selected = overlay.getSelectedTarget();
|
|
3352
|
+
const owner = selected ? encodedFrom(selected) : null;
|
|
3353
|
+
if (owner && !isCurrentItem(owner)) clearSelection();
|
|
3354
|
+
}
|
|
3355
|
+
function setAssets(assets) {
|
|
3356
|
+
const hadAssets = assetIndex.size > 0;
|
|
3357
|
+
assetIndex = createAssetIndex(assets);
|
|
3358
|
+
if (running && (hadAssets || assetIndex.size)) tagAssets();
|
|
3359
|
+
}
|
|
3360
|
+
function setReferences(referenceList, componentIds) {
|
|
3361
|
+
if (componentIds) ownComponents = new Set(componentIds);
|
|
3362
|
+
references = /* @__PURE__ */ new Map();
|
|
3363
|
+
for (const { id, field } of referenceList) {
|
|
3364
|
+
if (!references.has(id)) references.set(id, field);
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
function setLocale(value) {
|
|
3368
|
+
locale = value;
|
|
3369
|
+
}
|
|
3370
|
+
function syncGuard() {
|
|
3371
|
+
if (running && guardEnabled) guard.start();
|
|
3372
|
+
else guard.stop();
|
|
3373
|
+
}
|
|
3374
|
+
function setNavigationGuard(enabled) {
|
|
3375
|
+
guardEnabled = enabled;
|
|
3376
|
+
syncGuard();
|
|
3377
|
+
}
|
|
3378
|
+
return {
|
|
3379
|
+
start,
|
|
3380
|
+
stop,
|
|
3381
|
+
setCurrentItem,
|
|
3382
|
+
setReferences,
|
|
3383
|
+
setAssets,
|
|
3384
|
+
setLocale,
|
|
3385
|
+
focusField,
|
|
3386
|
+
setNavigationGuard,
|
|
3387
|
+
allowNavigation: (href) => guard.allow(href)
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
// src/core/create-preview.ts
|
|
3392
|
+
var debug9 = createScopedLogger("create-preview");
|
|
3393
|
+
var controllerStores = /* @__PURE__ */ new WeakMap();
|
|
3394
|
+
function defaultNavigation() {
|
|
3395
|
+
return {
|
|
3396
|
+
navigate: (url) => window.location.assign(url),
|
|
3397
|
+
currentPath: () => window.location.pathname + window.location.search
|
|
3398
|
+
};
|
|
3399
|
+
}
|
|
3400
|
+
function safeEditUrl(href) {
|
|
3401
|
+
try {
|
|
3402
|
+
const url = new URL(href, window.location.href);
|
|
3403
|
+
return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
|
|
3404
|
+
} catch {
|
|
3405
|
+
return null;
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
function buildSegments(data) {
|
|
3409
|
+
return [{ _id: "all_other_users", name: "All other users" }, ...data];
|
|
3410
|
+
}
|
|
3411
|
+
function resolveLocale(options) {
|
|
3412
|
+
const explicit = options?.locale;
|
|
3413
|
+
if (isLocale(explicit)) return explicit;
|
|
3414
|
+
if (typeof navigator !== "undefined") {
|
|
3415
|
+
const candidates = Array.isArray(navigator.languages) && navigator.languages.length ? navigator.languages : [navigator.language];
|
|
3416
|
+
const match = candidates.filter(Boolean).map((l2) => l2.toLowerCase().split("-")[0]).find(isLocale);
|
|
3417
|
+
if (match) return match;
|
|
3418
|
+
}
|
|
3419
|
+
return "en";
|
|
3420
|
+
}
|
|
3421
|
+
function createPreprPreview(opts = {}) {
|
|
3422
|
+
const { props, options } = opts;
|
|
2181
3423
|
const navigation = opts.navigation ?? defaultNavigation();
|
|
2182
3424
|
initDebugLogger(options?.debug ?? false);
|
|
2183
3425
|
const noop = () => ({ destroy: () => {
|
|
@@ -2189,7 +3431,7 @@ function createPreprPreview(opts = {}) {
|
|
|
2189
3431
|
const search = new URLSearchParams(window.location.search);
|
|
2190
3432
|
const hideBar = search.get(PARAM_HIDE_BAR) === "true";
|
|
2191
3433
|
if (hideBar) {
|
|
2192
|
-
|
|
3434
|
+
debug9.log(`${PARAM_HIDE_BAR}=true \u2014 no visible toolbar`);
|
|
2193
3435
|
}
|
|
2194
3436
|
const mountUi = (options?.ui ?? true) && !isIframe && !hideBar;
|
|
2195
3437
|
const locale = resolveLocale(options);
|
|
@@ -2218,30 +3460,45 @@ function createPreprPreview(opts = {}) {
|
|
|
2218
3460
|
el.connect(store, (key) => t(key, locale));
|
|
2219
3461
|
}
|
|
2220
3462
|
const editingEnabled = features.editMode || isIframe;
|
|
2221
|
-
const
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
}
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
}
|
|
3463
|
+
const onEdit = ({
|
|
3464
|
+
href,
|
|
3465
|
+
id,
|
|
3466
|
+
field,
|
|
3467
|
+
locale: contentLocale,
|
|
3468
|
+
block,
|
|
3469
|
+
entryId,
|
|
3470
|
+
referenceField
|
|
3471
|
+
}) => {
|
|
3472
|
+
const safeHref = href ? safeEditUrl(href) : null;
|
|
3473
|
+
if (href && !safeHref) {
|
|
3474
|
+
debug9.warn("ignored edit request with unsupported href scheme");
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3477
|
+
if (isIframe) {
|
|
3478
|
+
sendPreprEvent("field_edit_requested", {
|
|
3479
|
+
id,
|
|
3480
|
+
field,
|
|
3481
|
+
locale: contentLocale,
|
|
3482
|
+
...block !== void 0 && { block },
|
|
3483
|
+
...entryId && { entryId },
|
|
3484
|
+
...referenceField && { referenceField }
|
|
3485
|
+
});
|
|
3486
|
+
} else if (safeHref) {
|
|
3487
|
+
window.open(safeHref, "_blank", "noopener,noreferrer");
|
|
2244
3488
|
}
|
|
3489
|
+
};
|
|
3490
|
+
const visualEditor = isIframe ? createVisualEditor({
|
|
3491
|
+
onEdit,
|
|
3492
|
+
locale,
|
|
3493
|
+
onNavigationBlocked: (href) => sendPreprEvent("navigation_blocked", { href })
|
|
3494
|
+
}) : null;
|
|
3495
|
+
const stega = visualEditor ?? createStegaController({
|
|
3496
|
+
// The edit-mode marks and hover label are chrome, so they follow the
|
|
3497
|
+
// same decision as the bar: unwanted in a headless preview that asked
|
|
3498
|
+
// for no UI.
|
|
3499
|
+
tooltip: mountUi,
|
|
3500
|
+
onEdit,
|
|
3501
|
+
locale
|
|
2245
3502
|
});
|
|
2246
3503
|
const autoClean = createStegaAutoClean({
|
|
2247
3504
|
enabled: options?.autoClean
|
|
@@ -2263,7 +3520,35 @@ function createPreprPreview(opts = {}) {
|
|
|
2263
3520
|
handleChange(before, state);
|
|
2264
3521
|
});
|
|
2265
3522
|
const bridge = createIframeBridge(store, {
|
|
2266
|
-
allowedEditorOrigins: options?.allowedEditorOrigins
|
|
3523
|
+
allowedEditorOrigins: options?.allowedEditorOrigins,
|
|
3524
|
+
onHandshake: ({
|
|
3525
|
+
itemId,
|
|
3526
|
+
referencedIds,
|
|
3527
|
+
componentIds,
|
|
3528
|
+
uiLocale,
|
|
3529
|
+
navigationGuard,
|
|
3530
|
+
assets
|
|
3531
|
+
}) => {
|
|
3532
|
+
if (uiLocale) visualEditor?.setLocale(uiLocale);
|
|
3533
|
+
visualEditor?.setCurrentItem(itemId ?? null, referencedIds, componentIds);
|
|
3534
|
+
visualEditor?.setNavigationGuard(navigationGuard === true);
|
|
3535
|
+
if (assets) visualEditor?.setAssets(assets);
|
|
3536
|
+
},
|
|
3537
|
+
onAssets: (assets) => visualEditor?.setAssets(assets),
|
|
3538
|
+
onAllowNavigation: (href) => visualEditor?.allowNavigation(href),
|
|
3539
|
+
onReferences: (referencedIds, componentIds) => visualEditor?.setReferences(referencedIds, componentIds),
|
|
3540
|
+
// The editor selecting a field scrolls the matching element into view. The
|
|
3541
|
+
// lookup relies on the attributes the stega scan writes, so it finds
|
|
3542
|
+
// nothing until encoded content has been tagged.
|
|
3543
|
+
onScrollToField: ({ field, id, block, entryId, fieldById }) => {
|
|
3544
|
+
if (visualEditor) {
|
|
3545
|
+
void visualEditor.focusField({ field, id, block, entryId, fieldById });
|
|
3546
|
+
return;
|
|
3547
|
+
}
|
|
3548
|
+
if (!scrollToField({ field, id })) {
|
|
3549
|
+
debug9.log("no element on this page renders field", field);
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
2267
3552
|
});
|
|
2268
3553
|
sendPreprEvent(
|
|
2269
3554
|
"getScrollPosition",
|
|
@@ -2283,13 +3568,1724 @@ function createPreprPreview(opts = {}) {
|
|
|
2283
3568
|
bridge.stop();
|
|
2284
3569
|
}
|
|
2285
3570
|
el?.remove();
|
|
2286
|
-
|
|
3571
|
+
debug9.log("preview destroyed");
|
|
2287
3572
|
}
|
|
2288
3573
|
const controller = { destroy };
|
|
2289
3574
|
controllerStores.set(controller, store);
|
|
2290
3575
|
return controller;
|
|
2291
3576
|
}
|
|
2292
3577
|
|
|
3578
|
+
// src/core/preview-attrs.ts
|
|
3579
|
+
function previewAttrs(target, options) {
|
|
3580
|
+
if (!options.enabled || !target?.id || !target.field) return {};
|
|
3581
|
+
const attrs = {
|
|
3582
|
+
"data-prepr-encoded": "",
|
|
3583
|
+
"data-prepr-id": target.id,
|
|
3584
|
+
"data-prepr-field": target.field
|
|
3585
|
+
};
|
|
3586
|
+
if (target.locale) attrs["data-prepr-locale"] = target.locale;
|
|
3587
|
+
if (target.entryId) attrs["data-prepr-entry-id"] = target.entryId;
|
|
3588
|
+
return attrs;
|
|
3589
|
+
}
|
|
3590
|
+
var URL_BASE = "https://prepr.invalid";
|
|
3591
|
+
function stegaTarget(value, options = {}) {
|
|
3592
|
+
const decoded = value ? decodeStega(value) : null;
|
|
3593
|
+
if (!decoded) return null;
|
|
3594
|
+
let url;
|
|
3595
|
+
try {
|
|
3596
|
+
url = new URL(decoded.href, URL_BASE);
|
|
3597
|
+
} catch {
|
|
3598
|
+
return null;
|
|
3599
|
+
}
|
|
3600
|
+
const id = decodeURIComponent(url.pathname.split("/").pop() ?? "");
|
|
3601
|
+
const field = url.searchParams.get("field");
|
|
3602
|
+
if (!id || !field) return null;
|
|
3603
|
+
return {
|
|
3604
|
+
id,
|
|
3605
|
+
field: options.field ? `${field.slice(0, field.lastIndexOf(".") + 1)}${options.field}` : field,
|
|
3606
|
+
locale: url.searchParams.get("locale")
|
|
3607
|
+
};
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
// src/core/live-preview/normalize-query.ts
|
|
3611
|
+
var import_stega6 = require("@vercel/stega");
|
|
3612
|
+
|
|
3613
|
+
// src/core/live-preview/field-name.ts
|
|
3614
|
+
var UPPERCASE_BOUNDARY = /(.)(?=[A-Z])/g;
|
|
3615
|
+
function graphqlFieldName(apiId) {
|
|
3616
|
+
return apiId.replace(UPPERCASE_BOUNDARY, "$1_").toLowerCase();
|
|
3617
|
+
}
|
|
3618
|
+
function fieldKeyIn(record, apiId) {
|
|
3619
|
+
if (Object.prototype.hasOwnProperty.call(record, apiId)) return apiId;
|
|
3620
|
+
const name = graphqlFieldName(apiId);
|
|
3621
|
+
return Object.prototype.hasOwnProperty.call(record, name) ? name : void 0;
|
|
3622
|
+
}
|
|
3623
|
+
var TEXT_BODY_FIELDS = [
|
|
3624
|
+
{ name: "body", plain: false },
|
|
3625
|
+
{ name: "html", plain: false },
|
|
3626
|
+
{ name: "text", plain: true }
|
|
3627
|
+
];
|
|
3628
|
+
var TAG_PATTERN = /<[^>]+>/g;
|
|
3629
|
+
function plainTextFromHtml(html) {
|
|
3630
|
+
return html.replace(TAG_PATTERN, "");
|
|
3631
|
+
}
|
|
3632
|
+
function isTextEntry(record) {
|
|
3633
|
+
const typename = record.__typename;
|
|
3634
|
+
if (typeof typename === "string") return typename === "Text";
|
|
3635
|
+
return "html" in record || "text" in record && "format" in record;
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
// src/core/live-preview/index-snapshot.ts
|
|
3639
|
+
var debug10 = createScopedLogger("live-preview");
|
|
3640
|
+
function indexSnapshot(data) {
|
|
3641
|
+
const index = /* @__PURE__ */ new Map();
|
|
3642
|
+
const visited = /* @__PURE__ */ new WeakSet();
|
|
3643
|
+
const path = [];
|
|
3644
|
+
function walk2(node) {
|
|
3645
|
+
if (node === null || typeof node !== "object") return;
|
|
3646
|
+
if (visited.has(node)) return;
|
|
3647
|
+
visited.add(node);
|
|
3648
|
+
if (Array.isArray(node)) {
|
|
3649
|
+
for (let i2 = 0; i2 < node.length; i2++) {
|
|
3650
|
+
path.push(i2);
|
|
3651
|
+
walk2(node[i2]);
|
|
3652
|
+
path.pop();
|
|
3653
|
+
}
|
|
3654
|
+
return;
|
|
3655
|
+
}
|
|
3656
|
+
const record = node;
|
|
3657
|
+
if (typeof record._id === "string") {
|
|
3658
|
+
const paths = index.get(record._id);
|
|
3659
|
+
if (paths) paths.push([...path]);
|
|
3660
|
+
else index.set(record._id, [[...path]]);
|
|
3661
|
+
} else if (typeof record.__typename === "string") {
|
|
3662
|
+
debug10.log("item without _id will not live-update", {
|
|
3663
|
+
__typename: record.__typename
|
|
3664
|
+
});
|
|
3665
|
+
}
|
|
3666
|
+
for (const key in record) {
|
|
3667
|
+
if (!Object.prototype.hasOwnProperty.call(record, key)) continue;
|
|
3668
|
+
path.push(key);
|
|
3669
|
+
walk2(record[key]);
|
|
3670
|
+
path.pop();
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
walk2(data);
|
|
3674
|
+
return index;
|
|
3675
|
+
}
|
|
3676
|
+
|
|
3677
|
+
// src/core/live-preview/patch.ts
|
|
3678
|
+
var import_stega5 = require("@vercel/stega");
|
|
3679
|
+
var INDEX_SEGMENT2 = /^\d+$/;
|
|
3680
|
+
var FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
3681
|
+
function isPlainObject(value) {
|
|
3682
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3683
|
+
}
|
|
3684
|
+
function mergeObject(current, incoming) {
|
|
3685
|
+
let changed = false;
|
|
3686
|
+
const next = { ...current };
|
|
3687
|
+
for (const key of Object.keys(current)) {
|
|
3688
|
+
if (!(key in incoming) || incoming[key] === void 0) continue;
|
|
3689
|
+
const value = isPlainObject(current[key]) && isPlainObject(incoming[key]) ? mergeObject(current[key], incoming[key]) : incoming[key];
|
|
3690
|
+
if (value !== current[key]) {
|
|
3691
|
+
next[key] = value;
|
|
3692
|
+
changed = true;
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
return changed ? next : current;
|
|
3696
|
+
}
|
|
3697
|
+
function shapeLike(template, incoming) {
|
|
3698
|
+
const out = {};
|
|
3699
|
+
for (const key of Object.keys(template)) {
|
|
3700
|
+
const value = incoming[key];
|
|
3701
|
+
out[key] = value === void 0 ? null : isPlainObject(template[key]) && isPlainObject(value) ? shapeLike(template[key], value) : value;
|
|
3702
|
+
}
|
|
3703
|
+
return out;
|
|
3704
|
+
}
|
|
3705
|
+
function mergeStructured(current, incoming) {
|
|
3706
|
+
if (incoming === null || Array.isArray(incoming) && !incoming.length) {
|
|
3707
|
+
if (Array.isArray(current)) return current.length ? [] : current;
|
|
3708
|
+
return null;
|
|
3709
|
+
}
|
|
3710
|
+
const list = Array.isArray(incoming) ? incoming : [incoming];
|
|
3711
|
+
if (current === null || current === void 0) return incoming;
|
|
3712
|
+
if (Array.isArray(current)) {
|
|
3713
|
+
const template = current.find(isPlainObject);
|
|
3714
|
+
return list.map((entry) => {
|
|
3715
|
+
const match = current.find(
|
|
3716
|
+
(c2) => isPlainObject(c2) && c2._id !== void 0 && c2._id === entry._id
|
|
3717
|
+
);
|
|
3718
|
+
if (isPlainObject(match)) return mergeObject(match, entry);
|
|
3719
|
+
return isPlainObject(template) ? shapeLike(template, entry) : entry;
|
|
3720
|
+
});
|
|
3721
|
+
}
|
|
3722
|
+
if (isPlainObject(current)) return mergeObject(current, list[0]);
|
|
3723
|
+
return current;
|
|
3724
|
+
}
|
|
3725
|
+
function isPatchable(current) {
|
|
3726
|
+
return current === null || typeof current === "string" || typeof current === "number" || typeof current === "boolean";
|
|
3727
|
+
}
|
|
3728
|
+
function cloneNode(node) {
|
|
3729
|
+
return Array.isArray(node) ? [...node] : { ...node };
|
|
3730
|
+
}
|
|
3731
|
+
function patchSnapshot(snapshot, index, itemId, field, value) {
|
|
3732
|
+
const paths = index.get(itemId);
|
|
3733
|
+
if (!paths?.length) return snapshot;
|
|
3734
|
+
let root = snapshot;
|
|
3735
|
+
let changed = false;
|
|
3736
|
+
const markChanged = () => {
|
|
3737
|
+
changed = true;
|
|
3738
|
+
};
|
|
3739
|
+
for (const path of paths) {
|
|
3740
|
+
for (const [target, targetValue] of expandTextBody(
|
|
3741
|
+
root,
|
|
3742
|
+
path,
|
|
3743
|
+
field,
|
|
3744
|
+
value
|
|
3745
|
+
)) {
|
|
3746
|
+
root = patchAtPath(root, path, target, targetValue, markChanged);
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
return changed ? root : snapshot;
|
|
3750
|
+
}
|
|
3751
|
+
function expandTextBody(root, path, field, value) {
|
|
3752
|
+
const segments = field.split(".");
|
|
3753
|
+
if (segments[segments.length - 1] !== "body" || typeof value !== "string") {
|
|
3754
|
+
return [[field, value]];
|
|
3755
|
+
}
|
|
3756
|
+
let node = root;
|
|
3757
|
+
for (const step of path) {
|
|
3758
|
+
node = node[step];
|
|
3759
|
+
}
|
|
3760
|
+
for (const segment of segments.slice(0, -1)) {
|
|
3761
|
+
if (node === null || typeof node !== "object") return [[field, value]];
|
|
3762
|
+
const record2 = node;
|
|
3763
|
+
node = INDEX_SEGMENT2.test(segment) ? record2[Number(segment)] : record2[fieldKeyIn(record2, segment) ?? segment];
|
|
3764
|
+
}
|
|
3765
|
+
if (node === null || typeof node !== "object" || Array.isArray(node)) {
|
|
3766
|
+
return [[field, value]];
|
|
3767
|
+
}
|
|
3768
|
+
const record = node;
|
|
3769
|
+
if (!isTextEntry(record)) return [[field, value]];
|
|
3770
|
+
const prefix = segments.slice(0, -1);
|
|
3771
|
+
const writes = [];
|
|
3772
|
+
for (const { name, plain } of TEXT_BODY_FIELDS) {
|
|
3773
|
+
if (!(name in record)) continue;
|
|
3774
|
+
writes.push([
|
|
3775
|
+
[...prefix, name].join("."),
|
|
3776
|
+
plain ? plainTextFromHtml(value) : value
|
|
3777
|
+
]);
|
|
3778
|
+
}
|
|
3779
|
+
return writes.length ? writes : [[field, value]];
|
|
3780
|
+
}
|
|
3781
|
+
function patchAtPath(root, path, field, value, markChanged) {
|
|
3782
|
+
const segments = field.split(".");
|
|
3783
|
+
if (segments.some((s2) => FORBIDDEN_SEGMENTS.has(s2))) return root;
|
|
3784
|
+
let node = root;
|
|
3785
|
+
for (const step of path) {
|
|
3786
|
+
node = node[step];
|
|
3787
|
+
}
|
|
3788
|
+
const fullPath = [...path];
|
|
3789
|
+
for (const segment of segments) {
|
|
3790
|
+
let step = segment;
|
|
3791
|
+
if (INDEX_SEGMENT2.test(segment)) {
|
|
3792
|
+
step = Number(segment);
|
|
3793
|
+
} else if (node !== null && typeof node === "object") {
|
|
3794
|
+
step = fieldKeyIn(node, segment) ?? segment;
|
|
3795
|
+
}
|
|
3796
|
+
fullPath.push(step);
|
|
3797
|
+
node = node !== null && typeof node === "object" ? node[step] : void 0;
|
|
3798
|
+
}
|
|
3799
|
+
return writeAtPath(root, fullPath, value, markChanged);
|
|
3800
|
+
}
|
|
3801
|
+
function writeAtPath(root, fullPath, value, markChanged = () => {
|
|
3802
|
+
}) {
|
|
3803
|
+
if (!fullPath.length) return root;
|
|
3804
|
+
if (fullPath.some((s2) => FORBIDDEN_SEGMENTS.has(String(s2)))) return root;
|
|
3805
|
+
const last = fullPath.length - 1;
|
|
3806
|
+
let node = root;
|
|
3807
|
+
for (let i2 = 0; i2 < last; i2++) {
|
|
3808
|
+
if (node === null || typeof node !== "object") return root;
|
|
3809
|
+
node = node[fullPath[i2]];
|
|
3810
|
+
}
|
|
3811
|
+
if (node === null || typeof node !== "object") return root;
|
|
3812
|
+
const container = node;
|
|
3813
|
+
const key = fullPath[last];
|
|
3814
|
+
if (!(key in container)) return root;
|
|
3815
|
+
let next;
|
|
3816
|
+
if (value === null || typeof value === "object") {
|
|
3817
|
+
next = mergeStructured(container[key], value);
|
|
3818
|
+
} else if (!isPatchable(container[key])) {
|
|
3819
|
+
return root;
|
|
3820
|
+
} else {
|
|
3821
|
+
next = keepEditLink(container[key], value);
|
|
3822
|
+
}
|
|
3823
|
+
if (container[key] === next) return root;
|
|
3824
|
+
const newRoot = cloneNode(root);
|
|
3825
|
+
let cursor = newRoot;
|
|
3826
|
+
for (let i2 = 0; i2 < last; i2++) {
|
|
3827
|
+
const child = cloneNode(cursor[fullPath[i2]]);
|
|
3828
|
+
cursor[fullPath[i2]] = child;
|
|
3829
|
+
cursor = child;
|
|
3830
|
+
}
|
|
3831
|
+
cursor[key] = next;
|
|
3832
|
+
markChanged();
|
|
3833
|
+
return newRoot;
|
|
3834
|
+
}
|
|
3835
|
+
function listKeysOf(entry) {
|
|
3836
|
+
if (!isPlainObject(entry)) return [];
|
|
3837
|
+
const keys = [];
|
|
3838
|
+
if (typeof entry._id === "string") keys.push(entry._id);
|
|
3839
|
+
const first = Array.isArray(entry.items) ? entry.items[0] : null;
|
|
3840
|
+
if (isPlainObject(first) && typeof first._id === "string") {
|
|
3841
|
+
keys.push(first._id);
|
|
3842
|
+
}
|
|
3843
|
+
return keys;
|
|
3844
|
+
}
|
|
3845
|
+
function buildText(entry, template) {
|
|
3846
|
+
const body = typeof entry.body === "string" ? entry.body : "";
|
|
3847
|
+
const full = {
|
|
3848
|
+
__typename: "Text",
|
|
3849
|
+
_id: entry._id,
|
|
3850
|
+
body,
|
|
3851
|
+
html: body,
|
|
3852
|
+
text: plainTextFromHtml(body),
|
|
3853
|
+
format: entry.format ?? null
|
|
3854
|
+
};
|
|
3855
|
+
if (!template) return full;
|
|
3856
|
+
const out = {};
|
|
3857
|
+
for (const key of Object.keys(template)) {
|
|
3858
|
+
out[key] = key in full ? full[key] : null;
|
|
3859
|
+
}
|
|
3860
|
+
return out;
|
|
3861
|
+
}
|
|
3862
|
+
function syncList(current, entries, build) {
|
|
3863
|
+
if (current === null || current === void 0) {
|
|
3864
|
+
const built = syncList([], entries, build);
|
|
3865
|
+
return built.length ? built : current;
|
|
3866
|
+
}
|
|
3867
|
+
if (!Array.isArray(current)) return current;
|
|
3868
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
3869
|
+
const templates = /* @__PURE__ */ new Map();
|
|
3870
|
+
for (const c2 of current) {
|
|
3871
|
+
for (const key of listKeysOf(c2)) byKey.set(key, c2);
|
|
3872
|
+
if (isPlainObject(c2) && typeof c2.__typename === "string") {
|
|
3873
|
+
if (!templates.has(c2.__typename)) templates.set(c2.__typename, c2);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
if (!entries.length) return current.length ? [] : current;
|
|
3877
|
+
if (current.length && !byKey.size) return current;
|
|
3878
|
+
if (current.length && !entries.some((e2) => byKey.has(e2._id))) return current;
|
|
3879
|
+
const next = [];
|
|
3880
|
+
for (const entry of entries) {
|
|
3881
|
+
const existing = byKey.get(entry._id);
|
|
3882
|
+
const built = existing === void 0 && entry.fields ? build?.(entry) : void 0;
|
|
3883
|
+
if (existing !== void 0) {
|
|
3884
|
+
next.push(existing);
|
|
3885
|
+
} else if (built) {
|
|
3886
|
+
next.push(built);
|
|
3887
|
+
} else if (entry.__typename === "Text") {
|
|
3888
|
+
next.push(buildText(entry, templates.get("Text")));
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
const same = next.length === current.length && next.every((n2, i2) => n2 === current[i2]);
|
|
3892
|
+
return same ? current : next;
|
|
3893
|
+
}
|
|
3894
|
+
function syncListSnapshot(snapshot, index, itemId, field, entries) {
|
|
3895
|
+
const paths = index.get(itemId);
|
|
3896
|
+
if (!paths?.length) return snapshot;
|
|
3897
|
+
const fieldPath = field.split(".");
|
|
3898
|
+
if (fieldPath.some((s2) => FORBIDDEN_SEGMENTS.has(s2))) return snapshot;
|
|
3899
|
+
let root = snapshot;
|
|
3900
|
+
for (const path of paths) {
|
|
3901
|
+
root = syncListAtPath(root, [...path, ...fieldPath], entries);
|
|
3902
|
+
}
|
|
3903
|
+
return root;
|
|
3904
|
+
}
|
|
3905
|
+
function syncListAtPath(root, fullPath, entries, build) {
|
|
3906
|
+
if (!fullPath.length) return root;
|
|
3907
|
+
if (fullPath.some((s2) => FORBIDDEN_SEGMENTS.has(String(s2)))) return root;
|
|
3908
|
+
let node = root;
|
|
3909
|
+
for (const key of fullPath) {
|
|
3910
|
+
node = node !== null && typeof node === "object" ? node[key] : void 0;
|
|
3911
|
+
}
|
|
3912
|
+
const next = syncList(node, entries, build);
|
|
3913
|
+
if (next === node) return root;
|
|
3914
|
+
const newRoot = cloneNode(root);
|
|
3915
|
+
let cursor = newRoot;
|
|
3916
|
+
for (let i2 = 0; i2 < fullPath.length - 1; i2++) {
|
|
3917
|
+
const child = cloneNode(cursor[fullPath[i2]]);
|
|
3918
|
+
cursor[fullPath[i2]] = child;
|
|
3919
|
+
cursor = child;
|
|
3920
|
+
}
|
|
3921
|
+
cursor[fullPath[fullPath.length - 1]] = next;
|
|
3922
|
+
return newRoot;
|
|
3923
|
+
}
|
|
3924
|
+
function keepEditLink(current, value) {
|
|
3925
|
+
if (typeof current !== "string" || typeof value !== "string") return value;
|
|
3926
|
+
const { encoded } = (0, import_stega5.vercelStegaSplit)(current);
|
|
3927
|
+
if (!encoded || (0, import_stega5.vercelStegaSplit)(value).encoded) return value;
|
|
3928
|
+
return value + encoded;
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
// src/core/live-preview/query-document.ts
|
|
3932
|
+
var NAME_START = /[_A-Za-z]/;
|
|
3933
|
+
var NAME_CONTINUE = /[_0-9A-Za-z]/;
|
|
3934
|
+
var PUNCT = /* @__PURE__ */ new Set([
|
|
3935
|
+
"!",
|
|
3936
|
+
"$",
|
|
3937
|
+
"&",
|
|
3938
|
+
"(",
|
|
3939
|
+
")",
|
|
3940
|
+
":",
|
|
3941
|
+
"=",
|
|
3942
|
+
"@",
|
|
3943
|
+
"[",
|
|
3944
|
+
"]",
|
|
3945
|
+
"{",
|
|
3946
|
+
"|",
|
|
3947
|
+
"}"
|
|
3948
|
+
]);
|
|
3949
|
+
function tokenize(source) {
|
|
3950
|
+
const tokens = [];
|
|
3951
|
+
let i2 = 0;
|
|
3952
|
+
const n2 = source.length;
|
|
3953
|
+
while (i2 < n2) {
|
|
3954
|
+
const ch = source[i2];
|
|
3955
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "," || ch === "\uFEFF") {
|
|
3956
|
+
i2++;
|
|
3957
|
+
} else if (ch === "#") {
|
|
3958
|
+
while (i2 < n2 && source[i2] !== "\n" && source[i2] !== "\r") i2++;
|
|
3959
|
+
} else if (ch === "." && source.startsWith("...", i2)) {
|
|
3960
|
+
tokens.push({ type: "punct", value: "..." });
|
|
3961
|
+
i2 += 3;
|
|
3962
|
+
} else if (PUNCT.has(ch)) {
|
|
3963
|
+
tokens.push({ type: "punct", value: ch });
|
|
3964
|
+
i2++;
|
|
3965
|
+
} else if (NAME_START.test(ch)) {
|
|
3966
|
+
const start = i2;
|
|
3967
|
+
while (i2 < n2 && NAME_CONTINUE.test(source[i2])) i2++;
|
|
3968
|
+
tokens.push({ type: "name", value: source.slice(start, i2) });
|
|
3969
|
+
} else if (ch === '"') {
|
|
3970
|
+
if (source.startsWith('"""', i2)) {
|
|
3971
|
+
i2 += 3;
|
|
3972
|
+
while (i2 < n2 && !source.startsWith('"""', i2)) {
|
|
3973
|
+
i2 += source.startsWith('\\"""', i2) ? 4 : 1;
|
|
3974
|
+
}
|
|
3975
|
+
if (i2 >= n2) throw new SyntaxError("Unterminated block string");
|
|
3976
|
+
i2 += 3;
|
|
3977
|
+
} else {
|
|
3978
|
+
i2++;
|
|
3979
|
+
while (i2 < n2 && source[i2] !== '"') {
|
|
3980
|
+
if (source[i2] === "\n" || source[i2] === "\r") {
|
|
3981
|
+
throw new SyntaxError("Unterminated string");
|
|
3982
|
+
}
|
|
3983
|
+
i2 += source[i2] === "\\" ? 2 : 1;
|
|
3984
|
+
}
|
|
3985
|
+
if (i2 >= n2) throw new SyntaxError("Unterminated string");
|
|
3986
|
+
i2++;
|
|
3987
|
+
}
|
|
3988
|
+
tokens.push({ type: "value" });
|
|
3989
|
+
} else if (ch === "-" || ch >= "0" && ch <= "9") {
|
|
3990
|
+
i2++;
|
|
3991
|
+
while (i2 < n2 && /[0-9.eE+-]/.test(source[i2])) i2++;
|
|
3992
|
+
tokens.push({ type: "value" });
|
|
3993
|
+
} else {
|
|
3994
|
+
throw new SyntaxError(`Unexpected character "${ch}"`);
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
return tokens;
|
|
3998
|
+
}
|
|
3999
|
+
function parseSource(source, operationName) {
|
|
4000
|
+
const tokens = tokenize(source);
|
|
4001
|
+
let pos = 0;
|
|
4002
|
+
const peek = () => tokens[pos];
|
|
4003
|
+
const isPunct = (value) => {
|
|
4004
|
+
const t3 = tokens[pos];
|
|
4005
|
+
return t3?.type === "punct" && t3.value === value;
|
|
4006
|
+
};
|
|
4007
|
+
const expectPunct = (value) => {
|
|
4008
|
+
if (!isPunct(value)) throw new SyntaxError(`Expected "${value}"`);
|
|
4009
|
+
pos++;
|
|
4010
|
+
};
|
|
4011
|
+
const expectName = () => {
|
|
4012
|
+
const t3 = tokens[pos];
|
|
4013
|
+
if (t3?.type !== "name") throw new SyntaxError("Expected a name");
|
|
4014
|
+
pos++;
|
|
4015
|
+
return t3.value;
|
|
4016
|
+
};
|
|
4017
|
+
const skipParens = () => {
|
|
4018
|
+
expectPunct("(");
|
|
4019
|
+
let depth = 1;
|
|
4020
|
+
while (depth > 0) {
|
|
4021
|
+
const t3 = tokens[pos++];
|
|
4022
|
+
if (!t3) throw new SyntaxError("Unbalanced parentheses");
|
|
4023
|
+
if (t3.type === "punct" && t3.value === "(") depth++;
|
|
4024
|
+
else if (t3.type === "punct" && t3.value === ")") depth--;
|
|
4025
|
+
}
|
|
4026
|
+
};
|
|
4027
|
+
const skipDirectives = () => {
|
|
4028
|
+
while (isPunct("@")) {
|
|
4029
|
+
pos++;
|
|
4030
|
+
expectName();
|
|
4031
|
+
if (isPunct("(")) skipParens();
|
|
4032
|
+
}
|
|
4033
|
+
};
|
|
4034
|
+
const parseSelectionSet = () => {
|
|
4035
|
+
expectPunct("{");
|
|
4036
|
+
const selections = [];
|
|
4037
|
+
while (!isPunct("}")) {
|
|
4038
|
+
if (!peek()) throw new SyntaxError("Unterminated selection set");
|
|
4039
|
+
selections.push(parseSelection());
|
|
4040
|
+
}
|
|
4041
|
+
pos++;
|
|
4042
|
+
return selections;
|
|
4043
|
+
};
|
|
4044
|
+
const parseSelection = () => {
|
|
4045
|
+
if (isPunct("...")) {
|
|
4046
|
+
pos++;
|
|
4047
|
+
const t3 = peek();
|
|
4048
|
+
if (t3?.type === "name" && t3.value !== "on") {
|
|
4049
|
+
pos++;
|
|
4050
|
+
skipDirectives();
|
|
4051
|
+
return { kind: "spread", name: t3.value };
|
|
4052
|
+
}
|
|
4053
|
+
let on = null;
|
|
4054
|
+
if (t3?.type === "name") {
|
|
4055
|
+
pos++;
|
|
4056
|
+
on = expectName();
|
|
4057
|
+
}
|
|
4058
|
+
skipDirectives();
|
|
4059
|
+
return { kind: "inline", on, selections: parseSelectionSet() };
|
|
4060
|
+
}
|
|
4061
|
+
let name = expectName();
|
|
4062
|
+
const key = name;
|
|
4063
|
+
if (isPunct(":")) {
|
|
4064
|
+
pos++;
|
|
4065
|
+
name = expectName();
|
|
4066
|
+
}
|
|
4067
|
+
let hasArgs = false;
|
|
4068
|
+
if (isPunct("(")) {
|
|
4069
|
+
hasArgs = true;
|
|
4070
|
+
skipParens();
|
|
4071
|
+
}
|
|
4072
|
+
skipDirectives();
|
|
4073
|
+
const selections = isPunct("{") ? parseSelectionSet() : null;
|
|
4074
|
+
return { kind: "field", name, key, hasArgs, selections };
|
|
4075
|
+
};
|
|
4076
|
+
const fragments = /* @__PURE__ */ new Map();
|
|
4077
|
+
const operations = [];
|
|
4078
|
+
while (pos < tokens.length) {
|
|
4079
|
+
if (isPunct("{")) {
|
|
4080
|
+
operations.push({ name: null, selections: parseSelectionSet() });
|
|
4081
|
+
continue;
|
|
4082
|
+
}
|
|
4083
|
+
const keyword = expectName();
|
|
4084
|
+
if (keyword === "fragment") {
|
|
4085
|
+
const name = expectName();
|
|
4086
|
+
if (expectName() !== "on") throw new SyntaxError('Expected "on"');
|
|
4087
|
+
const on = expectName();
|
|
4088
|
+
skipDirectives();
|
|
4089
|
+
fragments.set(name, { on, selections: parseSelectionSet() });
|
|
4090
|
+
} else if (keyword === "query" || keyword === "mutation" || keyword === "subscription") {
|
|
4091
|
+
const t3 = peek();
|
|
4092
|
+
let name = null;
|
|
4093
|
+
if (t3?.type === "name") name = expectName();
|
|
4094
|
+
if (isPunct("(")) skipParens();
|
|
4095
|
+
skipDirectives();
|
|
4096
|
+
operations.push({ name, selections: parseSelectionSet() });
|
|
4097
|
+
} else {
|
|
4098
|
+
throw new SyntaxError(`Unexpected "${keyword}"`);
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
return { operation: pickOperation(operations, operationName), fragments };
|
|
4102
|
+
}
|
|
4103
|
+
function pickOperation(operations, operationName) {
|
|
4104
|
+
const op = operationName ? operations.find((o2) => o2.name === operationName) : operations[0];
|
|
4105
|
+
if (!op) throw new SyntaxError("The document has no operation");
|
|
4106
|
+
return op.selections;
|
|
4107
|
+
}
|
|
4108
|
+
function fromAstSelections(set) {
|
|
4109
|
+
return set.selections.map((s2) => {
|
|
4110
|
+
if (s2.kind === "FragmentSpread")
|
|
4111
|
+
return { kind: "spread", name: s2.name.value };
|
|
4112
|
+
if (s2.kind === "InlineFragment") {
|
|
4113
|
+
return {
|
|
4114
|
+
kind: "inline",
|
|
4115
|
+
on: s2.typeCondition?.name.value ?? null,
|
|
4116
|
+
selections: fromAstSelections(s2.selectionSet)
|
|
4117
|
+
};
|
|
4118
|
+
}
|
|
4119
|
+
const name = s2.name.value;
|
|
4120
|
+
return {
|
|
4121
|
+
kind: "field",
|
|
4122
|
+
name,
|
|
4123
|
+
key: s2.alias?.value ?? name,
|
|
4124
|
+
hasArgs: Boolean(s2.arguments?.length),
|
|
4125
|
+
selections: s2.selectionSet ? fromAstSelections(s2.selectionSet) : null
|
|
4126
|
+
};
|
|
4127
|
+
});
|
|
4128
|
+
}
|
|
4129
|
+
function fromAst(doc, operationName) {
|
|
4130
|
+
const fragments = /* @__PURE__ */ new Map();
|
|
4131
|
+
const operations = [];
|
|
4132
|
+
for (const def of doc.definitions ?? []) {
|
|
4133
|
+
if (def.kind === "FragmentDefinition" && def.name && def.typeCondition && def.selectionSet) {
|
|
4134
|
+
fragments.set(def.name.value, {
|
|
4135
|
+
on: def.typeCondition.name.value,
|
|
4136
|
+
selections: fromAstSelections(def.selectionSet)
|
|
4137
|
+
});
|
|
4138
|
+
} else if (def.kind === "OperationDefinition" && def.selectionSet) {
|
|
4139
|
+
operations.push({
|
|
4140
|
+
name: def.name?.value ?? null,
|
|
4141
|
+
selections: fromAstSelections(def.selectionSet)
|
|
4142
|
+
});
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
return { operation: pickOperation(operations, operationName), fragments };
|
|
4146
|
+
}
|
|
4147
|
+
var astCache = /* @__PURE__ */ new WeakMap();
|
|
4148
|
+
var sourceCache = /* @__PURE__ */ new Map();
|
|
4149
|
+
var SOURCE_CACHE_LIMIT = 200;
|
|
4150
|
+
function readQueryDocument(query, operationName) {
|
|
4151
|
+
const cacheKey = operationName ?? "";
|
|
4152
|
+
if (typeof query === "object" && query !== null && query.kind === "Document") {
|
|
4153
|
+
let byName = astCache.get(query);
|
|
4154
|
+
if (!byName) astCache.set(query, byName = /* @__PURE__ */ new Map());
|
|
4155
|
+
let parsed2 = byName.get(cacheKey);
|
|
4156
|
+
if (!parsed2) {
|
|
4157
|
+
parsed2 = fromAst(
|
|
4158
|
+
query,
|
|
4159
|
+
operationName
|
|
4160
|
+
);
|
|
4161
|
+
byName.set(cacheKey, parsed2);
|
|
4162
|
+
}
|
|
4163
|
+
return parsed2;
|
|
4164
|
+
}
|
|
4165
|
+
const source = String(query);
|
|
4166
|
+
const sourceKey = `${cacheKey}
|
|
4167
|
+
${source}`;
|
|
4168
|
+
let parsed = sourceCache.get(sourceKey);
|
|
4169
|
+
if (!parsed) {
|
|
4170
|
+
parsed = parseSource(source, operationName);
|
|
4171
|
+
if (sourceCache.size >= SOURCE_CACHE_LIMIT) {
|
|
4172
|
+
sourceCache.delete(sourceCache.keys().next().value);
|
|
4173
|
+
}
|
|
4174
|
+
sourceCache.set(sourceKey, parsed);
|
|
4175
|
+
}
|
|
4176
|
+
return parsed;
|
|
4177
|
+
}
|
|
4178
|
+
var fieldsCache = /* @__PURE__ */ new WeakMap();
|
|
4179
|
+
function collectFields(selections, typename, doc, options = {}) {
|
|
4180
|
+
const cacheKey = `${options.strict ? "s" : "l"}:${typename ?? ""}`;
|
|
4181
|
+
let cached = fieldsCache.get(selections);
|
|
4182
|
+
if (!cached) {
|
|
4183
|
+
cached = /* @__PURE__ */ new Map();
|
|
4184
|
+
fieldsCache.set(selections, cached);
|
|
4185
|
+
}
|
|
4186
|
+
const hit = cached.get(cacheKey);
|
|
4187
|
+
if (hit) return hit;
|
|
4188
|
+
const fields = resolveFields(selections, typename, doc, options);
|
|
4189
|
+
cached.set(cacheKey, fields);
|
|
4190
|
+
return fields;
|
|
4191
|
+
}
|
|
4192
|
+
function resolveFields(selections, typename, doc, options) {
|
|
4193
|
+
const exact = /* @__PURE__ */ new Map();
|
|
4194
|
+
const loose = /* @__PURE__ */ new Map();
|
|
4195
|
+
const merged = /* @__PURE__ */ new Map();
|
|
4196
|
+
const walkedMatched = /* @__PURE__ */ new Set();
|
|
4197
|
+
const walkedLoose = /* @__PURE__ */ new Set();
|
|
4198
|
+
const add = (target, s2) => {
|
|
4199
|
+
let field = target.get(s2.key);
|
|
4200
|
+
if (!field) {
|
|
4201
|
+
field = { name: s2.name, key: s2.key, hasArgs: s2.hasArgs, selections: [] };
|
|
4202
|
+
target.set(s2.key, field);
|
|
4203
|
+
merged.set(field, /* @__PURE__ */ new Set());
|
|
4204
|
+
}
|
|
4205
|
+
const done = merged.get(field);
|
|
4206
|
+
if (s2.selections && !done.has(s2.selections)) {
|
|
4207
|
+
done.add(s2.selections);
|
|
4208
|
+
field.selections = [...field.selections, ...s2.selections];
|
|
4209
|
+
}
|
|
4210
|
+
};
|
|
4211
|
+
const applies = (on, list) => conditionMatches(on, typename) || typename !== void 0 && typeConditions(list, doc).includes(typename);
|
|
4212
|
+
const walk2 = (list, matched) => {
|
|
4213
|
+
for (const s2 of list) {
|
|
4214
|
+
if (s2.kind === "field") {
|
|
4215
|
+
add(matched ? exact : loose, s2);
|
|
4216
|
+
} else if (s2.kind === "inline") {
|
|
4217
|
+
walk2(s2.selections, matched && applies(s2.on, s2.selections));
|
|
4218
|
+
} else {
|
|
4219
|
+
const fragment = doc.fragments.get(s2.name);
|
|
4220
|
+
if (!fragment) continue;
|
|
4221
|
+
const asMatched = matched && applies(fragment.on, fragment.selections);
|
|
4222
|
+
if (walkedMatched.has(fragment)) continue;
|
|
4223
|
+
if (!asMatched && walkedLoose.has(fragment)) continue;
|
|
4224
|
+
(asMatched ? walkedMatched : walkedLoose).add(fragment);
|
|
4225
|
+
walk2(fragment.selections, asMatched);
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
};
|
|
4229
|
+
walk2(selections, true);
|
|
4230
|
+
if (!options.strict) {
|
|
4231
|
+
for (const [key, field] of loose) {
|
|
4232
|
+
if (!exact.has(key)) exact.set(key, field);
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
return exact;
|
|
4236
|
+
}
|
|
4237
|
+
function conditionMatches(on, typename) {
|
|
4238
|
+
return on === null || typename === void 0 || on === typename;
|
|
4239
|
+
}
|
|
4240
|
+
var conditionsCache = /* @__PURE__ */ new WeakMap();
|
|
4241
|
+
function typeConditions(selections, doc) {
|
|
4242
|
+
const hit = conditionsCache.get(selections);
|
|
4243
|
+
if (hit) return hit;
|
|
4244
|
+
const names = /* @__PURE__ */ new Set();
|
|
4245
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4246
|
+
const walk2 = (list) => {
|
|
4247
|
+
for (const s2 of list) {
|
|
4248
|
+
if (s2.kind === "inline") {
|
|
4249
|
+
if (s2.on) names.add(s2.on);
|
|
4250
|
+
walk2(s2.selections);
|
|
4251
|
+
} else if (s2.kind === "spread") {
|
|
4252
|
+
const fragment = doc.fragments.get(s2.name);
|
|
4253
|
+
if (!fragment || seen.has(fragment)) continue;
|
|
4254
|
+
seen.add(fragment);
|
|
4255
|
+
names.add(fragment.on);
|
|
4256
|
+
walk2(fragment.selections);
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
4259
|
+
};
|
|
4260
|
+
walk2(selections);
|
|
4261
|
+
const conditions = [...names];
|
|
4262
|
+
conditionsCache.set(selections, conditions);
|
|
4263
|
+
return conditions;
|
|
4264
|
+
}
|
|
4265
|
+
|
|
4266
|
+
// src/core/live-preview/validate.ts
|
|
4267
|
+
function isScalar(v2) {
|
|
4268
|
+
return typeof v2 === "string" || typeof v2 === "number" || typeof v2 === "boolean";
|
|
4269
|
+
}
|
|
4270
|
+
var MAX_DEPTH = 3;
|
|
4271
|
+
var MAX_ENTRIES = 50;
|
|
4272
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
4273
|
+
var SAFE_URL = /^https?:\/\//i;
|
|
4274
|
+
var URL_KEY = /^(?:url|href|src|link)$|(?:_url|Url)$/;
|
|
4275
|
+
var URL_IGNORED_ANYWHERE = /[\t\n\r]/g;
|
|
4276
|
+
var SCRIPT_SCHEME = /^(?:javascript|vbscript|data):/i;
|
|
4277
|
+
function isSafeLinkValue(value) {
|
|
4278
|
+
const stripped = value.replace(URL_IGNORED_ANYWHERE, "");
|
|
4279
|
+
let start = 0;
|
|
4280
|
+
while (start < stripped.length && stripped.charCodeAt(start) <= 32) start++;
|
|
4281
|
+
return !SCRIPT_SCHEME.test(stripped.slice(start));
|
|
4282
|
+
}
|
|
4283
|
+
function isUrlKey(key) {
|
|
4284
|
+
return URL_KEY.test(key);
|
|
4285
|
+
}
|
|
4286
|
+
function isSafeKeyValue(key, value) {
|
|
4287
|
+
if (typeof value !== "string") return true;
|
|
4288
|
+
if (key === "url") return SAFE_URL.test(value);
|
|
4289
|
+
return !isUrlKey(key) || isSafeLinkValue(value);
|
|
4290
|
+
}
|
|
4291
|
+
function isLiveValue(v2, depth = 0) {
|
|
4292
|
+
if (v2 === null || isScalar(v2)) return true;
|
|
4293
|
+
if (depth >= MAX_DEPTH) return false;
|
|
4294
|
+
if (Array.isArray(v2)) {
|
|
4295
|
+
return depth === 0 && v2.length <= MAX_ENTRIES && v2.every((entry) => isPlainRecord(entry) && isLiveValue(entry, depth + 1));
|
|
4296
|
+
}
|
|
4297
|
+
if (!isPlainRecord(v2)) return false;
|
|
4298
|
+
const keys = Object.keys(v2);
|
|
4299
|
+
if (keys.length > MAX_ENTRIES) return false;
|
|
4300
|
+
return keys.every((key) => {
|
|
4301
|
+
if (FORBIDDEN_KEYS.has(key)) return false;
|
|
4302
|
+
const value = v2[key];
|
|
4303
|
+
if (!isSafeKeyValue(key, value)) return false;
|
|
4304
|
+
return !Array.isArray(value) && isLiveValue(value, depth + 1);
|
|
4305
|
+
});
|
|
4306
|
+
}
|
|
4307
|
+
function isLiveFieldChange(field, value) {
|
|
4308
|
+
if (!isLiveValue(value)) return false;
|
|
4309
|
+
const name = field.slice(field.lastIndexOf(".") + 1);
|
|
4310
|
+
return typeof value !== "string" || !isUrlKey(name) || isSafeLinkValue(value);
|
|
4311
|
+
}
|
|
4312
|
+
var MAX_LIST_ENTRIES = 200;
|
|
4313
|
+
function parseListEntries(value) {
|
|
4314
|
+
if (!Array.isArray(value) || value.length > MAX_LIST_ENTRIES) return null;
|
|
4315
|
+
const list = [];
|
|
4316
|
+
for (const raw of value) {
|
|
4317
|
+
if (!isPlainRecord(raw) || typeof raw._id !== "string" || !raw._id) {
|
|
4318
|
+
return null;
|
|
4319
|
+
}
|
|
4320
|
+
const entry = { _id: raw._id };
|
|
4321
|
+
if (typeof raw.__typename === "string") entry.__typename = raw.__typename;
|
|
4322
|
+
if (typeof raw.body === "string") entry.body = raw.body;
|
|
4323
|
+
if (typeof raw.format === "string" || raw.format === null) {
|
|
4324
|
+
entry.format = raw.format;
|
|
4325
|
+
}
|
|
4326
|
+
if (typeof raw.typeName === "string" && raw.typeName.length <= 200) {
|
|
4327
|
+
entry.typeName = raw.typeName;
|
|
4328
|
+
}
|
|
4329
|
+
if (raw.fields !== void 0) {
|
|
4330
|
+
if (!isPlainRecord(raw.fields) || !isFieldValue(raw.fields, 0)) {
|
|
4331
|
+
return null;
|
|
4332
|
+
}
|
|
4333
|
+
entry.fields = raw.fields;
|
|
4334
|
+
}
|
|
4335
|
+
list.push(entry);
|
|
4336
|
+
}
|
|
4337
|
+
return list;
|
|
4338
|
+
}
|
|
4339
|
+
function isPlainRecord(v2) {
|
|
4340
|
+
return v2 !== null && typeof v2 === "object" && Object.getPrototypeOf(v2) === Object.prototype;
|
|
4341
|
+
}
|
|
4342
|
+
var MAX_FIELD_DEPTH = 6;
|
|
4343
|
+
var MAX_FIELD_KEYS = 100;
|
|
4344
|
+
function isFieldValue(v2, depth) {
|
|
4345
|
+
if (v2 === null || isScalar(v2)) return true;
|
|
4346
|
+
if (depth >= MAX_FIELD_DEPTH) return false;
|
|
4347
|
+
if (Array.isArray(v2)) {
|
|
4348
|
+
return v2.length <= MAX_ENTRIES && v2.every(
|
|
4349
|
+
(entry) => isScalar(entry) || isPlainRecord(entry) && isFieldValue(entry, depth + 1)
|
|
4350
|
+
);
|
|
4351
|
+
}
|
|
4352
|
+
if (!isPlainRecord(v2)) return false;
|
|
4353
|
+
const keys = Object.keys(v2);
|
|
4354
|
+
if (keys.length > MAX_FIELD_KEYS) return false;
|
|
4355
|
+
return keys.every((key) => {
|
|
4356
|
+
if (FORBIDDEN_KEYS.has(key)) return false;
|
|
4357
|
+
const value = v2[key];
|
|
4358
|
+
if (!isSafeKeyValue(key, value)) return false;
|
|
4359
|
+
return isFieldValue(value, depth + 1);
|
|
4360
|
+
});
|
|
4361
|
+
}
|
|
4362
|
+
var MAX_EDIT_LENGTH = 1e3;
|
|
4363
|
+
function parseEditTarget(raw) {
|
|
4364
|
+
if (!isPlainRecord(raw)) return void 0;
|
|
4365
|
+
const { item, field } = raw;
|
|
4366
|
+
if (typeof item !== "string" || !item || item.length > MAX_EDIT_LENGTH) {
|
|
4367
|
+
return void 0;
|
|
4368
|
+
}
|
|
4369
|
+
if (typeof field !== "string" || !field || field.length > MAX_EDIT_LENGTH) {
|
|
4370
|
+
return void 0;
|
|
4371
|
+
}
|
|
4372
|
+
return { item, field };
|
|
4373
|
+
}
|
|
4374
|
+
|
|
4375
|
+
// src/core/live-preview/normalize-query.ts
|
|
4376
|
+
var debug11 = createScopedLogger("live-preview:normalize");
|
|
4377
|
+
var MAX_LOG_ENTRIES = 500;
|
|
4378
|
+
var MAX_LOG_SOURCE_LENGTH = 1e6;
|
|
4379
|
+
var INDEX_SEGMENT3 = /^\d+$/;
|
|
4380
|
+
var FORBIDDEN_SEGMENTS2 = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
4381
|
+
var ENVELOPE_KEYS = /* @__PURE__ */ new Set(["data", "errors", "extensions"]);
|
|
4382
|
+
function parsePatchLog(raw) {
|
|
4383
|
+
let value = raw;
|
|
4384
|
+
if (typeof value === "string") {
|
|
4385
|
+
if (!value || value.length > MAX_LOG_SOURCE_LENGTH) return [];
|
|
4386
|
+
try {
|
|
4387
|
+
value = JSON.parse(value);
|
|
4388
|
+
} catch {
|
|
4389
|
+
return [];
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
if (!Array.isArray(value)) return [];
|
|
4393
|
+
const log = [];
|
|
4394
|
+
for (const entry of value.slice(-MAX_LOG_ENTRIES)) {
|
|
4395
|
+
if (!isPlainRecord(entry)) continue;
|
|
4396
|
+
const { type, itemId, field } = entry;
|
|
4397
|
+
if (typeof itemId !== "string" || !itemId) continue;
|
|
4398
|
+
if (typeof field !== "string" || !field) continue;
|
|
4399
|
+
if (type === "field" && isLiveFieldChange(field, entry.value)) {
|
|
4400
|
+
const edit = parseEditTarget(entry.edit);
|
|
4401
|
+
log.push(
|
|
4402
|
+
edit ? { type, itemId, field, value: entry.value, edit } : { type, itemId, field, value: entry.value }
|
|
4403
|
+
);
|
|
4404
|
+
} else if (type === "list") {
|
|
4405
|
+
const entries = parseListEntries(entry.entries);
|
|
4406
|
+
if (entries) log.push({ type, itemId, field, entries });
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
4409
|
+
return log;
|
|
4410
|
+
}
|
|
4411
|
+
function normalizeQuery(query, result, options = {}) {
|
|
4412
|
+
return applyPatchLog(
|
|
4413
|
+
query,
|
|
4414
|
+
result,
|
|
4415
|
+
parsePatchLog(options.log),
|
|
4416
|
+
options.operationName
|
|
4417
|
+
);
|
|
4418
|
+
}
|
|
4419
|
+
function applyPatchLog(query, result, log, operationName) {
|
|
4420
|
+
if (!log.length || result === null || typeof result !== "object") {
|
|
4421
|
+
return result;
|
|
4422
|
+
}
|
|
4423
|
+
if (isEnvelope(result)) {
|
|
4424
|
+
const data = applyPatchLog(query, result.data, log, operationName);
|
|
4425
|
+
return data === result.data ? result : { ...result, data };
|
|
4426
|
+
}
|
|
4427
|
+
let doc = null;
|
|
4428
|
+
try {
|
|
4429
|
+
doc = readQueryDocument(query, operationName);
|
|
4430
|
+
} catch (error) {
|
|
4431
|
+
debug11.log("could not read the query; applying fields by literal path", {
|
|
4432
|
+
error: String(error)
|
|
4433
|
+
});
|
|
4434
|
+
}
|
|
4435
|
+
let root = result;
|
|
4436
|
+
let index = null;
|
|
4437
|
+
for (const patch of log) {
|
|
4438
|
+
try {
|
|
4439
|
+
index ?? (index = indexSnapshot(root));
|
|
4440
|
+
const next = applyPatch(root, index, patch, doc, result);
|
|
4441
|
+
if (next !== root) {
|
|
4442
|
+
root = next;
|
|
4443
|
+
if (patch.type === "list" || !isScalarValue(patch.value)) {
|
|
4444
|
+
index = null;
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
} catch (error) {
|
|
4448
|
+
debug11.log("could not apply a change", {
|
|
4449
|
+
itemId: patch.itemId,
|
|
4450
|
+
field: patch.field,
|
|
4451
|
+
error: String(error)
|
|
4452
|
+
});
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4455
|
+
return root;
|
|
4456
|
+
}
|
|
4457
|
+
function isScalarValue(value) {
|
|
4458
|
+
return value === null || typeof value !== "object";
|
|
4459
|
+
}
|
|
4460
|
+
function applyPatch(root, index, patch, doc, result) {
|
|
4461
|
+
const ownerPaths = index.get(patch.itemId);
|
|
4462
|
+
if (!ownerPaths?.length) return root;
|
|
4463
|
+
const segments = patch.field.split(".");
|
|
4464
|
+
if (segments.some((s2) => !s2 || FORBIDDEN_SEGMENTS2.has(s2))) return root;
|
|
4465
|
+
const targets = [];
|
|
4466
|
+
const plain = /* @__PURE__ */ new Set();
|
|
4467
|
+
for (const ownerPath of ownerPaths) {
|
|
4468
|
+
if (doc) {
|
|
4469
|
+
const resolved = resolveTargets(root, doc, ownerPath, segments);
|
|
4470
|
+
root = resolved.root;
|
|
4471
|
+
targets.push(...resolved.targets);
|
|
4472
|
+
resolved.plain.forEach((target) => plain.add(target));
|
|
4473
|
+
} else {
|
|
4474
|
+
targets.push([...ownerPath, ...segments.map(toPathSegment)]);
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
if (!targets.length) {
|
|
4478
|
+
debug11.log("no field in the query matches", {
|
|
4479
|
+
itemId: patch.itemId,
|
|
4480
|
+
field: patch.field
|
|
4481
|
+
});
|
|
4482
|
+
return root;
|
|
4483
|
+
}
|
|
4484
|
+
if (patch.type === "field") {
|
|
4485
|
+
for (const target of targets) {
|
|
4486
|
+
const pushed = plain.has(target) && typeof patch.value === "string" ? plainTextFromHtml(patch.value) : patch.value;
|
|
4487
|
+
let value = keepEditLink(readAtPath(root, target), pushed);
|
|
4488
|
+
if (patch.edit && typeof value === "string" && !(0, import_stega6.vercelStegaSplit)(value).encoded) {
|
|
4489
|
+
value = editLinksFor(result, patch.edit.item)?.encode(
|
|
4490
|
+
value,
|
|
4491
|
+
patch.edit.field
|
|
4492
|
+
) ?? value;
|
|
4493
|
+
}
|
|
4494
|
+
root = writeAtPath(root, target, value);
|
|
4495
|
+
}
|
|
4496
|
+
} else {
|
|
4497
|
+
for (const target of targets) {
|
|
4498
|
+
const selections = doc ? selectionsAt(root, doc, target) : null;
|
|
4499
|
+
const build = doc && selections ? (entry) => buildEntry(doc, selections, entry, {
|
|
4500
|
+
links: editLinks(result, entry),
|
|
4501
|
+
lists: listKeysIn(result)
|
|
4502
|
+
}) : void 0;
|
|
4503
|
+
root = syncListAtPath(root, target, patch.entries, build);
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
return root;
|
|
4507
|
+
}
|
|
4508
|
+
function readAtPath(root, path) {
|
|
4509
|
+
let node = root;
|
|
4510
|
+
for (const step of path) {
|
|
4511
|
+
if (node === null || typeof node !== "object") return void 0;
|
|
4512
|
+
node = node[step];
|
|
4513
|
+
}
|
|
4514
|
+
return node;
|
|
4515
|
+
}
|
|
4516
|
+
function isEnvelope(value) {
|
|
4517
|
+
if (Array.isArray(value) || !("data" in value)) return false;
|
|
4518
|
+
return Object.keys(value).every((key) => ENVELOPE_KEYS.has(key));
|
|
4519
|
+
}
|
|
4520
|
+
function toPathSegment(segment) {
|
|
4521
|
+
return INDEX_SEGMENT3.test(segment) ? Number(segment) : segment;
|
|
4522
|
+
}
|
|
4523
|
+
function typenameOf(node) {
|
|
4524
|
+
const typename = node.__typename;
|
|
4525
|
+
return typeof typename === "string" ? typename : void 0;
|
|
4526
|
+
}
|
|
4527
|
+
function selectionsAt(root, doc, path) {
|
|
4528
|
+
let node = root;
|
|
4529
|
+
let selections = doc.operation;
|
|
4530
|
+
for (const step of path) {
|
|
4531
|
+
if (Array.isArray(node)) {
|
|
4532
|
+
node = node[step];
|
|
4533
|
+
continue;
|
|
4534
|
+
}
|
|
4535
|
+
if (node === null || typeof node !== "object") return null;
|
|
4536
|
+
const field = collectFields(selections, typenameOf(node), doc).get(
|
|
4537
|
+
String(step)
|
|
4538
|
+
);
|
|
4539
|
+
if (!field) return null;
|
|
4540
|
+
selections = field.selections;
|
|
4541
|
+
node = node[String(step)];
|
|
4542
|
+
}
|
|
4543
|
+
return selections;
|
|
4544
|
+
}
|
|
4545
|
+
function resolveTargets(root, doc, ownerPath, segments) {
|
|
4546
|
+
let owner = root;
|
|
4547
|
+
for (const step of ownerPath) {
|
|
4548
|
+
owner = owner[step];
|
|
4549
|
+
}
|
|
4550
|
+
const selections = selectionsAt(root, doc, ownerPath);
|
|
4551
|
+
if (!selections) return { root, targets: [], plain: /* @__PURE__ */ new Set() };
|
|
4552
|
+
let current = root;
|
|
4553
|
+
const out = [];
|
|
4554
|
+
const plain = /* @__PURE__ */ new Set();
|
|
4555
|
+
const walk2 = (node, sel, i2, path) => {
|
|
4556
|
+
if (i2 === segments.length) {
|
|
4557
|
+
out.push(path);
|
|
4558
|
+
return;
|
|
4559
|
+
}
|
|
4560
|
+
const segment = segments[i2];
|
|
4561
|
+
if (Array.isArray(node)) {
|
|
4562
|
+
if (INDEX_SEGMENT3.test(segment)) {
|
|
4563
|
+
const position = Number(segment);
|
|
4564
|
+
if (position < node.length) {
|
|
4565
|
+
walk2(node[position], sel, i2 + 1, [...path, position]);
|
|
4566
|
+
}
|
|
4567
|
+
} else if (segment === "items") {
|
|
4568
|
+
walk2(node, sel, i2 + 1, path);
|
|
4569
|
+
}
|
|
4570
|
+
return;
|
|
4571
|
+
}
|
|
4572
|
+
if (node === null || typeof node !== "object") return;
|
|
4573
|
+
const record = node;
|
|
4574
|
+
const wanted = graphqlFieldName(segment);
|
|
4575
|
+
const textBody = i2 === segments.length - 1 && segment === "body" && isTextEntry(record) ? TEXT_BODY_FIELDS : null;
|
|
4576
|
+
let matched = false;
|
|
4577
|
+
for (const field of collectFields(sel, typenameOf(node), doc).values()) {
|
|
4578
|
+
const alias = textBody?.find((f3) => f3.name === field.name);
|
|
4579
|
+
if (field.name !== segment && field.name !== wanted && !alias) continue;
|
|
4580
|
+
if (!Object.prototype.hasOwnProperty.call(record, field.key)) continue;
|
|
4581
|
+
matched = true;
|
|
4582
|
+
const fieldPath = [...path, field.key];
|
|
4583
|
+
if (alias?.plain) plain.add(fieldPath);
|
|
4584
|
+
let value = record[field.key];
|
|
4585
|
+
const next = segments[i2 + 1];
|
|
4586
|
+
if (value === null && field.selections.length && next !== void 0 && next !== "items" && !INDEX_SEGMENT3.test(next)) {
|
|
4587
|
+
const typename = resolveTypename(
|
|
4588
|
+
doc,
|
|
4589
|
+
field.selections,
|
|
4590
|
+
void 0,
|
|
4591
|
+
void 0
|
|
4592
|
+
);
|
|
4593
|
+
if (typename !== void 0) {
|
|
4594
|
+
value = fillObject(doc, field.selections, typename, {}, void 0, {
|
|
4595
|
+
links: null,
|
|
4596
|
+
lists: listKeysIn(root)
|
|
4597
|
+
});
|
|
4598
|
+
current = writeAtPath(current, fieldPath, value);
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4601
|
+
walk2(value, field.selections, i2 + 1, fieldPath);
|
|
4602
|
+
}
|
|
4603
|
+
if (!matched && segment === "items") walk2(node, sel, i2 + 1, path);
|
|
4604
|
+
};
|
|
4605
|
+
walk2(owner, selections, 0, ownerPath);
|
|
4606
|
+
return { root: current, targets: out, plain };
|
|
4607
|
+
}
|
|
4608
|
+
var NON_ALPHANUMERIC = /[^a-z0-9]/gi;
|
|
4609
|
+
var typeKey = (name) => name.replace(NON_ALPHANUMERIC, "").toLowerCase();
|
|
4610
|
+
function resolveTypename(doc, selections, typename, typeName) {
|
|
4611
|
+
const known = typeConditions(selections, doc);
|
|
4612
|
+
const explicit = typeof typename === "string" ? typename : void 0;
|
|
4613
|
+
if (!known.length) return explicit ?? null;
|
|
4614
|
+
if (explicit && known.includes(explicit)) return explicit;
|
|
4615
|
+
for (const candidate of [explicit, typeName]) {
|
|
4616
|
+
if (typeof candidate !== "string" || !candidate) continue;
|
|
4617
|
+
const match = known.find((name) => typeKey(name) === typeKey(candidate));
|
|
4618
|
+
if (match) return match;
|
|
4619
|
+
}
|
|
4620
|
+
const named = explicit !== void 0 || typeof typeName === "string";
|
|
4621
|
+
return known.length === 1 && !named ? known[0] : void 0;
|
|
4622
|
+
}
|
|
4623
|
+
function buildEntry(doc, selections, entry, ctx) {
|
|
4624
|
+
const typename = resolveTypename(
|
|
4625
|
+
doc,
|
|
4626
|
+
selections,
|
|
4627
|
+
entry.__typename,
|
|
4628
|
+
entry.typeName
|
|
4629
|
+
);
|
|
4630
|
+
if (typename === void 0) return void 0;
|
|
4631
|
+
return fillObject(
|
|
4632
|
+
doc,
|
|
4633
|
+
selections,
|
|
4634
|
+
typename,
|
|
4635
|
+
entry.fields ?? {},
|
|
4636
|
+
entry._id,
|
|
4637
|
+
ctx
|
|
4638
|
+
);
|
|
4639
|
+
}
|
|
4640
|
+
function fillObject(doc, selections, typename, values, id, ctx) {
|
|
4641
|
+
const out = {};
|
|
4642
|
+
const { links } = ctx;
|
|
4643
|
+
const lists = ctx.lists(typename);
|
|
4644
|
+
const edit = isPlainRecord(values.__edit) ? values.__edit : null;
|
|
4645
|
+
const apiIdOf = /* @__PURE__ */ new Map();
|
|
4646
|
+
for (const key of Object.keys(values)) {
|
|
4647
|
+
apiIdOf.set(key, key);
|
|
4648
|
+
const name = graphqlFieldName(key);
|
|
4649
|
+
if (!apiIdOf.has(name)) apiIdOf.set(name, key);
|
|
4650
|
+
}
|
|
4651
|
+
if (edit && isPlainRecord(edit.apiIds)) {
|
|
4652
|
+
for (const [name, apiId] of Object.entries(edit.apiIds)) {
|
|
4653
|
+
if (typeof apiId === "string") apiIdOf.set(name, apiId);
|
|
4654
|
+
}
|
|
4655
|
+
}
|
|
4656
|
+
const valueOf = (name) => {
|
|
4657
|
+
if (Object.prototype.hasOwnProperty.call(values, name)) return values[name];
|
|
4658
|
+
const key = apiIdOf.get(name);
|
|
4659
|
+
return key !== void 0 && key in values ? values[key] : void 0;
|
|
4660
|
+
};
|
|
4661
|
+
const editField = (name) => {
|
|
4662
|
+
if (!edit) return null;
|
|
4663
|
+
if (typeof edit.field === "string")
|
|
4664
|
+
return name === "body" ? edit.field : null;
|
|
4665
|
+
if (typeof edit.path !== "string") return null;
|
|
4666
|
+
const apiId = apiIdOf.get(name) ?? name;
|
|
4667
|
+
const text = Array.isArray(edit.text) ? edit.text : [];
|
|
4668
|
+
if (!text.includes(apiId) && !text.includes(name)) return null;
|
|
4669
|
+
return `${edit.path}.items.${apiId}`;
|
|
4670
|
+
};
|
|
4671
|
+
const fields = collectFields(selections, typename ?? void 0, doc, {
|
|
4672
|
+
strict: true
|
|
4673
|
+
});
|
|
4674
|
+
for (const field of fields.values()) {
|
|
4675
|
+
if (field.name === "__typename") {
|
|
4676
|
+
out[field.key] = typename;
|
|
4677
|
+
} else if (field.name === "_id") {
|
|
4678
|
+
out[field.key] = typeof id === "string" ? id : values._id ?? null;
|
|
4679
|
+
} else if (field.selections.length) {
|
|
4680
|
+
const value = valueOf(field.name);
|
|
4681
|
+
out[field.key] = value == null && lists.has(field.key) ? [] : fillNested(doc, field.selections, value, ctx);
|
|
4682
|
+
} else {
|
|
4683
|
+
const value = valueOf(field.name);
|
|
4684
|
+
if (Array.isArray(value) && value.every(isScalarItem)) {
|
|
4685
|
+
out[field.key] = value;
|
|
4686
|
+
continue;
|
|
4687
|
+
}
|
|
4688
|
+
if (value == null && lists.has(field.key)) {
|
|
4689
|
+
out[field.key] = [];
|
|
4690
|
+
continue;
|
|
4691
|
+
}
|
|
4692
|
+
const scalar = value === void 0 || value !== null && typeof value === "object" ? null : value;
|
|
4693
|
+
const target = typeof scalar === "string" ? editField(field.name) : null;
|
|
4694
|
+
out[field.key] = target && links ? links.encode(scalar, target) : scalar;
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4697
|
+
return out;
|
|
4698
|
+
}
|
|
4699
|
+
function fillNested(doc, selections, value, ctx) {
|
|
4700
|
+
const one = (item) => {
|
|
4701
|
+
if (!isPlainRecord(item)) return null;
|
|
4702
|
+
const typename = resolveTypename(
|
|
4703
|
+
doc,
|
|
4704
|
+
selections,
|
|
4705
|
+
item.__typename,
|
|
4706
|
+
item.typeName
|
|
4707
|
+
);
|
|
4708
|
+
if (typename === void 0) return null;
|
|
4709
|
+
return fillObject(doc, selections, typename, item, item._id, ctx);
|
|
4710
|
+
};
|
|
4711
|
+
if (Array.isArray(value)) return value.map(one).filter((v2) => v2 !== null);
|
|
4712
|
+
return value === void 0 ? null : one(value);
|
|
4713
|
+
}
|
|
4714
|
+
function isScalarItem(value) {
|
|
4715
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
4716
|
+
}
|
|
4717
|
+
var NO_KEYS = /* @__PURE__ */ new Set();
|
|
4718
|
+
var listKeysCache = /* @__PURE__ */ new WeakMap();
|
|
4719
|
+
function listKeysIn(result) {
|
|
4720
|
+
return (typename) => {
|
|
4721
|
+
if (!typename || result === null || typeof result !== "object") {
|
|
4722
|
+
return NO_KEYS;
|
|
4723
|
+
}
|
|
4724
|
+
let byType = listKeysCache.get(result);
|
|
4725
|
+
if (!byType) {
|
|
4726
|
+
byType = /* @__PURE__ */ new Map();
|
|
4727
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
4728
|
+
const walk2 = (node) => {
|
|
4729
|
+
if (node === null || typeof node !== "object" || seen.has(node)) return;
|
|
4730
|
+
seen.add(node);
|
|
4731
|
+
if (Array.isArray(node)) {
|
|
4732
|
+
node.forEach(walk2);
|
|
4733
|
+
return;
|
|
4734
|
+
}
|
|
4735
|
+
const record = node;
|
|
4736
|
+
const type = typeof record.__typename === "string" ? record.__typename : null;
|
|
4737
|
+
for (const [key, value] of Object.entries(record)) {
|
|
4738
|
+
if (type && Array.isArray(value)) {
|
|
4739
|
+
let keys = byType.get(type);
|
|
4740
|
+
if (!keys) byType.set(type, keys = /* @__PURE__ */ new Set());
|
|
4741
|
+
keys.add(key);
|
|
4742
|
+
}
|
|
4743
|
+
walk2(value);
|
|
4744
|
+
}
|
|
4745
|
+
};
|
|
4746
|
+
walk2(result);
|
|
4747
|
+
listKeysCache.set(result, byType);
|
|
4748
|
+
}
|
|
4749
|
+
return byType.get(typename) ?? NO_KEYS;
|
|
4750
|
+
};
|
|
4751
|
+
}
|
|
4752
|
+
var EDIT_PATH = /\/content\/edit\/[^/?#]+/;
|
|
4753
|
+
function editLinks(result, entry) {
|
|
4754
|
+
const edit = entry.fields?.__edit;
|
|
4755
|
+
if (!isPlainRecord(edit) || typeof edit.item !== "string") return null;
|
|
4756
|
+
return editLinksFor(result, edit.item);
|
|
4757
|
+
}
|
|
4758
|
+
var ITEM_ID = /^[A-Za-z0-9_-]+$/;
|
|
4759
|
+
function editLinksFor(result, item) {
|
|
4760
|
+
if (!ITEM_ID.test(item)) return null;
|
|
4761
|
+
const template = findEditLink(result);
|
|
4762
|
+
if (!template) return null;
|
|
4763
|
+
return {
|
|
4764
|
+
encode(text, field) {
|
|
4765
|
+
if ((0, import_stega6.vercelStegaSplit)(text).encoded) return text;
|
|
4766
|
+
let href;
|
|
4767
|
+
try {
|
|
4768
|
+
href = new URL(template.href);
|
|
4769
|
+
} catch {
|
|
4770
|
+
return text;
|
|
4771
|
+
}
|
|
4772
|
+
href.pathname = href.pathname.replace(
|
|
4773
|
+
EDIT_PATH,
|
|
4774
|
+
() => `/content/edit/${item}`
|
|
4775
|
+
);
|
|
4776
|
+
href.searchParams.set("field", field);
|
|
4777
|
+
const link = href.toString().replace(/%2E/gi, ".").replace(/%2F/gi, "/");
|
|
4778
|
+
return (0, import_stega6.vercelStegaCombine)(text, { origin: template.origin, href: link });
|
|
4779
|
+
}
|
|
4780
|
+
};
|
|
4781
|
+
}
|
|
4782
|
+
var linkCache = /* @__PURE__ */ new WeakMap();
|
|
4783
|
+
function findEditLink(root) {
|
|
4784
|
+
if (root === null || typeof root !== "object") return null;
|
|
4785
|
+
if (linkCache.has(root)) return linkCache.get(root) ?? null;
|
|
4786
|
+
let found = null;
|
|
4787
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
4788
|
+
const walk2 = (node) => {
|
|
4789
|
+
if (found) return;
|
|
4790
|
+
if (typeof node === "string") {
|
|
4791
|
+
if (!(0, import_stega6.vercelStegaSplit)(node).encoded) return;
|
|
4792
|
+
try {
|
|
4793
|
+
const decoded = (0, import_stega6.vercelStegaDecode)(node);
|
|
4794
|
+
if (typeof decoded?.origin === "string" && typeof decoded.href === "string" && EDIT_PATH.test(decoded.href)) {
|
|
4795
|
+
found = { origin: decoded.origin, href: decoded.href };
|
|
4796
|
+
}
|
|
4797
|
+
} catch {
|
|
4798
|
+
}
|
|
4799
|
+
return;
|
|
4800
|
+
}
|
|
4801
|
+
if (node === null || typeof node !== "object" || seen.has(node)) return;
|
|
4802
|
+
seen.add(node);
|
|
4803
|
+
for (const value of Object.values(node)) walk2(value);
|
|
4804
|
+
};
|
|
4805
|
+
walk2(root);
|
|
4806
|
+
linkCache.set(root, found);
|
|
4807
|
+
return found;
|
|
4808
|
+
}
|
|
4809
|
+
|
|
4810
|
+
// src/core/live-preview/edit-links.ts
|
|
4811
|
+
var import_stega7 = require("@vercel/stega");
|
|
4812
|
+
var ENTRY_PARAM = "entry";
|
|
4813
|
+
var TOP_LEVEL_ENTRY = /^[A-Za-z0-9_]+\.\d+$/;
|
|
4814
|
+
function entryIdOf(element) {
|
|
4815
|
+
if (!isRecord(element)) return void 0;
|
|
4816
|
+
if (typeof element._id === "string") return element._id;
|
|
4817
|
+
if (element.__typename !== "Assets" || !Array.isArray(element.items)) {
|
|
4818
|
+
return void 0;
|
|
4819
|
+
}
|
|
4820
|
+
const first = element.items[0];
|
|
4821
|
+
return isRecord(first) && typeof first._id === "string" ? first._id : void 0;
|
|
4822
|
+
}
|
|
4823
|
+
function isRecord(value) {
|
|
4824
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4825
|
+
}
|
|
4826
|
+
function annotate(value, ctx) {
|
|
4827
|
+
if (!ctx.entryId) return value;
|
|
4828
|
+
const { cleaned, encoded } = (0, import_stega7.vercelStegaSplit)(value);
|
|
4829
|
+
if (!encoded) return value;
|
|
4830
|
+
let payload;
|
|
4831
|
+
try {
|
|
4832
|
+
payload = (0, import_stega7.vercelStegaDecode)(encoded);
|
|
4833
|
+
} catch {
|
|
4834
|
+
return value;
|
|
4835
|
+
}
|
|
4836
|
+
if (typeof payload?.href !== "string") return value;
|
|
4837
|
+
let url;
|
|
4838
|
+
try {
|
|
4839
|
+
url = new URL(payload.href);
|
|
4840
|
+
} catch {
|
|
4841
|
+
return value;
|
|
4842
|
+
}
|
|
4843
|
+
if (url.searchParams.has(ENTRY_PARAM)) return value;
|
|
4844
|
+
url.searchParams.set(ENTRY_PARAM, ctx.entryId);
|
|
4845
|
+
const itemId = decodeURIComponent(url.pathname.split("/").pop() ?? "");
|
|
4846
|
+
if (!url.searchParams.has("field") && ctx.entryField && TOP_LEVEL_ENTRY.test(ctx.entryField) && itemId === ctx.entryOwnerId) {
|
|
4847
|
+
url.searchParams.set("field", ctx.entryField);
|
|
4848
|
+
}
|
|
4849
|
+
return (0, import_stega7.vercelStegaCombine)(
|
|
4850
|
+
cleaned,
|
|
4851
|
+
{ ...payload, href: url.toString() },
|
|
4852
|
+
false
|
|
4853
|
+
);
|
|
4854
|
+
}
|
|
4855
|
+
function walk(value, selections, ctx) {
|
|
4856
|
+
if (typeof value === "string") return annotate(value, ctx);
|
|
4857
|
+
if (Array.isArray(value)) {
|
|
4858
|
+
let changed2 = false;
|
|
4859
|
+
const next2 = value.map((element, index) => {
|
|
4860
|
+
const path = [...ctx.path, String(index)];
|
|
4861
|
+
const entryId = ctx.inAssets ? void 0 : entryIdOf(element);
|
|
4862
|
+
const entry = entryId ? {
|
|
4863
|
+
entryId,
|
|
4864
|
+
entryField: ctx.ownerId ? path.join(".") : void 0,
|
|
4865
|
+
entryOwnerId: ctx.ownerId,
|
|
4866
|
+
inAssets: isRecord(element) && element._id === void 0
|
|
4867
|
+
} : {};
|
|
4868
|
+
const out = walk(element, selections, { ...ctx, ...entry, path });
|
|
4869
|
+
if (out !== element) changed2 = true;
|
|
4870
|
+
return out;
|
|
4871
|
+
});
|
|
4872
|
+
return changed2 ? next2 : value;
|
|
4873
|
+
}
|
|
4874
|
+
if (!isRecord(value)) return value;
|
|
4875
|
+
const id = typeof value._id === "string" ? value._id : void 0;
|
|
4876
|
+
const typename = typeof value.__typename === "string" ? value.__typename : void 0;
|
|
4877
|
+
const fields = ctx.doc && selections ? collectFields(selections, typename, ctx.doc) : null;
|
|
4878
|
+
let changed = false;
|
|
4879
|
+
const next = {};
|
|
4880
|
+
for (const [key, child] of Object.entries(value)) {
|
|
4881
|
+
const field = fields?.get(key);
|
|
4882
|
+
const name = field?.name ?? key;
|
|
4883
|
+
const out = walk(child, field?.selections ?? null, {
|
|
4884
|
+
...ctx,
|
|
4885
|
+
ownerId: id ?? ctx.ownerId,
|
|
4886
|
+
path: id ? [name] : [...ctx.path, name]
|
|
4887
|
+
});
|
|
4888
|
+
if (out !== child) changed = true;
|
|
4889
|
+
next[key] = out;
|
|
4890
|
+
}
|
|
4891
|
+
return changed ? next : value;
|
|
4892
|
+
}
|
|
4893
|
+
function annotateEditLinks(result, query, operationName) {
|
|
4894
|
+
let doc = null;
|
|
4895
|
+
if (query !== void 0) {
|
|
4896
|
+
try {
|
|
4897
|
+
doc = readQueryDocument(query, operationName);
|
|
4898
|
+
} catch {
|
|
4899
|
+
doc = null;
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4902
|
+
return walk(result, doc?.operation ?? null, { doc, path: [] });
|
|
4903
|
+
}
|
|
4904
|
+
|
|
4905
|
+
// src/core/live-preview/bridge-events.ts
|
|
4906
|
+
var debug12 = createScopedLogger("live-preview:bridge");
|
|
4907
|
+
function createLiveBridge(store, options = {}) {
|
|
4908
|
+
let parentOrigin = null;
|
|
4909
|
+
const onMessage = (evt) => {
|
|
4910
|
+
const data = evt?.data;
|
|
4911
|
+
if (data?.event === "prepr:initVE" && !parentOrigin) {
|
|
4912
|
+
if (isAllowedEditorOrigin(evt.origin, options.allowedEditorOrigins?.())) {
|
|
4913
|
+
parentOrigin = evt.origin;
|
|
4914
|
+
} else {
|
|
4915
|
+
debug12.warn("ignored live preview handshake from origin", evt.origin);
|
|
4916
|
+
}
|
|
4917
|
+
return;
|
|
4918
|
+
}
|
|
4919
|
+
const trusted = parentOrigin ?? getTrustedParentOrigin();
|
|
4920
|
+
if (!trusted || evt.origin !== trusted) return;
|
|
4921
|
+
if (data?.event === "prepr:fieldChange") {
|
|
4922
|
+
if (typeof data.itemId === "string" && typeof data.field === "string" && data.field && isLiveFieldChange(data.field, data.value)) {
|
|
4923
|
+
const edit = parseEditTarget(data.edit);
|
|
4924
|
+
if (edit) store.pushPatch(data.itemId, data.field, data.value, edit);
|
|
4925
|
+
else store.pushPatch(data.itemId, data.field, data.value);
|
|
4926
|
+
} else {
|
|
4927
|
+
debug12.log("dropped malformed fieldChange", data);
|
|
4928
|
+
}
|
|
4929
|
+
return;
|
|
4930
|
+
}
|
|
4931
|
+
if (data?.event === "prepr:listChange") {
|
|
4932
|
+
const list = parseListEntries(data.entries);
|
|
4933
|
+
if (typeof data.itemId === "string" && typeof data.field === "string" && data.field && list) {
|
|
4934
|
+
store.pushList(data.itemId, data.field, list);
|
|
4935
|
+
} else {
|
|
4936
|
+
debug12.log("dropped malformed listChange", data);
|
|
4937
|
+
}
|
|
4938
|
+
return;
|
|
4939
|
+
}
|
|
4940
|
+
if (data?.event === "prepr:itemChanged") {
|
|
4941
|
+
if (typeof data.itemId === "string") {
|
|
4942
|
+
store.invalidate(data.itemId);
|
|
4943
|
+
} else {
|
|
4944
|
+
debug12.log("dropped malformed itemChanged", data);
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
};
|
|
4948
|
+
return {
|
|
4949
|
+
start() {
|
|
4950
|
+
window.addEventListener("message", onMessage);
|
|
4951
|
+
sendPreprEvent(
|
|
4952
|
+
"live_ready",
|
|
4953
|
+
options.strictMode?.() ? { strictMode: true } : void 0,
|
|
4954
|
+
{ allowUntrustedTarget: true }
|
|
4955
|
+
);
|
|
4956
|
+
},
|
|
4957
|
+
stop() {
|
|
4958
|
+
window.removeEventListener("message", onMessage);
|
|
4959
|
+
}
|
|
4960
|
+
};
|
|
4961
|
+
}
|
|
4962
|
+
|
|
4963
|
+
// src/core/live-preview/live-store.ts
|
|
4964
|
+
var debug13 = createScopedLogger("live-preview");
|
|
4965
|
+
var INVALIDATE_DEBOUNCE_MS = 150;
|
|
4966
|
+
var MAX_LOG_ENTRIES2 = 500;
|
|
4967
|
+
function withPatch(log, patch) {
|
|
4968
|
+
let previous = -1;
|
|
4969
|
+
for (let i2 = log.length - 1; i2 >= 0; i2--) {
|
|
4970
|
+
const p2 = log[i2];
|
|
4971
|
+
if (p2.type === patch.type && p2.itemId === patch.itemId && p2.field === patch.field) {
|
|
4972
|
+
previous = i2;
|
|
4973
|
+
break;
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
if (previous === -1) return [...log, patch];
|
|
4977
|
+
const since = log.slice(previous + 1);
|
|
4978
|
+
const listPrefix = `${patch.field}.`;
|
|
4979
|
+
const dependsOnPosition = since.some(
|
|
4980
|
+
(p2) => p2.type === "list" || patch.type === "list" && p2.itemId === patch.itemId && p2.field.startsWith(listPrefix)
|
|
4981
|
+
);
|
|
4982
|
+
if (dependsOnPosition) return [...log, patch];
|
|
4983
|
+
return [...log.slice(0, previous), ...since, patch];
|
|
4984
|
+
}
|
|
4985
|
+
function createLiveStore(options = {}) {
|
|
4986
|
+
const accepts = (patch) => options.accept ? options.accept(patch) : true;
|
|
4987
|
+
const entries = /* @__PURE__ */ new Set();
|
|
4988
|
+
const invalidateListeners = /* @__PURE__ */ new Set();
|
|
4989
|
+
const pendingInvalidates = /* @__PURE__ */ new Map();
|
|
4990
|
+
let defaultFetcher = null;
|
|
4991
|
+
let log = [];
|
|
4992
|
+
const logListeners = /* @__PURE__ */ new Set();
|
|
4993
|
+
function record(patch) {
|
|
4994
|
+
log = withPatch(log, patch).slice(-MAX_LOG_ENTRIES2);
|
|
4995
|
+
logListeners.forEach((fn) => fn(log));
|
|
4996
|
+
}
|
|
4997
|
+
function notify(entry) {
|
|
4998
|
+
entry.listeners.forEach((fn) => fn());
|
|
4999
|
+
}
|
|
5000
|
+
async function refetch(entry) {
|
|
5001
|
+
const fetcher = entry.opts.fetcher ?? defaultFetcher;
|
|
5002
|
+
if (!fetcher || entry.opts.query == null) return;
|
|
5003
|
+
const token = ++entry.fetchToken;
|
|
5004
|
+
try {
|
|
5005
|
+
const result = await fetcher(entry.opts.query, entry.opts.variables);
|
|
5006
|
+
if (token !== entry.fetchToken || !entries.has(entry)) return;
|
|
5007
|
+
entry.snapshot = result;
|
|
5008
|
+
entry.index = indexSnapshot(result);
|
|
5009
|
+
notify(entry);
|
|
5010
|
+
} catch (error) {
|
|
5011
|
+
debug13.log("refetch failed, keeping previous snapshot", error);
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
function runInvalidate(itemId) {
|
|
5015
|
+
pendingInvalidates.delete(itemId);
|
|
5016
|
+
invalidateListeners.forEach((fn) => fn(itemId));
|
|
5017
|
+
for (const entry of entries) {
|
|
5018
|
+
if (entry.index.has(itemId)) void refetch(entry);
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
5021
|
+
return {
|
|
5022
|
+
register(data, opts = {}) {
|
|
5023
|
+
const entry = {
|
|
5024
|
+
snapshot: data,
|
|
5025
|
+
index: indexSnapshot(data),
|
|
5026
|
+
opts,
|
|
5027
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
5028
|
+
fetchToken: 0
|
|
5029
|
+
};
|
|
5030
|
+
for (const patch of log) {
|
|
5031
|
+
if (patch.type === "field") {
|
|
5032
|
+
entry.snapshot = patchSnapshot(
|
|
5033
|
+
entry.snapshot,
|
|
5034
|
+
entry.index,
|
|
5035
|
+
patch.itemId,
|
|
5036
|
+
patch.field,
|
|
5037
|
+
patch.value
|
|
5038
|
+
);
|
|
5039
|
+
} else {
|
|
5040
|
+
const next = syncListSnapshot(
|
|
5041
|
+
entry.snapshot,
|
|
5042
|
+
entry.index,
|
|
5043
|
+
patch.itemId,
|
|
5044
|
+
patch.field,
|
|
5045
|
+
patch.entries
|
|
5046
|
+
);
|
|
5047
|
+
if (next !== entry.snapshot) {
|
|
5048
|
+
entry.snapshot = next;
|
|
5049
|
+
entry.index = indexSnapshot(next);
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
}
|
|
5053
|
+
entries.add(entry);
|
|
5054
|
+
return {
|
|
5055
|
+
getSnapshot: () => entry.snapshot,
|
|
5056
|
+
subscribe(fn) {
|
|
5057
|
+
entry.listeners.add(fn);
|
|
5058
|
+
return () => entry.listeners.delete(fn);
|
|
5059
|
+
},
|
|
5060
|
+
unregister() {
|
|
5061
|
+
entry.listeners.clear();
|
|
5062
|
+
entries.delete(entry);
|
|
5063
|
+
}
|
|
5064
|
+
};
|
|
5065
|
+
},
|
|
5066
|
+
pushPatch(itemId, field, value, edit) {
|
|
5067
|
+
const patch = edit ? { type: "field", itemId, field, value, edit } : { type: "field", itemId, field, value };
|
|
5068
|
+
if (!accepts(patch)) return;
|
|
5069
|
+
record(patch);
|
|
5070
|
+
for (const entry of entries) {
|
|
5071
|
+
const next = patchSnapshot(
|
|
5072
|
+
entry.snapshot,
|
|
5073
|
+
entry.index,
|
|
5074
|
+
itemId,
|
|
5075
|
+
field,
|
|
5076
|
+
value
|
|
5077
|
+
);
|
|
5078
|
+
if (next !== entry.snapshot) {
|
|
5079
|
+
entry.snapshot = next;
|
|
5080
|
+
notify(entry);
|
|
5081
|
+
}
|
|
5082
|
+
}
|
|
5083
|
+
},
|
|
5084
|
+
pushList(itemId, field, list) {
|
|
5085
|
+
const patch = { type: "list", itemId, field, entries: list };
|
|
5086
|
+
if (!accepts(patch)) return;
|
|
5087
|
+
record(patch);
|
|
5088
|
+
for (const entry of entries) {
|
|
5089
|
+
const next = syncListSnapshot(
|
|
5090
|
+
entry.snapshot,
|
|
5091
|
+
entry.index,
|
|
5092
|
+
itemId,
|
|
5093
|
+
field,
|
|
5094
|
+
list
|
|
5095
|
+
);
|
|
5096
|
+
if (next !== entry.snapshot) {
|
|
5097
|
+
entry.snapshot = next;
|
|
5098
|
+
entry.index = indexSnapshot(next);
|
|
5099
|
+
notify(entry);
|
|
5100
|
+
}
|
|
5101
|
+
}
|
|
5102
|
+
},
|
|
5103
|
+
invalidate(itemId) {
|
|
5104
|
+
if (log.length) {
|
|
5105
|
+
log = [];
|
|
5106
|
+
logListeners.forEach((fn) => fn(log));
|
|
5107
|
+
}
|
|
5108
|
+
const pending = pendingInvalidates.get(itemId);
|
|
5109
|
+
if (pending) clearTimeout(pending);
|
|
5110
|
+
pendingInvalidates.set(
|
|
5111
|
+
itemId,
|
|
5112
|
+
setTimeout(() => runInvalidate(itemId), INVALIDATE_DEBOUNCE_MS)
|
|
5113
|
+
);
|
|
5114
|
+
},
|
|
5115
|
+
setFetcher(fetcher) {
|
|
5116
|
+
defaultFetcher = fetcher;
|
|
5117
|
+
},
|
|
5118
|
+
onInvalidate(fn) {
|
|
5119
|
+
invalidateListeners.add(fn);
|
|
5120
|
+
return () => invalidateListeners.delete(fn);
|
|
5121
|
+
},
|
|
5122
|
+
getLog: () => log,
|
|
5123
|
+
onLogChange(fn) {
|
|
5124
|
+
logListeners.add(fn);
|
|
5125
|
+
return () => logListeners.delete(fn);
|
|
5126
|
+
}
|
|
5127
|
+
};
|
|
5128
|
+
}
|
|
5129
|
+
|
|
5130
|
+
// src/core/live-preview/morph.ts
|
|
5131
|
+
function morphElement(current, next) {
|
|
5132
|
+
syncAttributes(current, next);
|
|
5133
|
+
morphChildren(current, next);
|
|
5134
|
+
}
|
|
5135
|
+
var SKIP_ATTR = "data-prepr-morph-skip";
|
|
5136
|
+
var RUNTIME_ATTR_PREFIXES = ["data-prepr-"];
|
|
5137
|
+
function isRuntimeAttribute(name) {
|
|
5138
|
+
return RUNTIME_ATTR_PREFIXES.some((prefix) => name.startsWith(prefix));
|
|
5139
|
+
}
|
|
5140
|
+
function syncAttributes(current, next) {
|
|
5141
|
+
for (const { name } of Array.from(current.attributes)) {
|
|
5142
|
+
if (!next.hasAttribute(name) && !isRuntimeAttribute(name)) {
|
|
5143
|
+
current.removeAttribute(name);
|
|
5144
|
+
}
|
|
5145
|
+
}
|
|
5146
|
+
for (const { name, value } of Array.from(next.attributes)) {
|
|
5147
|
+
if (current.getAttribute(name) !== value) current.setAttribute(name, value);
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
function isSkipped(node) {
|
|
5151
|
+
return node.nodeType === Node.ELEMENT_NODE && (node.hasAttribute(SKIP_ATTR) || node.localName === "prepr-toolbar");
|
|
5152
|
+
}
|
|
5153
|
+
function sameKind(a2, b2) {
|
|
5154
|
+
if (a2.nodeType !== b2.nodeType) return false;
|
|
5155
|
+
if (a2.nodeType !== Node.ELEMENT_NODE) return true;
|
|
5156
|
+
const ea = a2;
|
|
5157
|
+
const eb = b2;
|
|
5158
|
+
return ea.localName === eb.localName && ea.id === eb.id;
|
|
5159
|
+
}
|
|
5160
|
+
function morphChildren(current, next) {
|
|
5161
|
+
const wanted = Array.from(next.childNodes).filter((n2) => !isSkipped(n2));
|
|
5162
|
+
const existing = Array.from(current.childNodes).filter((n2) => !isSkipped(n2));
|
|
5163
|
+
wanted.forEach((target, i2) => {
|
|
5164
|
+
const node = existing[i2];
|
|
5165
|
+
if (node && sameKind(node, target)) {
|
|
5166
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
5167
|
+
const element = node;
|
|
5168
|
+
if (element.localName === "script") return;
|
|
5169
|
+
if (element.localName === "astro-island" && !element.isEqualNode(target)) {
|
|
5170
|
+
element.replaceWith(adopt(target, current));
|
|
5171
|
+
return;
|
|
5172
|
+
}
|
|
5173
|
+
morphElement(element, target);
|
|
5174
|
+
} else if (node.nodeValue !== target.nodeValue) {
|
|
5175
|
+
node.nodeValue = target.nodeValue;
|
|
5176
|
+
}
|
|
5177
|
+
return;
|
|
5178
|
+
}
|
|
5179
|
+
const replacement = adopt(target, current);
|
|
5180
|
+
if (node) node.replaceWith(replacement);
|
|
5181
|
+
else current.appendChild(replacement);
|
|
5182
|
+
});
|
|
5183
|
+
for (const extra of existing.slice(wanted.length)) extra.remove();
|
|
5184
|
+
}
|
|
5185
|
+
function adopt(node, parent) {
|
|
5186
|
+
const copy = parent.ownerDocument.importNode(node, true);
|
|
5187
|
+
if (copy.nodeType === Node.ELEMENT_NODE) {
|
|
5188
|
+
const element = copy;
|
|
5189
|
+
const scripts = element.localName === "script" ? [element] : Array.from(element.querySelectorAll("script"));
|
|
5190
|
+
scripts.forEach((s2) => s2.setAttribute("data-prepr-morphed", ""));
|
|
5191
|
+
}
|
|
5192
|
+
return copy;
|
|
5193
|
+
}
|
|
5194
|
+
|
|
5195
|
+
// src/core/live-preview/html-live.ts
|
|
5196
|
+
var debug14 = createScopedLogger("live-preview:html");
|
|
5197
|
+
var COMPLETE_DOCUMENT = /<\/html>\s*$/i;
|
|
5198
|
+
var SYNC_DEBOUNCE_MS = 150;
|
|
5199
|
+
function startHtmlLivePreview(options = {}) {
|
|
5200
|
+
if (typeof window === "undefined" || window.self === window.top) {
|
|
5201
|
+
return { stop() {
|
|
5202
|
+
} };
|
|
5203
|
+
}
|
|
5204
|
+
const store = createLiveStore();
|
|
5205
|
+
const bridge = createLiveBridge(store, {
|
|
5206
|
+
allowedEditorOrigins: () => options.allowedEditorOrigins,
|
|
5207
|
+
strictMode: () => options.strictMode
|
|
5208
|
+
});
|
|
5209
|
+
bridge.start();
|
|
5210
|
+
let timer = null;
|
|
5211
|
+
let inFlight = false;
|
|
5212
|
+
let pending = null;
|
|
5213
|
+
let stopped = false;
|
|
5214
|
+
const render = async (log) => {
|
|
5215
|
+
inFlight = true;
|
|
5216
|
+
try {
|
|
5217
|
+
const response = await fetch(window.location.href, {
|
|
5218
|
+
method: log.length ? "POST" : "GET",
|
|
5219
|
+
headers: log.length ? { "Content-Type": "application/json", [HEADER_LIVE_RENDER]: "1" } : {},
|
|
5220
|
+
body: log.length ? JSON.stringify({ log }) : void 0,
|
|
5221
|
+
credentials: "same-origin",
|
|
5222
|
+
cache: "no-store"
|
|
5223
|
+
});
|
|
5224
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
5225
|
+
const html = await response.text();
|
|
5226
|
+
if (stopped) return;
|
|
5227
|
+
if (!COMPLETE_DOCUMENT.test(html)) {
|
|
5228
|
+
throw new Error("incomplete page; a component failed to render");
|
|
5229
|
+
}
|
|
5230
|
+
const next = new DOMParser().parseFromString(html, "text/html");
|
|
5231
|
+
if (next.title && next.title !== document.title)
|
|
5232
|
+
document.title = next.title;
|
|
5233
|
+
morphElement(document.body, next.body);
|
|
5234
|
+
} catch (error) {
|
|
5235
|
+
debug14.log("re-render failed", error);
|
|
5236
|
+
} finally {
|
|
5237
|
+
inFlight = false;
|
|
5238
|
+
}
|
|
5239
|
+
if (pending && !stopped) {
|
|
5240
|
+
const queued = pending;
|
|
5241
|
+
pending = null;
|
|
5242
|
+
void render(queued);
|
|
5243
|
+
}
|
|
5244
|
+
};
|
|
5245
|
+
const schedule = (log) => {
|
|
5246
|
+
if (timer) clearTimeout(timer);
|
|
5247
|
+
timer = setTimeout(() => {
|
|
5248
|
+
timer = null;
|
|
5249
|
+
if (inFlight) pending = log;
|
|
5250
|
+
else void render(log);
|
|
5251
|
+
}, SYNC_DEBOUNCE_MS);
|
|
5252
|
+
};
|
|
5253
|
+
const offLog = store.onLogChange((log) => {
|
|
5254
|
+
if (log.length) schedule(log);
|
|
5255
|
+
});
|
|
5256
|
+
const offInvalidate = store.onInvalidate(() => schedule([]));
|
|
5257
|
+
return {
|
|
5258
|
+
stop() {
|
|
5259
|
+
stopped = true;
|
|
5260
|
+
if (timer) clearTimeout(timer);
|
|
5261
|
+
offLog();
|
|
5262
|
+
offInvalidate();
|
|
5263
|
+
bridge.stop();
|
|
5264
|
+
}
|
|
5265
|
+
};
|
|
5266
|
+
}
|
|
5267
|
+
|
|
5268
|
+
// src/core/live-preview/live-request.ts
|
|
5269
|
+
var MAX_BODY_BYTES = 1e6;
|
|
5270
|
+
async function readLiveLogFromRequest(request) {
|
|
5271
|
+
try {
|
|
5272
|
+
if (request.method !== "POST") return [];
|
|
5273
|
+
if (request.headers.get(HEADER_LIVE_RENDER) !== "1") return [];
|
|
5274
|
+
const origin = request.headers.get("origin");
|
|
5275
|
+
if (!origin || origin !== new URL(request.url).origin) return [];
|
|
5276
|
+
const type = request.headers.get("content-type") ?? "";
|
|
5277
|
+
if (!type.toLowerCase().startsWith("application/json")) return [];
|
|
5278
|
+
const length = Number(request.headers.get("content-length") ?? "0");
|
|
5279
|
+
if (length > MAX_BODY_BYTES) return [];
|
|
5280
|
+
const text = await request.clone().text();
|
|
5281
|
+
if (text.length > MAX_BODY_BYTES) return [];
|
|
5282
|
+
const body = JSON.parse(text);
|
|
5283
|
+
return parsePatchLog(body?.log);
|
|
5284
|
+
} catch {
|
|
5285
|
+
return [];
|
|
5286
|
+
}
|
|
5287
|
+
}
|
|
5288
|
+
|
|
2293
5289
|
// src/core/server.ts
|
|
2294
5290
|
var PreprError = class extends Error {
|
|
2295
5291
|
constructor(message, code, context, originalError) {
|
|
@@ -2677,6 +5673,7 @@ function setTrackingParam(key, value) {
|
|
|
2677
5673
|
PreprError,
|
|
2678
5674
|
PreprToolbarElement,
|
|
2679
5675
|
VERSION,
|
|
5676
|
+
annotateEditLinks,
|
|
2680
5677
|
createPreprPreview,
|
|
2681
5678
|
createToolbarStore,
|
|
2682
5679
|
definePreprToolbar,
|
|
@@ -2688,10 +5685,16 @@ function setTrackingParam(key, value) {
|
|
|
2688
5685
|
getPreprUUIDFromHeaders,
|
|
2689
5686
|
getToolbarPropsFromHeaders,
|
|
2690
5687
|
loadTrackingPixel,
|
|
5688
|
+
normalizeQuery,
|
|
5689
|
+
parsePatchLog,
|
|
5690
|
+
previewAttrs,
|
|
2691
5691
|
processPreprRequest,
|
|
5692
|
+
readLiveLogFromRequest,
|
|
2692
5693
|
resolveFeatures,
|
|
2693
5694
|
setTrackingParam,
|
|
5695
|
+
startHtmlLivePreview,
|
|
2694
5696
|
stegaClean,
|
|
5697
|
+
stegaTarget,
|
|
2695
5698
|
trackEvent,
|
|
2696
5699
|
validatePreprToken
|
|
2697
5700
|
});
|