@preprio/toolkit 0.5.0-rc.1 → 0.5.0-rc.3
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 +207 -10
- package/dist/astro/index.cjs +1174 -1
- package/dist/astro/index.d.cts +47 -1
- package/dist/astro/index.d.ts +47 -1
- package/dist/astro/index.js +1177 -1
- package/dist/chunk-42Z2VW7G.js +1161 -0
- package/dist/chunk-6WY3MWCX.js +246 -0
- package/dist/chunk-MDAV2FLM.js +26 -0
- package/dist/{chunk-4JNKYIEV.js → chunk-PP3O7RB4.js} +1370 -617
- package/dist/chunk-S3VT4UWM.js +75 -0
- package/dist/chunk-SXUDBGLZ.js +184 -0
- package/dist/chunk-URXSDJOU.js +356 -0
- package/dist/index.cjs +3113 -391
- package/dist/index.d.cts +240 -5
- package/dist/index.d.ts +240 -5
- package/dist/index.js +189 -149
- package/dist/live-preview-EADA3ZL4.js +18 -0
- package/dist/nextjs/components.cjs +1530 -361
- 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 +1065 -6
- package/dist/nextjs/index.d.cts +81 -3
- package/dist/nextjs/index.d.ts +81 -3
- package/dist/nextjs/index.js +1059 -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 +1838 -0
- package/dist/nextjs/live.d.cts +52 -0
- package/dist/nextjs/live.d.ts +52 -0
- package/dist/nextjs/live.js +265 -0
- package/dist/nuxt/index.cjs +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/nuxt/live-preview-PTEC3GPC.js +1462 -0
- package/dist/nuxt/live.cjs +1662 -0
- package/dist/nuxt/live.d.cts +43 -0
- package/dist/nuxt/live.d.ts +43 -0
- package/dist/nuxt/live.js +94 -0
- package/dist/{pixel-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 +1530 -361
- package/dist/react/components.d.cts +17 -2
- package/dist/react/components.d.ts +17 -2
- package/dist/react/components.js +1530 -361
- package/dist/sveltekit/index.cjs +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/package.json +12 -1
- package/src/astro/PreprToolbar.astro +7 -1
|
@@ -96,6 +96,40 @@ var ALL_FEATURES_ENABLED = Object.freeze({
|
|
|
96
96
|
editMode: true
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
+
// src/core/locales/de.json
|
|
100
|
+
var de_default = {
|
|
101
|
+
common: {
|
|
102
|
+
viewingAs: "Ansicht als:",
|
|
103
|
+
user: "Benutzer",
|
|
104
|
+
off: "Aus",
|
|
105
|
+
on: "An",
|
|
106
|
+
reset: "Zur\xFCcksetzen",
|
|
107
|
+
toolbar: "Symbolleiste"
|
|
108
|
+
},
|
|
109
|
+
adaptiveContent: {
|
|
110
|
+
adaptiveContent: "Adaptive Inhalte",
|
|
111
|
+
enablePreview: "Vorschaumodus",
|
|
112
|
+
segment: "Segment",
|
|
113
|
+
ABVariant: "A/B-Variante",
|
|
114
|
+
chooseSegment: "Segment w\xE4hlen",
|
|
115
|
+
searchSegments: "Segmente suchen",
|
|
116
|
+
none: "Keine Segmente",
|
|
117
|
+
allOtherUsers: "Alle anderen Benutzer",
|
|
118
|
+
offDescription: "Die Website als echter Benutzer ansehen (Cookies verwenden)",
|
|
119
|
+
onDescription: "Mit der Symbolleiste Segmente und A/B-Varianten simulieren"
|
|
120
|
+
},
|
|
121
|
+
editingTools: {
|
|
122
|
+
editingTools: "Bearbeitungswerkzeuge",
|
|
123
|
+
editMode: "Bearbeitungsmodus",
|
|
124
|
+
ariaCloseEditMode: "Bearbeitungsmodus schlie\xDFen"
|
|
125
|
+
},
|
|
126
|
+
visualEditor: {
|
|
127
|
+
edit: "Bearbeiten",
|
|
128
|
+
otherItem: "anderes Element",
|
|
129
|
+
linkedItem: "verkn\xFCpftes Element"
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
99
133
|
// src/core/locales/en.json
|
|
100
134
|
var en_default = {
|
|
101
135
|
common: {
|
|
@@ -122,6 +156,45 @@ var en_default = {
|
|
|
122
156
|
editingTools: "Editing tools",
|
|
123
157
|
editMode: "Edit mode",
|
|
124
158
|
ariaCloseEditMode: "Close edit mode"
|
|
159
|
+
},
|
|
160
|
+
visualEditor: {
|
|
161
|
+
edit: "Edit",
|
|
162
|
+
otherItem: "other item",
|
|
163
|
+
linkedItem: "linked item"
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// src/core/locales/fr.json
|
|
168
|
+
var fr_default = {
|
|
169
|
+
common: {
|
|
170
|
+
viewingAs: "Affichage en tant que :",
|
|
171
|
+
user: "Utilisateur",
|
|
172
|
+
off: "D\xE9sactiv\xE9",
|
|
173
|
+
on: "Activ\xE9",
|
|
174
|
+
reset: "R\xE9initialiser",
|
|
175
|
+
toolbar: "Barre d'outils"
|
|
176
|
+
},
|
|
177
|
+
adaptiveContent: {
|
|
178
|
+
adaptiveContent: "Contenu adaptatif",
|
|
179
|
+
enablePreview: "Mode aper\xE7u",
|
|
180
|
+
segment: "Segment",
|
|
181
|
+
ABVariant: "Variante A/B",
|
|
182
|
+
chooseSegment: "Choisir un segment",
|
|
183
|
+
searchSegments: "Rechercher des segments",
|
|
184
|
+
none: "Aucun segment",
|
|
185
|
+
allOtherUsers: "Tous les autres utilisateurs",
|
|
186
|
+
offDescription: "Voir le site comme un vrai utilisateur (utiliser les cookies)",
|
|
187
|
+
onDescription: "Utilisez la barre d'outils pour simuler des segments et des variantes A/B"
|
|
188
|
+
},
|
|
189
|
+
editingTools: {
|
|
190
|
+
editingTools: "Outils d'\xE9dition",
|
|
191
|
+
editMode: "Mode \xE9dition",
|
|
192
|
+
ariaCloseEditMode: "Fermer le mode \xE9dition"
|
|
193
|
+
},
|
|
194
|
+
visualEditor: {
|
|
195
|
+
edit: "Modifier",
|
|
196
|
+
otherItem: "autre \xE9l\xE9ment",
|
|
197
|
+
linkedItem: "\xE9l\xE9ment li\xE9"
|
|
125
198
|
}
|
|
126
199
|
};
|
|
127
200
|
|
|
@@ -151,13 +224,20 @@ var nl_default = {
|
|
|
151
224
|
editingTools: "Editing Tools",
|
|
152
225
|
editMode: "Edit mode",
|
|
153
226
|
ariaCloseEditMode: "Edit mode sluiten"
|
|
227
|
+
},
|
|
228
|
+
visualEditor: {
|
|
229
|
+
edit: "Bewerken",
|
|
230
|
+
otherItem: "ander item",
|
|
231
|
+
linkedItem: "gekoppeld item"
|
|
154
232
|
}
|
|
155
233
|
};
|
|
156
234
|
|
|
157
235
|
// src/core/i18n.ts
|
|
158
236
|
var dictionaries = {
|
|
159
237
|
en: en_default,
|
|
160
|
-
nl: nl_default
|
|
238
|
+
nl: nl_default,
|
|
239
|
+
fr: fr_default,
|
|
240
|
+
de: de_default
|
|
161
241
|
};
|
|
162
242
|
var LOCALES = Object.keys(dictionaries);
|
|
163
243
|
function isLocale(value) {
|
|
@@ -250,52 +330,53 @@ function createScopedLogger(scopeName) {
|
|
|
250
330
|
prefix: `[Prepr][${scopeName}]`
|
|
251
331
|
});
|
|
252
332
|
}
|
|
253
|
-
function throttle(func, delay) {
|
|
254
|
-
let timeoutId = null;
|
|
255
|
-
let lastExecTime = 0;
|
|
256
|
-
const throttledFunc = ((...args) => {
|
|
257
|
-
const currentTime = Date.now();
|
|
258
|
-
const timeSinceLastExec = currentTime - lastExecTime;
|
|
259
|
-
if (timeSinceLastExec >= delay) {
|
|
260
|
-
func(...args);
|
|
261
|
-
lastExecTime = currentTime;
|
|
262
|
-
} else {
|
|
263
|
-
if (timeoutId) {
|
|
264
|
-
clearTimeout(timeoutId);
|
|
265
|
-
}
|
|
266
|
-
timeoutId = setTimeout(() => {
|
|
267
|
-
func(...args);
|
|
268
|
-
lastExecTime = Date.now();
|
|
269
|
-
timeoutId = null;
|
|
270
|
-
}, delay - timeSinceLastExec);
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
throttledFunc.cancel = () => {
|
|
274
|
-
if (timeoutId) {
|
|
275
|
-
clearTimeout(timeoutId);
|
|
276
|
-
timeoutId = null;
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
return throttledFunc;
|
|
280
|
-
}
|
|
281
|
-
function createElementCache(query, ttl = 1e3) {
|
|
282
|
-
let cache = null;
|
|
283
|
-
let lastCacheTime = 0;
|
|
284
|
-
return () => {
|
|
285
|
-
const now = Date.now();
|
|
286
|
-
if (!cache || now - lastCacheTime > ttl) {
|
|
287
|
-
cache = document.querySelectorAll(query);
|
|
288
|
-
lastCacheTime = now;
|
|
289
|
-
}
|
|
290
|
-
return cache;
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
333
|
|
|
294
334
|
// src/core/iframe-bridge.ts
|
|
295
335
|
var debug = createScopedLogger("iframe-bridge");
|
|
336
|
+
var MAX_REFERENCES = 500;
|
|
337
|
+
var MAX_HREF_LENGTH = 8192;
|
|
338
|
+
var MAX_ASSETS = 500;
|
|
339
|
+
var MAX_ASSET_KEY_LENGTH = 2048;
|
|
340
|
+
var MAX_ASSET_FIELD_LENGTH = 512;
|
|
341
|
+
function parseIds(value) {
|
|
342
|
+
if (!Array.isArray(value)) return void 0;
|
|
343
|
+
return value.slice(0, MAX_REFERENCES).filter((id) => typeof id === "string" && id.length > 0);
|
|
344
|
+
}
|
|
345
|
+
function parseReferences(value) {
|
|
346
|
+
if (!Array.isArray(value)) return void 0;
|
|
347
|
+
const list = [];
|
|
348
|
+
for (const entry of value.slice(0, MAX_REFERENCES)) {
|
|
349
|
+
const id = entry?.id;
|
|
350
|
+
const field = entry?.field;
|
|
351
|
+
if (typeof id === "string" && id && typeof field === "string" && field) {
|
|
352
|
+
list.push({ id, field });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return list;
|
|
356
|
+
}
|
|
357
|
+
function boundedString(value, max) {
|
|
358
|
+
return typeof value === "string" && value && value.length <= max ? value : void 0;
|
|
359
|
+
}
|
|
360
|
+
function parseAssets(value) {
|
|
361
|
+
if (!Array.isArray(value)) return void 0;
|
|
362
|
+
const list = [];
|
|
363
|
+
for (const entry of value.slice(0, MAX_ASSETS)) {
|
|
364
|
+
if (!entry || typeof entry !== "object") continue;
|
|
365
|
+
const raw = entry;
|
|
366
|
+
const key = boundedString(raw.key, MAX_ASSET_KEY_LENGTH);
|
|
367
|
+
const field = boundedString(raw.field, MAX_ASSET_FIELD_LENGTH);
|
|
368
|
+
if (!key || !field) continue;
|
|
369
|
+
const entryId = boundedString(raw.entryId, MAX_ASSET_FIELD_LENGTH);
|
|
370
|
+
list.push(entryId ? { key, field, entryId } : { key, field });
|
|
371
|
+
}
|
|
372
|
+
return list;
|
|
373
|
+
}
|
|
296
374
|
var EDITOR_BASE_DOMAIN = "prepr.io";
|
|
297
375
|
function isAllowedEditorOrigin(origin, allowed) {
|
|
298
|
-
if (allowed)
|
|
376
|
+
if (allowed) {
|
|
377
|
+
if (allowed.includes("*")) return origin !== "null";
|
|
378
|
+
return allowed.includes(origin);
|
|
379
|
+
}
|
|
299
380
|
let url;
|
|
300
381
|
try {
|
|
301
382
|
url = new URL(origin);
|
|
@@ -311,6 +392,10 @@ function isAllowedEditorOrigin(origin, allowed) {
|
|
|
311
392
|
function createIframeBridge(store, options = {}) {
|
|
312
393
|
const allowedOrigins = options.allowedEditorOrigins;
|
|
313
394
|
const onScrollToField = options.onScrollToField;
|
|
395
|
+
const onHandshake = options.onHandshake;
|
|
396
|
+
const onReferences = options.onReferences;
|
|
397
|
+
const onAssets = options.onAssets;
|
|
398
|
+
const onAllowNavigation = options.onAllowNavigation;
|
|
314
399
|
let parentOrigin = null;
|
|
315
400
|
const onKeyDown = (event) => {
|
|
316
401
|
const key = event.key.toLowerCase();
|
|
@@ -320,7 +405,10 @@ function createIframeBridge(store, options = {}) {
|
|
|
320
405
|
const onMessage = (evt) => {
|
|
321
406
|
const data = evt?.data;
|
|
322
407
|
if (data?.event === "prepr:initVE" && !parentOrigin) {
|
|
323
|
-
if (!isAllowedEditorOrigin(evt.origin, allowedOrigins))
|
|
408
|
+
if (!isAllowedEditorOrigin(evt.origin, allowedOrigins)) {
|
|
409
|
+
debug.warn("ignored editor handshake from origin", evt.origin);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
324
412
|
parentOrigin = evt.origin;
|
|
325
413
|
setTrustedParentOrigin(parentOrigin);
|
|
326
414
|
if (data.scrollPosition != null) {
|
|
@@ -328,8 +416,43 @@ function createIframeBridge(store, options = {}) {
|
|
|
328
416
|
setTimeout(() => window.scrollTo(0, top), 1);
|
|
329
417
|
}
|
|
330
418
|
store?.set({ previewMode: true, editMode: data.editMode ?? true });
|
|
419
|
+
const itemId = typeof data.itemId === "string" && data.itemId ? data.itemId : void 0;
|
|
420
|
+
const referencedIds = parseReferences(data.referencedIds);
|
|
421
|
+
const componentIds = parseIds(data.componentIds);
|
|
422
|
+
const uiLocale = isLocale(data.uiLocale) ? data.uiLocale : void 0;
|
|
423
|
+
const navigationGuard = data.navigationGuard === true;
|
|
424
|
+
const assets = parseAssets(data.assets);
|
|
425
|
+
try {
|
|
426
|
+
onHandshake?.({
|
|
427
|
+
...itemId && { itemId },
|
|
428
|
+
...referencedIds && { referencedIds },
|
|
429
|
+
...componentIds && { componentIds },
|
|
430
|
+
...uiLocale && { uiLocale },
|
|
431
|
+
...navigationGuard && { navigationGuard },
|
|
432
|
+
...assets && { assets }
|
|
433
|
+
});
|
|
434
|
+
} catch (error) {
|
|
435
|
+
debug.warn("handshake handler failed", error);
|
|
436
|
+
}
|
|
331
437
|
}
|
|
332
438
|
if (!parentOrigin || evt.origin !== parentOrigin) return;
|
|
439
|
+
if (data?.event === "prepr:references") {
|
|
440
|
+
const assets = parseAssets(data.assets);
|
|
441
|
+
if (assets) {
|
|
442
|
+
try {
|
|
443
|
+
onAssets?.(assets);
|
|
444
|
+
} catch (error) {
|
|
445
|
+
debug.warn("assets handler failed", error);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const referencedIds = parseReferences(data.referencedIds);
|
|
449
|
+
if (!referencedIds) return;
|
|
450
|
+
try {
|
|
451
|
+
onReferences?.(referencedIds, parseIds(data.componentIds));
|
|
452
|
+
} catch (error) {
|
|
453
|
+
debug.warn("references handler failed", error);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
333
456
|
if (data?.event === "prepr:getScrollPosition") {
|
|
334
457
|
const currentScrollY = window.scrollY || document.documentElement.scrollTop;
|
|
335
458
|
sendPreprEvent("getScrollPosition", { value: currentScrollY });
|
|
@@ -337,12 +460,32 @@ function createIframeBridge(store, options = {}) {
|
|
|
337
460
|
if (data?.event === "prepr:scrollToField") {
|
|
338
461
|
if (typeof data.field !== "string" || !data.field) return;
|
|
339
462
|
if (data.id !== void 0 && typeof data.id !== "string") return;
|
|
463
|
+
const block = typeof data.block === "number" && Number.isInteger(data.block) ? data.block : void 0;
|
|
464
|
+
const entryId = typeof data.entryId === "string" && data.entryId ? data.entryId : void 0;
|
|
465
|
+
const fieldById = typeof data.fieldById === "string" && data.fieldById ? data.fieldById : void 0;
|
|
340
466
|
try {
|
|
341
|
-
onScrollToField?.({
|
|
467
|
+
onScrollToField?.({
|
|
468
|
+
field: data.field,
|
|
469
|
+
id: data.id,
|
|
470
|
+
block,
|
|
471
|
+
entryId,
|
|
472
|
+
fieldById
|
|
473
|
+
});
|
|
342
474
|
} catch (error) {
|
|
343
475
|
debug.warn("scrollToField handler failed", error);
|
|
344
476
|
}
|
|
345
477
|
}
|
|
478
|
+
if (data?.event === "prepr:allowNavigation") {
|
|
479
|
+
const href = data.href;
|
|
480
|
+
if (typeof href !== "string" || !href || href.length > MAX_HREF_LENGTH) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
onAllowNavigation?.(href);
|
|
485
|
+
} catch (error) {
|
|
486
|
+
debug.warn("allowNavigation handler failed", error);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
346
489
|
};
|
|
347
490
|
return {
|
|
348
491
|
start() {
|
|
@@ -446,29 +589,43 @@ function tagEncodedElement(element, decoded) {
|
|
|
446
589
|
element.setAttribute("data-prepr-origin", decoded.origin);
|
|
447
590
|
const { id, field, locale } = parseEditHref(decoded.href);
|
|
448
591
|
setOrRemove(element, "data-prepr-id", id);
|
|
449
|
-
|
|
592
|
+
const fieldHint = element.getAttribute("data-prepr-edit-target")?.trim() || void 0;
|
|
593
|
+
setOrRemove(element, "data-prepr-field", field ?? fieldHint);
|
|
450
594
|
setOrRemove(element, "data-prepr-locale", locale);
|
|
451
595
|
}
|
|
596
|
+
function readEditPayload(element) {
|
|
597
|
+
return {
|
|
598
|
+
href: element.getAttribute("data-prepr-href") ?? "",
|
|
599
|
+
origin: element.getAttribute("data-prepr-origin") ?? "",
|
|
600
|
+
id: element.getAttribute("data-prepr-id") ?? void 0,
|
|
601
|
+
field: element.getAttribute("data-prepr-field") ?? void 0,
|
|
602
|
+
locale: element.getAttribute("data-prepr-locale") ?? void 0,
|
|
603
|
+
entryId: element.getAttribute("data-prepr-entry-id") || void 0
|
|
604
|
+
};
|
|
605
|
+
}
|
|
452
606
|
|
|
453
607
|
// src/core/stega/auto-clean.ts
|
|
454
608
|
var debug3 = createScopedLogger("stega:auto-clean");
|
|
609
|
+
var cleanedNodes = /* @__PURE__ */ new WeakMap();
|
|
455
610
|
function createStegaAutoClean(options) {
|
|
456
611
|
const stripText = options?.enabled ?? true;
|
|
457
612
|
let observer = null;
|
|
458
613
|
let processing = false;
|
|
459
|
-
let cleaned = /* @__PURE__ */ new WeakMap();
|
|
460
614
|
let pendingHidden = /* @__PURE__ */ new Set();
|
|
461
615
|
let pendingDirty = /* @__PURE__ */ new Set();
|
|
462
616
|
let debounceTimeout = null;
|
|
463
617
|
function cleanTextNode(textNode) {
|
|
464
618
|
if (isIgnoredTextNode(textNode)) return false;
|
|
465
619
|
const textContent = textNode.textContent;
|
|
466
|
-
const info =
|
|
620
|
+
const info = cleanedNodes.get(textNode);
|
|
467
621
|
const settled = stripText ? info?.cleaned : info?.original;
|
|
468
622
|
if (info && textContent === settled) {
|
|
469
623
|
if (!info.decoded) return false;
|
|
470
624
|
const shown = resolveEditTarget(textNode);
|
|
471
|
-
if (!shown)
|
|
625
|
+
if (!shown) {
|
|
626
|
+
pendingHidden.add(textNode);
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
472
629
|
pendingHidden.delete(textNode);
|
|
473
630
|
if (shown.hasAttribute("data-prepr-encoded")) return false;
|
|
474
631
|
tagEncodedElement(shown, info.decoded);
|
|
@@ -479,7 +636,7 @@ function createStegaAutoClean(options) {
|
|
|
479
636
|
if (!encoded || stripped === textContent) return false;
|
|
480
637
|
const decoded = decodeStega(textContent);
|
|
481
638
|
if (!decoded?.href && !stripText) return false;
|
|
482
|
-
|
|
639
|
+
cleanedNodes.set(textNode, {
|
|
483
640
|
original: textContent,
|
|
484
641
|
cleaned: stripped,
|
|
485
642
|
decoded: decoded?.href ? decoded : null
|
|
@@ -521,7 +678,7 @@ function createStegaAutoClean(options) {
|
|
|
521
678
|
}
|
|
522
679
|
if (mutation.type === "characterData" && mutation.target.nodeType === Node.TEXT_NODE) {
|
|
523
680
|
const textNode = mutation.target;
|
|
524
|
-
const info =
|
|
681
|
+
const info = cleanedNodes.get(textNode);
|
|
525
682
|
const text = textNode.textContent ?? "";
|
|
526
683
|
const settled = stripText ? info?.cleaned : info?.original;
|
|
527
684
|
if (text !== settled && (0, import_stega2.vercelStegaSplit)(text).encoded) {
|
|
@@ -564,7 +721,16 @@ function createStegaAutoClean(options) {
|
|
|
564
721
|
retryPendingHidden();
|
|
565
722
|
}, 50);
|
|
566
723
|
}
|
|
724
|
+
let resizeTimeout = null;
|
|
725
|
+
function handleResize() {
|
|
726
|
+
if (pendingHidden.size === 0 || resizeTimeout) return;
|
|
727
|
+
resizeTimeout = setTimeout(() => {
|
|
728
|
+
resizeTimeout = null;
|
|
729
|
+
retryPendingHidden();
|
|
730
|
+
}, 100);
|
|
731
|
+
}
|
|
567
732
|
function setupObserver() {
|
|
733
|
+
window.addEventListener("resize", handleResize, { passive: true });
|
|
568
734
|
observer = new MutationObserver(handleMutations);
|
|
569
735
|
observer.observe(document.body, {
|
|
570
736
|
childList: true,
|
|
@@ -587,11 +753,15 @@ function createStegaAutoClean(options) {
|
|
|
587
753
|
clearTimeout(debounceTimeout);
|
|
588
754
|
debounceTimeout = null;
|
|
589
755
|
}
|
|
756
|
+
if (resizeTimeout) {
|
|
757
|
+
clearTimeout(resizeTimeout);
|
|
758
|
+
resizeTimeout = null;
|
|
759
|
+
}
|
|
590
760
|
if (observer) {
|
|
591
761
|
observer.disconnect();
|
|
592
762
|
observer = null;
|
|
763
|
+
window.removeEventListener("resize", handleResize);
|
|
593
764
|
}
|
|
594
|
-
cleaned = /* @__PURE__ */ new WeakMap();
|
|
595
765
|
pendingHidden = /* @__PURE__ */ new Set();
|
|
596
766
|
pendingDirty = /* @__PURE__ */ new Set();
|
|
597
767
|
debug3.log("stega auto-clean stopped");
|
|
@@ -599,6 +769,439 @@ function createStegaAutoClean(options) {
|
|
|
599
769
|
return { start, stop };
|
|
600
770
|
}
|
|
601
771
|
|
|
772
|
+
// src/core/visual-editor/overlay.ts
|
|
773
|
+
var HOST_TAG = "prepr-visual-editor";
|
|
774
|
+
var SCROLL_SETTLE_MS = 120;
|
|
775
|
+
var LABEL_SPACE_PX = 22;
|
|
776
|
+
var BOX_PADDING_PX = 3;
|
|
777
|
+
var LABEL_MARGIN_PX = 4;
|
|
778
|
+
var PROBE_POINTS = [
|
|
779
|
+
[0.5, 0.5],
|
|
780
|
+
[0.15, 0.2],
|
|
781
|
+
[0.85, 0.2],
|
|
782
|
+
[0.15, 0.8],
|
|
783
|
+
[0.85, 0.8]
|
|
784
|
+
];
|
|
785
|
+
function overlapsBox(area, rect) {
|
|
786
|
+
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;
|
|
787
|
+
}
|
|
788
|
+
function overlaps(a2, b2) {
|
|
789
|
+
return a2.left < b2.right && b2.left < a2.right && a2.top < b2.bottom && b2.top < a2.bottom;
|
|
790
|
+
}
|
|
791
|
+
function coverOf(hit, target, x2, y2) {
|
|
792
|
+
let cover = hit.getBoundingClientRect();
|
|
793
|
+
for (let el = hit.parentElement; el && !el.contains(target) && el !== document.body; el = el.parentElement) {
|
|
794
|
+
const r2 = el.getBoundingClientRect();
|
|
795
|
+
if (x2 < r2.left || x2 >= r2.right || y2 < r2.top || y2 >= r2.bottom) break;
|
|
796
|
+
cover = r2;
|
|
797
|
+
}
|
|
798
|
+
return cover;
|
|
799
|
+
}
|
|
800
|
+
function mergeAreas(areas) {
|
|
801
|
+
const merged = areas.slice();
|
|
802
|
+
for (let i2 = 0; i2 < merged.length; i2++) {
|
|
803
|
+
for (let j2 = i2 + 1; j2 < merged.length; j2++) {
|
|
804
|
+
if (!overlaps(merged[i2], merged[j2])) continue;
|
|
805
|
+
merged[i2] = {
|
|
806
|
+
left: Math.min(merged[i2].left, merged[j2].left),
|
|
807
|
+
top: Math.min(merged[i2].top, merged[j2].top),
|
|
808
|
+
right: Math.max(merged[i2].right, merged[j2].right),
|
|
809
|
+
bottom: Math.max(merged[i2].bottom, merged[j2].bottom)
|
|
810
|
+
};
|
|
811
|
+
merged.splice(j2, 1);
|
|
812
|
+
j2 = i2;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return merged;
|
|
816
|
+
}
|
|
817
|
+
function findCovers(target, rect) {
|
|
818
|
+
const width = window.innerWidth;
|
|
819
|
+
const height = window.innerHeight;
|
|
820
|
+
const covers = [];
|
|
821
|
+
const seen = /* @__PURE__ */ new Set();
|
|
822
|
+
let probed = false;
|
|
823
|
+
let reached = false;
|
|
824
|
+
for (const [fx, fy] of PROBE_POINTS) {
|
|
825
|
+
const x2 = rect.left + rect.width * fx;
|
|
826
|
+
const y2 = rect.top + rect.height * fy;
|
|
827
|
+
if (x2 < 0 || y2 < 0 || x2 >= width || y2 >= height) continue;
|
|
828
|
+
probed = true;
|
|
829
|
+
const hit = document.elementFromPoint(x2, y2);
|
|
830
|
+
if (!hit || hit === target || target.contains(hit)) {
|
|
831
|
+
reached = true;
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
if (seen.has(hit)) continue;
|
|
835
|
+
seen.add(hit);
|
|
836
|
+
if (covers.some(
|
|
837
|
+
(c2) => x2 >= c2.left && x2 < c2.right && y2 >= c2.top && y2 < c2.bottom
|
|
838
|
+
)) {
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
covers.push(coverOf(hit, target, x2, y2));
|
|
842
|
+
}
|
|
843
|
+
if (!probed) return [];
|
|
844
|
+
return reached ? mergeAreas(covers) : null;
|
|
845
|
+
}
|
|
846
|
+
function clipPath(x2, y2, covers) {
|
|
847
|
+
const far = 1e5;
|
|
848
|
+
const points = [
|
|
849
|
+
`${-far}px ${-far}px`,
|
|
850
|
+
`${far}px ${-far}px`,
|
|
851
|
+
`${far}px ${far}px`,
|
|
852
|
+
`${-far}px ${far}px`,
|
|
853
|
+
`${-far}px ${-far}px`
|
|
854
|
+
];
|
|
855
|
+
for (const c2 of covers) {
|
|
856
|
+
const l2 = c2.left - x2;
|
|
857
|
+
const t3 = c2.top - y2;
|
|
858
|
+
const r2 = c2.right - x2;
|
|
859
|
+
const b2 = c2.bottom - y2;
|
|
860
|
+
points.push(
|
|
861
|
+
`${l2}px ${t3}px`,
|
|
862
|
+
`${r2}px ${t3}px`,
|
|
863
|
+
`${r2}px ${b2}px`,
|
|
864
|
+
`${l2}px ${b2}px`,
|
|
865
|
+
`${l2}px ${t3}px`,
|
|
866
|
+
`${-far}px ${-far}px`
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
return `polygon(evenodd, ${points.join(", ")})`;
|
|
870
|
+
}
|
|
871
|
+
var STYLES = `
|
|
872
|
+
:host {
|
|
873
|
+
all: initial;
|
|
874
|
+
position: fixed;
|
|
875
|
+
inset: 0;
|
|
876
|
+
pointer-events: none;
|
|
877
|
+
z-index: 2147483646;
|
|
878
|
+
contain: strict;
|
|
879
|
+
}
|
|
880
|
+
.box {
|
|
881
|
+
position: absolute;
|
|
882
|
+
top: 0;
|
|
883
|
+
left: 0;
|
|
884
|
+
box-sizing: border-box;
|
|
885
|
+
border-radius: 6px;
|
|
886
|
+
opacity: 0;
|
|
887
|
+
will-change: transform, width, height;
|
|
888
|
+
transition:
|
|
889
|
+
transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
|
|
890
|
+
width 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
|
|
891
|
+
height 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
|
|
892
|
+
opacity 140ms ease;
|
|
893
|
+
}
|
|
894
|
+
.box[data-visible] { opacity: 1; }
|
|
895
|
+
.box[data-instant], :host([data-scrolling]) .box {
|
|
896
|
+
transition: opacity 140ms ease;
|
|
897
|
+
}
|
|
898
|
+
.hover {
|
|
899
|
+
border: 1.5px solid rgba(79, 70, 229, 0.9);
|
|
900
|
+
background: rgba(79, 70, 229, 0.06);
|
|
901
|
+
}
|
|
902
|
+
.selected {
|
|
903
|
+
border: 2px solid #4f46e5;
|
|
904
|
+
box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.18);
|
|
905
|
+
}
|
|
906
|
+
.label {
|
|
907
|
+
position: absolute;
|
|
908
|
+
left: -2px;
|
|
909
|
+
bottom: 100%;
|
|
910
|
+
margin-bottom: 4px;
|
|
911
|
+
padding: 3px 7px;
|
|
912
|
+
border-radius: 5px;
|
|
913
|
+
background: #4f46e5;
|
|
914
|
+
color: #fff;
|
|
915
|
+
font: 500 11px/1.3 ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
916
|
+
letter-spacing: 0.01em;
|
|
917
|
+
white-space: nowrap;
|
|
918
|
+
display: flex;
|
|
919
|
+
max-width: 240px;
|
|
920
|
+
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.25);
|
|
921
|
+
}
|
|
922
|
+
.text {
|
|
923
|
+
min-width: 0;
|
|
924
|
+
overflow: hidden;
|
|
925
|
+
text-overflow: ellipsis;
|
|
926
|
+
}
|
|
927
|
+
.hover .label { background: rgba(79, 70, 229, 0.92); }
|
|
928
|
+
/* Content of another item than the one open in the editor: still clickable,
|
|
929
|
+
but visibly not part of the item being edited. */
|
|
930
|
+
.hover[data-item-kind='other'] {
|
|
931
|
+
border: 1.5px dashed #a5b4fc;
|
|
932
|
+
background: rgba(100, 116, 139, 0.05);
|
|
933
|
+
}
|
|
934
|
+
.hover[data-item-kind='other'] .label { background: #64748b; }
|
|
935
|
+
.hover[data-item-kind='referenced'] {
|
|
936
|
+
border: 1.5px solid #a5b4fc;
|
|
937
|
+
background: rgba(79, 70, 229, 0.03);
|
|
938
|
+
}
|
|
939
|
+
.hover[data-item-kind='referenced'] .label { background: #818cf8; }
|
|
940
|
+
.box[data-label-covered] .label { visibility: hidden; }
|
|
941
|
+
.box[data-label-inside] .label {
|
|
942
|
+
bottom: auto;
|
|
943
|
+
top: 2px;
|
|
944
|
+
left: 2px;
|
|
945
|
+
margin: 0;
|
|
946
|
+
}
|
|
947
|
+
/* A clickable hover label (edit mode on the live site). Only while shown, so
|
|
948
|
+
a fading box never leaves an invisible click target behind. */
|
|
949
|
+
:host([data-label-action]) .hover[data-visible] .label {
|
|
950
|
+
pointer-events: auto;
|
|
951
|
+
cursor: pointer;
|
|
952
|
+
}
|
|
953
|
+
:host([data-label-action]) .hover .label::after {
|
|
954
|
+
content: '\\2197';
|
|
955
|
+
margin-left: 5px;
|
|
956
|
+
}
|
|
957
|
+
:host([data-label-action]) .hover .label:hover { background: #4338ca; }
|
|
958
|
+
/* Bridges the gap between the label and its element, so the pointer can
|
|
959
|
+
travel from one to the other without the hover ending on the way. */
|
|
960
|
+
:host([data-label-action]) .hover .label::before {
|
|
961
|
+
content: '';
|
|
962
|
+
position: absolute;
|
|
963
|
+
left: 0;
|
|
964
|
+
right: 0;
|
|
965
|
+
top: 100%;
|
|
966
|
+
height: ${LABEL_MARGIN_PX + BOX_PADDING_PX + 2}px;
|
|
967
|
+
}
|
|
968
|
+
@media (prefers-reduced-motion: reduce) {
|
|
969
|
+
.box { transition: opacity 80ms linear; }
|
|
970
|
+
}
|
|
971
|
+
`;
|
|
972
|
+
var VisualEditorOverlay = class {
|
|
973
|
+
constructor(options = {}) {
|
|
974
|
+
this.host = null;
|
|
975
|
+
this.hoverBox = null;
|
|
976
|
+
this.selectedBox = null;
|
|
977
|
+
this.frame = null;
|
|
978
|
+
this.scrollTimeout = null;
|
|
979
|
+
this.resizeObserver = null;
|
|
980
|
+
this.onLabelClick = options.onLabelClick;
|
|
981
|
+
}
|
|
982
|
+
mount() {
|
|
983
|
+
if (this.host) return;
|
|
984
|
+
const host = document.createElement(HOST_TAG);
|
|
985
|
+
const shadow = host.attachShadow({ mode: "open" });
|
|
986
|
+
const style = document.createElement("style");
|
|
987
|
+
style.textContent = STYLES;
|
|
988
|
+
shadow.appendChild(style);
|
|
989
|
+
this.hoverBox = this.createBox(shadow, "hover");
|
|
990
|
+
this.selectedBox = this.createBox(shadow, "selected");
|
|
991
|
+
const onLabelClick = this.onLabelClick;
|
|
992
|
+
if (onLabelClick) {
|
|
993
|
+
host.setAttribute("data-label-action", "");
|
|
994
|
+
const hoverBox = this.hoverBox;
|
|
995
|
+
hoverBox.label.addEventListener("click", () => {
|
|
996
|
+
if (hoverBox.target) onLabelClick(hoverBox.target);
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
document.documentElement.appendChild(host);
|
|
1000
|
+
this.host = host;
|
|
1001
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1002
|
+
this.resizeObserver = new ResizeObserver(() => this.schedule());
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
unmount() {
|
|
1006
|
+
if (this.frame !== null) cancelAnimationFrame(this.frame);
|
|
1007
|
+
if (this.scrollTimeout) clearTimeout(this.scrollTimeout);
|
|
1008
|
+
this.frame = null;
|
|
1009
|
+
this.scrollTimeout = null;
|
|
1010
|
+
this.resizeObserver?.disconnect();
|
|
1011
|
+
this.resizeObserver = null;
|
|
1012
|
+
this.host?.remove();
|
|
1013
|
+
this.host = null;
|
|
1014
|
+
this.hoverBox = null;
|
|
1015
|
+
this.selectedBox = null;
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Whether a pointer event target is this chrome: events from inside the
|
|
1019
|
+
* shadow root reach the page retargeted to its host.
|
|
1020
|
+
*/
|
|
1021
|
+
owns(node) {
|
|
1022
|
+
return node !== null && node === this.host;
|
|
1023
|
+
}
|
|
1024
|
+
getHoverTarget() {
|
|
1025
|
+
return this.hoverBox?.target ?? null;
|
|
1026
|
+
}
|
|
1027
|
+
getSelectedTarget() {
|
|
1028
|
+
return this.selectedBox?.target ?? null;
|
|
1029
|
+
}
|
|
1030
|
+
setHover(target, label, options = {}) {
|
|
1031
|
+
if (target) {
|
|
1032
|
+
this.hoverBox?.root.setAttribute(
|
|
1033
|
+
"data-item-kind",
|
|
1034
|
+
options.kind ?? "current"
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
this.retarget(this.hoverBox, target, label);
|
|
1038
|
+
}
|
|
1039
|
+
setSelected(target, label) {
|
|
1040
|
+
this.retarget(this.selectedBox, target, label);
|
|
1041
|
+
}
|
|
1042
|
+
/** Call on every scroll; positions follow in the next frame, untransitioned. */
|
|
1043
|
+
onScroll() {
|
|
1044
|
+
if (!this.host) return;
|
|
1045
|
+
this.host.setAttribute("data-scrolling", "");
|
|
1046
|
+
if (this.scrollTimeout) clearTimeout(this.scrollTimeout);
|
|
1047
|
+
this.scrollTimeout = setTimeout(() => {
|
|
1048
|
+
this.host?.removeAttribute("data-scrolling");
|
|
1049
|
+
this.scrollTimeout = null;
|
|
1050
|
+
}, SCROLL_SETTLE_MS);
|
|
1051
|
+
this.schedule();
|
|
1052
|
+
}
|
|
1053
|
+
/** Re-measure both boxes in the next animation frame. */
|
|
1054
|
+
schedule() {
|
|
1055
|
+
if (this.frame !== null || !this.host) return;
|
|
1056
|
+
this.frame = requestAnimationFrame(() => {
|
|
1057
|
+
this.frame = null;
|
|
1058
|
+
this.render();
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
createBox(shadow, kind) {
|
|
1062
|
+
const root = document.createElement("div");
|
|
1063
|
+
root.className = `box ${kind}`;
|
|
1064
|
+
const label = document.createElement("div");
|
|
1065
|
+
label.className = "label";
|
|
1066
|
+
const text = document.createElement("span");
|
|
1067
|
+
text.className = "text";
|
|
1068
|
+
label.appendChild(text);
|
|
1069
|
+
root.appendChild(label);
|
|
1070
|
+
shadow.appendChild(root);
|
|
1071
|
+
return { root, label, text, target: null };
|
|
1072
|
+
}
|
|
1073
|
+
retarget(box, target, label) {
|
|
1074
|
+
if (!box || box.target === target) return;
|
|
1075
|
+
const previous = box.target;
|
|
1076
|
+
if (previous) this.resizeObserver?.unobserve(previous);
|
|
1077
|
+
box.target = target;
|
|
1078
|
+
if (target) {
|
|
1079
|
+
this.resizeObserver?.observe(target);
|
|
1080
|
+
box.text.textContent = label;
|
|
1081
|
+
if (!box.root.hasAttribute("data-visible")) {
|
|
1082
|
+
box.root.setAttribute("data-instant", "");
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
this.schedule();
|
|
1086
|
+
}
|
|
1087
|
+
render() {
|
|
1088
|
+
const hover = this.hoverBox ? this.measure(this.hoverBox) : null;
|
|
1089
|
+
const selected = this.selectedBox ? this.measure(this.selectedBox) : null;
|
|
1090
|
+
if (this.hoverBox) this.paint(this.hoverBox, hover, selected);
|
|
1091
|
+
if (this.selectedBox) this.paint(this.selectedBox, selected, hover);
|
|
1092
|
+
}
|
|
1093
|
+
measure(box) {
|
|
1094
|
+
const target = box.target;
|
|
1095
|
+
if (!target) return null;
|
|
1096
|
+
if (!target.isConnected) {
|
|
1097
|
+
this.resizeObserver?.unobserve(target);
|
|
1098
|
+
box.target = null;
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
const rect = target.getBoundingClientRect();
|
|
1102
|
+
if (rect.width === 0 && rect.height === 0) return null;
|
|
1103
|
+
const covers = findCovers(target, rect);
|
|
1104
|
+
if (!covers) return null;
|
|
1105
|
+
return {
|
|
1106
|
+
rect,
|
|
1107
|
+
covers,
|
|
1108
|
+
labelWidth: covers.length ? box.label.offsetWidth : 0,
|
|
1109
|
+
labelHeight: covers.length ? box.label.offsetHeight : 0
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
paint(box, measurement, other = null) {
|
|
1113
|
+
const root = box.root;
|
|
1114
|
+
if (!measurement) {
|
|
1115
|
+
root.removeAttribute("data-visible");
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const { rect, covers } = measurement;
|
|
1119
|
+
const x2 = rect.left - BOX_PADDING_PX;
|
|
1120
|
+
const y2 = rect.top - BOX_PADDING_PX;
|
|
1121
|
+
const labelAbove = {
|
|
1122
|
+
left: x2 - 2,
|
|
1123
|
+
top: y2 - LABEL_SPACE_PX,
|
|
1124
|
+
right: x2 - 2 + (measurement.labelWidth || Math.min(240, rect.width)),
|
|
1125
|
+
bottom: y2
|
|
1126
|
+
};
|
|
1127
|
+
const labelInside = y2 < LABEL_SPACE_PX || other !== null && other.rect !== rect && overlapsBox(labelAbove, other.rect);
|
|
1128
|
+
root.style.transform = `translate3d(${x2}px, ${y2}px, 0)`;
|
|
1129
|
+
root.style.width = `${rect.width + BOX_PADDING_PX * 2}px`;
|
|
1130
|
+
root.style.height = `${rect.height + BOX_PADDING_PX * 2}px`;
|
|
1131
|
+
root.toggleAttribute("data-label-inside", labelInside);
|
|
1132
|
+
root.style.clipPath = covers.length ? clipPath(x2, y2, covers) : "";
|
|
1133
|
+
const labelLeft = labelInside ? x2 + 2 : x2 - 2;
|
|
1134
|
+
const labelTop = labelInside ? y2 + 2 : y2 - LABEL_MARGIN_PX - measurement.labelHeight;
|
|
1135
|
+
const label = {
|
|
1136
|
+
left: labelLeft,
|
|
1137
|
+
top: labelTop,
|
|
1138
|
+
right: labelLeft + measurement.labelWidth,
|
|
1139
|
+
bottom: labelTop + measurement.labelHeight
|
|
1140
|
+
};
|
|
1141
|
+
root.toggleAttribute(
|
|
1142
|
+
"data-label-covered",
|
|
1143
|
+
covers.some((c2) => overlaps(c2, label))
|
|
1144
|
+
);
|
|
1145
|
+
if (root.hasAttribute("data-instant")) {
|
|
1146
|
+
root.setAttribute("data-visible", "");
|
|
1147
|
+
requestAnimationFrame(() => root.removeAttribute("data-instant"));
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
root.setAttribute("data-visible", "");
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
// src/core/visual-editor/field-path.ts
|
|
1155
|
+
var INDEX_SEGMENT = /^\d+$/;
|
|
1156
|
+
var WORD_SEPARATORS = /[_-]+/g;
|
|
1157
|
+
function normalizeFieldPath(path) {
|
|
1158
|
+
return path.split(".").map(
|
|
1159
|
+
(segment) => INDEX_SEGMENT.test(segment) ? String(Number.parseInt(segment, 10)) : segment
|
|
1160
|
+
).join(".");
|
|
1161
|
+
}
|
|
1162
|
+
var GENERIC_LEAVES = /* @__PURE__ */ new Set(["value", "text", "body", "content", "label"]);
|
|
1163
|
+
function humanize(name) {
|
|
1164
|
+
const words = name.replace(WORD_SEPARATORS, " ").trim();
|
|
1165
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
1166
|
+
}
|
|
1167
|
+
function fieldLabel(path, locale = "en") {
|
|
1168
|
+
if (!path) return t("visualEditor.edit", locale);
|
|
1169
|
+
const segments = path.split(".").filter((s2) => s2 && s2 !== "items" && !INDEX_SEGMENT.test(s2));
|
|
1170
|
+
const name = segments[segments.length - 1];
|
|
1171
|
+
if (!name) return t("visualEditor.edit", locale);
|
|
1172
|
+
const parent = segments[segments.length - 2];
|
|
1173
|
+
if (parent && GENERIC_LEAVES.has(name)) {
|
|
1174
|
+
return `${humanize(parent)} \u203A ${humanize(name)}`;
|
|
1175
|
+
}
|
|
1176
|
+
return humanize(name);
|
|
1177
|
+
}
|
|
1178
|
+
function findFieldElement(lookup) {
|
|
1179
|
+
if (!lookup.field) return null;
|
|
1180
|
+
const candidates = document.querySelectorAll(
|
|
1181
|
+
"[data-prepr-encoded]"
|
|
1182
|
+
);
|
|
1183
|
+
if (lookup.entryId) {
|
|
1184
|
+
for (const candidate of candidates) {
|
|
1185
|
+
if (candidate.getAttribute("data-prepr-entry-id") === lookup.entryId) {
|
|
1186
|
+
return candidate;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
for (const path of [lookup.field, lookup.fieldById]) {
|
|
1191
|
+
if (!path) continue;
|
|
1192
|
+
const wanted = normalizeFieldPath(path);
|
|
1193
|
+
for (const candidate of candidates) {
|
|
1194
|
+
const field = candidate.getAttribute("data-prepr-field");
|
|
1195
|
+
if (!field || normalizeFieldPath(field) !== wanted) continue;
|
|
1196
|
+
if (lookup.id && candidate.getAttribute("data-prepr-id") !== lookup.id) {
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
return candidate;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
return null;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
602
1205
|
// src/core/stega/elements.ts
|
|
603
1206
|
var debug4 = createScopedLogger("stega:elements");
|
|
604
1207
|
var StegaElements = class {
|
|
@@ -688,235 +1291,22 @@ var StegaElements = class {
|
|
|
688
1291
|
attributeFilter: ["class", "style", "hidden", "open", "aria-expanded"]
|
|
689
1292
|
});
|
|
690
1293
|
}
|
|
691
|
-
/**
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
1294
|
+
/**
|
|
1295
|
+
* Stops watching for new content. Tags stay on their elements: the page's
|
|
1296
|
+
* stega characters are stripped once they are read, so a tag removed here
|
|
1297
|
+
* could never be rebuilt when edit mode is turned back on.
|
|
1298
|
+
*/
|
|
1299
|
+
disconnect() {
|
|
696
1300
|
if (this.observer) {
|
|
697
1301
|
this.observer.disconnect();
|
|
698
1302
|
this.observer = null;
|
|
699
1303
|
}
|
|
700
|
-
document.querySelectorAll("[data-prepr-encoded]").forEach((element) => {
|
|
701
|
-
element.removeAttribute("data-prepr-encoded");
|
|
702
|
-
element.removeAttribute("data-prepr-href");
|
|
703
|
-
element.removeAttribute("data-prepr-origin");
|
|
704
|
-
element.removeAttribute("data-prepr-id");
|
|
705
|
-
element.removeAttribute("data-prepr-field");
|
|
706
|
-
element.removeAttribute("data-prepr-locale");
|
|
707
|
-
element.classList.remove("prepr-overlay-active");
|
|
708
|
-
});
|
|
709
1304
|
this.elements = void 0;
|
|
710
1305
|
}
|
|
711
1306
|
};
|
|
712
1307
|
|
|
713
|
-
// src/core/stega/overlay.ts
|
|
714
|
-
var debug5 = createScopedLogger("stega:overlay");
|
|
715
|
-
var StegaOverlay = class {
|
|
716
|
-
constructor(onEdit) {
|
|
717
|
-
this.overlay = null;
|
|
718
|
-
this.tooltip = null;
|
|
719
|
-
this.hideTimeout = null;
|
|
720
|
-
this.currentElement = null;
|
|
721
|
-
this.onEdit = onEdit;
|
|
722
|
-
}
|
|
723
|
-
create() {
|
|
724
|
-
const overlay = document.createElement("div");
|
|
725
|
-
overlay.className = "prepr-overlay";
|
|
726
|
-
overlay.style.display = "none";
|
|
727
|
-
const tooltip = document.createElement("div");
|
|
728
|
-
tooltip.className = "prepr-tooltip";
|
|
729
|
-
tooltip.style.display = "none";
|
|
730
|
-
document.body.appendChild(overlay);
|
|
731
|
-
document.body.appendChild(tooltip);
|
|
732
|
-
this.overlay = overlay;
|
|
733
|
-
this.tooltip = tooltip;
|
|
734
|
-
return { overlay, tooltip };
|
|
735
|
-
}
|
|
736
|
-
getTooltip() {
|
|
737
|
-
return this.tooltip;
|
|
738
|
-
}
|
|
739
|
-
hasCurrentElement() {
|
|
740
|
-
return this.currentElement !== null;
|
|
741
|
-
}
|
|
742
|
-
clearHideTimeout() {
|
|
743
|
-
if (this.hideTimeout) {
|
|
744
|
-
clearTimeout(this.hideTimeout);
|
|
745
|
-
this.hideTimeout = null;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
show(element) {
|
|
749
|
-
if (!this.overlay || !this.tooltip) return;
|
|
750
|
-
this.clearHideTimeout();
|
|
751
|
-
if (this.currentElement && this.currentElement !== element) {
|
|
752
|
-
this.currentElement.classList.remove("prepr-overlay-active");
|
|
753
|
-
}
|
|
754
|
-
const rect = element.getBoundingClientRect();
|
|
755
|
-
const href = element.getAttribute("data-prepr-href");
|
|
756
|
-
const origin = element.getAttribute("data-prepr-origin");
|
|
757
|
-
const id = element.getAttribute("data-prepr-id") ?? void 0;
|
|
758
|
-
const field = element.getAttribute("data-prepr-field") ?? void 0;
|
|
759
|
-
const locale = element.getAttribute("data-prepr-locale") ?? void 0;
|
|
760
|
-
debug5.log("showing overlay", { href, origin });
|
|
761
|
-
const overlay = this.overlay;
|
|
762
|
-
overlay.style.display = "block";
|
|
763
|
-
overlay.style.top = `${rect.top + window.scrollY - 2}px`;
|
|
764
|
-
overlay.style.left = `${rect.left + window.scrollX - 4}px`;
|
|
765
|
-
overlay.style.width = `${rect.width + 8}px`;
|
|
766
|
-
overlay.style.height = `${rect.height + 4}px`;
|
|
767
|
-
const tooltip = this.tooltip;
|
|
768
|
-
if (href && origin) {
|
|
769
|
-
const MIN_WIDTH_FOR_TEXT = 80;
|
|
770
|
-
const isCompact = rect.width < MIN_WIDTH_FOR_TEXT;
|
|
771
|
-
tooltip.textContent = isCompact ? "\u2197" : `${origin} \u2197`;
|
|
772
|
-
tooltip.style.display = "block";
|
|
773
|
-
tooltip.style.minWidth = isCompact ? "auto" : "80px";
|
|
774
|
-
requestAnimationFrame(() => {
|
|
775
|
-
let top = rect.top + window.scrollY - tooltip.clientHeight - 2;
|
|
776
|
-
let left = rect.right + 4 - tooltip.clientWidth;
|
|
777
|
-
const minTop = window.scrollY + 4;
|
|
778
|
-
const maxTop = window.scrollY + window.innerHeight - tooltip.clientHeight - 4;
|
|
779
|
-
const minLeft = window.scrollX + 4;
|
|
780
|
-
const maxLeft = window.scrollX + window.innerWidth - tooltip.clientWidth - 4;
|
|
781
|
-
const below = top < minTop;
|
|
782
|
-
if (below) {
|
|
783
|
-
top = rect.bottom + window.scrollY + 2;
|
|
784
|
-
}
|
|
785
|
-
top = Math.max(minTop, Math.min(top, maxTop));
|
|
786
|
-
left = Math.max(minLeft, Math.min(left, maxLeft));
|
|
787
|
-
tooltip.dataset.preprPlacement = below ? "below" : "above";
|
|
788
|
-
tooltip.style.top = `${top}px`;
|
|
789
|
-
tooltip.style.left = `${left}px`;
|
|
790
|
-
});
|
|
791
|
-
tooltip.onclick = () => {
|
|
792
|
-
this.onEdit({ href, origin, id, field, locale });
|
|
793
|
-
};
|
|
794
|
-
}
|
|
795
|
-
this.currentElement = element;
|
|
796
|
-
element.classList.add("prepr-overlay-active");
|
|
797
|
-
}
|
|
798
|
-
hideImmediate() {
|
|
799
|
-
this.clearHideTimeout();
|
|
800
|
-
if (this.overlay) this.overlay.style.display = "none";
|
|
801
|
-
if (this.tooltip) this.tooltip.style.display = "none";
|
|
802
|
-
if (this.currentElement) {
|
|
803
|
-
this.currentElement.classList.remove("prepr-overlay-active");
|
|
804
|
-
this.currentElement = null;
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
hide() {
|
|
808
|
-
if (!this.overlay || !this.tooltip) return;
|
|
809
|
-
this.clearHideTimeout();
|
|
810
|
-
this.hideTimeout = setTimeout(() => this.hideImmediate(), 100);
|
|
811
|
-
}
|
|
812
|
-
cleanup() {
|
|
813
|
-
this.clearHideTimeout();
|
|
814
|
-
if (this.overlay) {
|
|
815
|
-
this.overlay.style.display = "none";
|
|
816
|
-
this.overlay.parentNode?.removeChild(this.overlay);
|
|
817
|
-
this.overlay = null;
|
|
818
|
-
}
|
|
819
|
-
if (this.tooltip) {
|
|
820
|
-
this.tooltip.style.display = "none";
|
|
821
|
-
this.tooltip.parentNode?.removeChild(this.tooltip);
|
|
822
|
-
this.tooltip = null;
|
|
823
|
-
}
|
|
824
|
-
if (this.currentElement) {
|
|
825
|
-
this.currentElement.classList.remove("prepr-overlay-active");
|
|
826
|
-
this.currentElement = null;
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
};
|
|
830
|
-
|
|
831
|
-
// src/core/stega/proximity.ts
|
|
832
|
-
var debug6 = createScopedLogger("stega:proximity");
|
|
833
|
-
var StegaProximity = class {
|
|
834
|
-
constructor() {
|
|
835
|
-
this.highlighted = /* @__PURE__ */ new Set();
|
|
836
|
-
this.visible = /* @__PURE__ */ new Set();
|
|
837
|
-
this.observer = null;
|
|
838
|
-
this.getEncodedElements = createElementCache(
|
|
839
|
-
"[data-prepr-encoded]",
|
|
840
|
-
200
|
|
841
|
-
);
|
|
842
|
-
}
|
|
843
|
-
/** Call after the encoded element set changes — rebuilds the observer. */
|
|
844
|
-
refreshObserving() {
|
|
845
|
-
try {
|
|
846
|
-
if (typeof IntersectionObserver === "undefined") return;
|
|
847
|
-
if (this.observer) {
|
|
848
|
-
this.observer.disconnect();
|
|
849
|
-
this.observer = null;
|
|
850
|
-
}
|
|
851
|
-
this.visible = /* @__PURE__ */ new Set();
|
|
852
|
-
this.observer = new IntersectionObserver(
|
|
853
|
-
(entries) => {
|
|
854
|
-
entries.forEach((entry) => {
|
|
855
|
-
const el = entry.target;
|
|
856
|
-
if (entry.isIntersecting) {
|
|
857
|
-
this.visible.add(el);
|
|
858
|
-
} else {
|
|
859
|
-
this.visible.delete(el);
|
|
860
|
-
}
|
|
861
|
-
});
|
|
862
|
-
},
|
|
863
|
-
{ root: null, rootMargin: "0px", threshold: 0 }
|
|
864
|
-
);
|
|
865
|
-
const nodes = this.getEncodedElements();
|
|
866
|
-
nodes.forEach((el) => this.observer.observe(el));
|
|
867
|
-
debug6.log("observing", nodes.length, "encoded elements");
|
|
868
|
-
} catch (error) {
|
|
869
|
-
debug6.log("error setting up IntersectionObserver", error);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
stopObserving() {
|
|
873
|
-
if (this.observer) {
|
|
874
|
-
this.observer.disconnect();
|
|
875
|
-
this.observer = null;
|
|
876
|
-
this.visible.clear();
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
updateElementGradients(cursorX, cursorY) {
|
|
880
|
-
const candidates = this.visible.size > 0 ? Array.from(this.visible) : Array.from(this.getEncodedElements());
|
|
881
|
-
const newHighlighted = /* @__PURE__ */ new Set();
|
|
882
|
-
candidates.forEach((element) => {
|
|
883
|
-
const rect = element.getBoundingClientRect();
|
|
884
|
-
const distance = Math.min(
|
|
885
|
-
Math.abs(cursorX - rect.left),
|
|
886
|
-
Math.abs(cursorX - rect.right),
|
|
887
|
-
Math.abs(cursorY - rect.top),
|
|
888
|
-
Math.abs(cursorY - rect.bottom)
|
|
889
|
-
);
|
|
890
|
-
if (distance < 150) {
|
|
891
|
-
const relativeX = cursorX - rect.left;
|
|
892
|
-
const relativeY = cursorY - rect.top;
|
|
893
|
-
element.style.setProperty("--cursor-x", `${relativeX}px`);
|
|
894
|
-
element.style.setProperty("--cursor-y", `${relativeY}px`);
|
|
895
|
-
const baseGradientSize = Math.max(
|
|
896
|
-
150,
|
|
897
|
-
Math.max(rect.width, rect.height) * 1.1
|
|
898
|
-
);
|
|
899
|
-
const distanceScale = Math.max(0, (400 - distance) / 400);
|
|
900
|
-
const gradientSize = baseGradientSize * distanceScale;
|
|
901
|
-
element.style.setProperty("--gradient-size", `${gradientSize}px`);
|
|
902
|
-
element.classList.add("prepr-proximity-highlight");
|
|
903
|
-
newHighlighted.add(element);
|
|
904
|
-
} else {
|
|
905
|
-
element.classList.remove("prepr-proximity-highlight");
|
|
906
|
-
}
|
|
907
|
-
});
|
|
908
|
-
this.highlighted = newHighlighted;
|
|
909
|
-
}
|
|
910
|
-
clearAllHighlights() {
|
|
911
|
-
this.highlighted.forEach(
|
|
912
|
-
(element) => element.classList.remove("prepr-proximity-highlight")
|
|
913
|
-
);
|
|
914
|
-
this.highlighted.clear();
|
|
915
|
-
}
|
|
916
|
-
};
|
|
917
|
-
|
|
918
1308
|
// src/core/stega/stega.generated.ts
|
|
919
|
-
var STEGA_STYLES = '
|
|
1309
|
+
var STEGA_STYLES = '@property --prepr-presence{syntax:"<number>";inherits:true;initial-value:1}[data-prepr-encoded]{outline:1px dashed rgba(79,70,229,calc(.3 * var(--prepr-presence,1)));outline-offset:2px}';
|
|
920
1310
|
|
|
921
1311
|
// src/core/stega/styles.ts
|
|
922
1312
|
var STEGA_STYLE_MARKER = "data-prepr-stega";
|
|
@@ -936,46 +1326,80 @@ function removeStegaStyles() {
|
|
|
936
1326
|
}
|
|
937
1327
|
|
|
938
1328
|
// src/core/stega/scan.ts
|
|
939
|
-
var
|
|
1329
|
+
var debug5 = createScopedLogger("stega:scan");
|
|
1330
|
+
var ENCODED_SELECTOR = "[data-prepr-encoded]";
|
|
1331
|
+
var PRESENCE_FADE_MS = 400;
|
|
940
1332
|
var EDITOR_MODE_STYLES = "[data-prepr-encoded]{cursor:pointer}";
|
|
1333
|
+
function fadeInPresence() {
|
|
1334
|
+
const root = document.documentElement;
|
|
1335
|
+
if (typeof root.animate !== "function") return;
|
|
1336
|
+
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;
|
|
1337
|
+
root.animate(
|
|
1338
|
+
{ "--prepr-presence": ["0", "1"] },
|
|
1339
|
+
{ duration: PRESENCE_FADE_MS, easing: "ease-out" }
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
941
1342
|
function createStegaController(opts) {
|
|
942
1343
|
const tooltipEnabled = opts.tooltip ?? true;
|
|
943
|
-
const
|
|
944
|
-
const proximity = new StegaProximity();
|
|
1344
|
+
const locale = opts.locale ?? "en";
|
|
945
1345
|
const elements = new StegaElements();
|
|
1346
|
+
const overlay = new VisualEditorOverlay({
|
|
1347
|
+
onLabelClick: (target) => opts.onEdit(readEditPayload(target))
|
|
1348
|
+
});
|
|
946
1349
|
let initialized = false;
|
|
947
|
-
let
|
|
948
|
-
|
|
949
|
-
|
|
1350
|
+
let pointerFrame = null;
|
|
1351
|
+
let pointerTarget = null;
|
|
1352
|
+
let pointerX = 0;
|
|
1353
|
+
let pointerY = 0;
|
|
1354
|
+
let hitTestPending = false;
|
|
1355
|
+
const updateHover = () => {
|
|
1356
|
+
pointerFrame = null;
|
|
1357
|
+
if (hitTestPending) {
|
|
1358
|
+
hitTestPending = false;
|
|
1359
|
+
pointerTarget = document.elementFromPoint(pointerX, pointerY);
|
|
1360
|
+
}
|
|
1361
|
+
if (overlay.owns(pointerTarget)) return;
|
|
1362
|
+
const encoded = pointerTarget instanceof Element ? pointerTarget.closest(ENCODED_SELECTOR) : null;
|
|
1363
|
+
overlay.setHover(
|
|
1364
|
+
encoded,
|
|
1365
|
+
encoded ? fieldLabel(encoded.getAttribute("data-prepr-field"), locale) : ""
|
|
1366
|
+
);
|
|
950
1367
|
};
|
|
951
|
-
const
|
|
952
|
-
if (
|
|
953
|
-
|
|
1368
|
+
const scheduleHover = () => {
|
|
1369
|
+
if (pointerFrame === null) {
|
|
1370
|
+
pointerFrame = requestAnimationFrame(updateHover);
|
|
954
1371
|
}
|
|
955
1372
|
};
|
|
1373
|
+
const handlePointer = (e2) => {
|
|
1374
|
+
pointerTarget = e2.target;
|
|
1375
|
+
pointerX = e2.clientX;
|
|
1376
|
+
pointerY = e2.clientY;
|
|
1377
|
+
hitTestPending = false;
|
|
1378
|
+
scheduleHover();
|
|
1379
|
+
};
|
|
1380
|
+
const handlePointerLeave = () => {
|
|
1381
|
+
pointerTarget = null;
|
|
1382
|
+
hitTestPending = false;
|
|
1383
|
+
overlay.setHover(null, "");
|
|
1384
|
+
};
|
|
956
1385
|
const handleScroll = () => {
|
|
957
|
-
overlay.
|
|
958
|
-
|
|
1386
|
+
overlay.onScroll();
|
|
1387
|
+
if (pointerTarget === null) return;
|
|
1388
|
+
hitTestPending = true;
|
|
1389
|
+
scheduleHover();
|
|
959
1390
|
};
|
|
1391
|
+
const handleResize = () => overlay.schedule();
|
|
960
1392
|
const handleClick = (e2) => {
|
|
961
1393
|
const target = e2.target;
|
|
962
|
-
const encoded = target?.closest(
|
|
963
|
-
"[data-prepr-encoded]"
|
|
964
|
-
);
|
|
1394
|
+
const encoded = target?.closest(ENCODED_SELECTOR);
|
|
965
1395
|
if (!encoded) return;
|
|
966
1396
|
e2.preventDefault();
|
|
967
1397
|
e2.stopPropagation();
|
|
968
|
-
opts.onEdit(
|
|
969
|
-
href: encoded.getAttribute("data-prepr-href") ?? "",
|
|
970
|
-
origin: encoded.getAttribute("data-prepr-origin") ?? "",
|
|
971
|
-
id: encoded.getAttribute("data-prepr-id") ?? void 0,
|
|
972
|
-
field: encoded.getAttribute("data-prepr-field") ?? void 0,
|
|
973
|
-
locale: encoded.getAttribute("data-prepr-locale") ?? void 0
|
|
974
|
-
});
|
|
1398
|
+
opts.onEdit(readEditPayload(encoded));
|
|
975
1399
|
};
|
|
976
1400
|
const start = () => {
|
|
977
1401
|
if (initialized) {
|
|
978
|
-
|
|
1402
|
+
debug5.log("already initialized, skipping setup");
|
|
979
1403
|
return;
|
|
980
1404
|
}
|
|
981
1405
|
if (!tooltipEnabled) {
|
|
@@ -985,73 +1409,64 @@ function createStegaController(opts) {
|
|
|
985
1409
|
});
|
|
986
1410
|
document.addEventListener("click", handleClick, { capture: true });
|
|
987
1411
|
initialized = true;
|
|
988
|
-
|
|
1412
|
+
debug5.log("stega controller started (headless)");
|
|
989
1413
|
return;
|
|
990
1414
|
}
|
|
991
1415
|
injectStegaStyles();
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
tooltip.addEventListener("mouseleave", handleTooltipMouseLeave);
|
|
1416
|
+
fadeInPresence();
|
|
1417
|
+
overlay.mount();
|
|
995
1418
|
elements.scanDocument(true);
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const target = mouseEvent.target;
|
|
1001
|
-
if (!target) return;
|
|
1002
|
-
if (target.closest(".prepr-tooltip")) return;
|
|
1003
|
-
proximity.updateElementGradients(mouseEvent.clientX, mouseEvent.clientY);
|
|
1004
|
-
const encoded = target.closest("[data-prepr-encoded]");
|
|
1005
|
-
if (encoded) {
|
|
1006
|
-
overlay.show(encoded);
|
|
1007
|
-
} else {
|
|
1008
|
-
overlay.hide();
|
|
1009
|
-
}
|
|
1010
|
-
}, 16);
|
|
1011
|
-
document.addEventListener("mousemove", throttledMouseMove, {
|
|
1012
|
-
passive: true
|
|
1419
|
+
elements.setupMutationObserver(() => {
|
|
1420
|
+
if (pointerTarget === null) return;
|
|
1421
|
+
hitTestPending = true;
|
|
1422
|
+
scheduleHover();
|
|
1013
1423
|
});
|
|
1424
|
+
document.addEventListener("pointermove", handlePointer, { passive: true });
|
|
1425
|
+
document.addEventListener("pointerdown", handlePointer, { passive: true });
|
|
1426
|
+
document.documentElement.addEventListener(
|
|
1427
|
+
"pointerleave",
|
|
1428
|
+
handlePointerLeave
|
|
1429
|
+
);
|
|
1014
1430
|
window.addEventListener("scroll", handleScroll, {
|
|
1015
1431
|
capture: true,
|
|
1016
1432
|
passive: true
|
|
1017
1433
|
});
|
|
1434
|
+
window.addEventListener("resize", handleResize, { passive: true });
|
|
1018
1435
|
initialized = true;
|
|
1019
|
-
|
|
1436
|
+
debug5.log("stega controller started");
|
|
1020
1437
|
};
|
|
1021
1438
|
const stop = () => {
|
|
1022
1439
|
if (!initialized) return;
|
|
1440
|
+
initialized = false;
|
|
1023
1441
|
if (!tooltipEnabled) {
|
|
1024
1442
|
document.removeEventListener("click", handleClick, { capture: true });
|
|
1025
|
-
elements.
|
|
1443
|
+
elements.disconnect();
|
|
1026
1444
|
removeStegaStyles();
|
|
1027
|
-
|
|
1028
|
-
debug7.log("stega controller stopped (editor mode)");
|
|
1445
|
+
debug5.log("stega controller stopped (headless)");
|
|
1029
1446
|
return;
|
|
1030
1447
|
}
|
|
1031
|
-
if (
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1448
|
+
if (pointerFrame !== null) cancelAnimationFrame(pointerFrame);
|
|
1449
|
+
pointerFrame = null;
|
|
1450
|
+
pointerTarget = null;
|
|
1451
|
+
hitTestPending = false;
|
|
1452
|
+
document.removeEventListener("pointermove", handlePointer);
|
|
1453
|
+
document.removeEventListener("pointerdown", handlePointer);
|
|
1454
|
+
document.documentElement.removeEventListener(
|
|
1455
|
+
"pointerleave",
|
|
1456
|
+
handlePointerLeave
|
|
1457
|
+
);
|
|
1036
1458
|
window.removeEventListener("scroll", handleScroll, { capture: true });
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
tooltip.removeEventListener("mouseleave", handleTooltipMouseLeave);
|
|
1041
|
-
}
|
|
1042
|
-
overlay.cleanup();
|
|
1043
|
-
proximity.clearAllHighlights();
|
|
1044
|
-
proximity.stopObserving();
|
|
1045
|
-
elements.cleanup();
|
|
1459
|
+
window.removeEventListener("resize", handleResize);
|
|
1460
|
+
overlay.unmount();
|
|
1461
|
+
elements.disconnect();
|
|
1046
1462
|
removeStegaStyles();
|
|
1047
|
-
|
|
1048
|
-
debug7.log("stega controller stopped");
|
|
1463
|
+
debug5.log("stega controller stopped");
|
|
1049
1464
|
};
|
|
1050
1465
|
return { start, stop };
|
|
1051
1466
|
}
|
|
1052
1467
|
|
|
1053
1468
|
// src/core/stega/scroll-to-field.ts
|
|
1054
|
-
var
|
|
1469
|
+
var debug6 = createScopedLogger("stega:scroll");
|
|
1055
1470
|
var FLASH_DURATION_MS = 1200;
|
|
1056
1471
|
var flashTimeout = null;
|
|
1057
1472
|
function scrollToField(request) {
|
|
@@ -1068,7 +1483,7 @@ function scrollToField(request) {
|
|
|
1068
1483
|
break;
|
|
1069
1484
|
}
|
|
1070
1485
|
if (!target) {
|
|
1071
|
-
|
|
1486
|
+
debug6.log("no element found for field", field);
|
|
1072
1487
|
return false;
|
|
1073
1488
|
}
|
|
1074
1489
|
target.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
@@ -1498,7 +1913,7 @@ var CLOSE_ICON_SVG = `<svg width="10" height="10" viewBox="0 0 10 10" fill="none
|
|
|
1498
1913
|
</svg>`;
|
|
1499
1914
|
|
|
1500
1915
|
// src/core/ui/toolbar.generated.ts
|
|
1501
|
-
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:-webkit-sticky;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}.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}}";
|
|
1916
|
+
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:-webkit-sticky;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}}";
|
|
1502
1917
|
|
|
1503
1918
|
// ../../node_modules/.pnpm/preact@10.29.7/node_modules/preact/jsx-runtime/dist/jsxRuntime.mjs
|
|
1504
1919
|
var f2 = 0;
|
|
@@ -2193,6 +2608,724 @@ function definePreprToolbar() {
|
|
|
2193
2608
|
}
|
|
2194
2609
|
}
|
|
2195
2610
|
|
|
2611
|
+
// src/core/visual-editor/asset-images.ts
|
|
2612
|
+
var MAX_URL_LENGTH = 4096;
|
|
2613
|
+
var MIN_KEY_SEGMENTS = 2;
|
|
2614
|
+
var TRANSFORM_OPTION = /^[a-z]{1,3}_[\w.-]*$/i;
|
|
2615
|
+
var FORMAT_PLACEHOLDER = "{format}";
|
|
2616
|
+
var PROXY_PARAMS = ["url", "href", "src", "image"];
|
|
2617
|
+
var ABSOLUTE_HTTP = /^https?:\/\//i;
|
|
2618
|
+
var SOURCE_URL_ATTRIBUTES = ["src", "data-src"];
|
|
2619
|
+
var SOURCE_SET_ATTRIBUTES = ["srcset", "data-srcset"];
|
|
2620
|
+
var ASSET_ATTR = "data-prepr-asset";
|
|
2621
|
+
var ASSET_SOURCE_ATTRIBUTES = [
|
|
2622
|
+
...SOURCE_URL_ATTRIBUTES,
|
|
2623
|
+
...SOURCE_SET_ATTRIBUTES
|
|
2624
|
+
];
|
|
2625
|
+
var TAG_ATTRIBUTES = [
|
|
2626
|
+
"data-prepr-encoded",
|
|
2627
|
+
"data-prepr-id",
|
|
2628
|
+
"data-prepr-field",
|
|
2629
|
+
"data-prepr-entry-id",
|
|
2630
|
+
ASSET_ATTR
|
|
2631
|
+
];
|
|
2632
|
+
function decodeSegment(segment) {
|
|
2633
|
+
try {
|
|
2634
|
+
return decodeURIComponent(segment);
|
|
2635
|
+
} catch {
|
|
2636
|
+
return segment;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
function isTransformSegment(segment) {
|
|
2640
|
+
return segment === FORMAT_PLACEHOLDER || segment.split(",").every((option) => TRANSFORM_OPTION.test(option));
|
|
2641
|
+
}
|
|
2642
|
+
function assetKey(url, depth = 0) {
|
|
2643
|
+
if (!url || url.length > MAX_URL_LENGTH) return null;
|
|
2644
|
+
let parsed;
|
|
2645
|
+
try {
|
|
2646
|
+
parsed = new URL(url, "https://base.invalid");
|
|
2647
|
+
} catch {
|
|
2648
|
+
return null;
|
|
2649
|
+
}
|
|
2650
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
|
|
2651
|
+
if (depth === 0) {
|
|
2652
|
+
for (const name of PROXY_PARAMS) {
|
|
2653
|
+
const inner = parsed.searchParams.get(name);
|
|
2654
|
+
if (inner && ABSOLUTE_HTTP.test(inner)) return assetKey(inner, 1);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
const segments = parsed.pathname.split("/").filter(Boolean).map(decodeSegment);
|
|
2658
|
+
const file = segments.pop();
|
|
2659
|
+
if (!file) return null;
|
|
2660
|
+
const kept = segments.filter((segment) => !isTransformSegment(segment));
|
|
2661
|
+
kept.push(file);
|
|
2662
|
+
return kept.length >= MIN_KEY_SEGMENTS ? kept.join("/") : null;
|
|
2663
|
+
}
|
|
2664
|
+
function srcsetUrls(srcset) {
|
|
2665
|
+
const urls = [];
|
|
2666
|
+
let expectUrl = true;
|
|
2667
|
+
for (const token of srcset.split(/\s+/)) {
|
|
2668
|
+
if (!token) continue;
|
|
2669
|
+
if (!expectUrl) {
|
|
2670
|
+
const comma = token.indexOf(",");
|
|
2671
|
+
if (comma === -1) continue;
|
|
2672
|
+
expectUrl = true;
|
|
2673
|
+
const rest = token.slice(comma + 1);
|
|
2674
|
+
if (!rest) continue;
|
|
2675
|
+
urls.push(rest.replace(/,+$/, ""));
|
|
2676
|
+
expectUrl = rest.endsWith(",");
|
|
2677
|
+
continue;
|
|
2678
|
+
}
|
|
2679
|
+
const url = token.replace(/^,+|,+$/g, "");
|
|
2680
|
+
if (url) urls.push(url);
|
|
2681
|
+
expectUrl = token.endsWith(",");
|
|
2682
|
+
}
|
|
2683
|
+
return urls;
|
|
2684
|
+
}
|
|
2685
|
+
function createAssetIndex(assets) {
|
|
2686
|
+
const index = /* @__PURE__ */ new Map();
|
|
2687
|
+
for (const asset of assets) {
|
|
2688
|
+
const key = assetKey(asset.key);
|
|
2689
|
+
if (key && !index.has(key)) index.set(key, asset);
|
|
2690
|
+
}
|
|
2691
|
+
return index;
|
|
2692
|
+
}
|
|
2693
|
+
function urlsOf(img) {
|
|
2694
|
+
const urls = [];
|
|
2695
|
+
const collect = (el) => {
|
|
2696
|
+
for (const name of SOURCE_URL_ATTRIBUTES) {
|
|
2697
|
+
const value = el.getAttribute(name);
|
|
2698
|
+
if (value) urls.push(value);
|
|
2699
|
+
}
|
|
2700
|
+
for (const name of SOURCE_SET_ATTRIBUTES) {
|
|
2701
|
+
const value = el.getAttribute(name);
|
|
2702
|
+
if (value) urls.push(...srcsetUrls(value));
|
|
2703
|
+
}
|
|
2704
|
+
};
|
|
2705
|
+
collect(img);
|
|
2706
|
+
const picture = img.parentElement;
|
|
2707
|
+
if (picture?.tagName === "PICTURE") {
|
|
2708
|
+
for (const source of picture.children) {
|
|
2709
|
+
if (source.tagName === "SOURCE") collect(source);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
return urls;
|
|
2713
|
+
}
|
|
2714
|
+
function matchAsset(img, index, keys) {
|
|
2715
|
+
for (const url of urlsOf(img)) {
|
|
2716
|
+
let key = keys.get(url);
|
|
2717
|
+
if (key === void 0) {
|
|
2718
|
+
key = assetKey(url);
|
|
2719
|
+
keys.set(url, key);
|
|
2720
|
+
}
|
|
2721
|
+
const asset = key ? index.get(key) : void 0;
|
|
2722
|
+
if (asset) return asset;
|
|
2723
|
+
}
|
|
2724
|
+
return void 0;
|
|
2725
|
+
}
|
|
2726
|
+
function setIfChanged(el, name, value) {
|
|
2727
|
+
if (value === null) {
|
|
2728
|
+
if (el.hasAttribute(name)) el.removeAttribute(name);
|
|
2729
|
+
} else if (el.getAttribute(name) !== value) {
|
|
2730
|
+
el.setAttribute(name, value);
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
function untagAssetImage(img) {
|
|
2734
|
+
for (const name of TAG_ATTRIBUTES) img.removeAttribute(name);
|
|
2735
|
+
}
|
|
2736
|
+
function tagAssetImages(index, itemId, keys) {
|
|
2737
|
+
for (const img of document.getElementsByTagName("img")) {
|
|
2738
|
+
const ours = img.hasAttribute(ASSET_ATTR);
|
|
2739
|
+
if (!ours && img.hasAttribute("data-prepr-encoded")) continue;
|
|
2740
|
+
const asset = index.size ? matchAsset(img, index, keys) : void 0;
|
|
2741
|
+
if (!asset) {
|
|
2742
|
+
if (ours) untagAssetImage(img);
|
|
2743
|
+
continue;
|
|
2744
|
+
}
|
|
2745
|
+
setIfChanged(img, "data-prepr-encoded", "");
|
|
2746
|
+
setIfChanged(img, ASSET_ATTR, "");
|
|
2747
|
+
setIfChanged(img, "data-prepr-id", itemId);
|
|
2748
|
+
setIfChanged(img, "data-prepr-field", asset.field);
|
|
2749
|
+
setIfChanged(img, "data-prepr-entry-id", asset.entryId ?? null);
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
function untagAssetImages() {
|
|
2753
|
+
document.querySelectorAll(`img[${ASSET_ATTR}]`).forEach(untagAssetImage);
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
// src/core/visual-editor/navigation-guard.ts
|
|
2757
|
+
var debug7 = createScopedLogger("navigation-guard");
|
|
2758
|
+
var trimSlash = (path) => path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
|
|
2759
|
+
function resolve(url) {
|
|
2760
|
+
try {
|
|
2761
|
+
return new URL(url, document.baseURI);
|
|
2762
|
+
} catch {
|
|
2763
|
+
return null;
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
function leavesPage(url) {
|
|
2767
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
2768
|
+
const here = window.location;
|
|
2769
|
+
return url.origin !== here.origin || trimSlash(url.pathname) !== trimSlash(here.pathname);
|
|
2770
|
+
}
|
|
2771
|
+
function leavingLinkHref(target) {
|
|
2772
|
+
if (!(target instanceof Element)) return null;
|
|
2773
|
+
const link = target.closest("a[href]");
|
|
2774
|
+
if (!link || link.hasAttribute("download")) return null;
|
|
2775
|
+
const linkTarget = link.getAttribute("target");
|
|
2776
|
+
if (linkTarget && linkTarget !== "_self") return null;
|
|
2777
|
+
const href = link.getAttribute("href") ?? "";
|
|
2778
|
+
if (href.startsWith("#")) return null;
|
|
2779
|
+
const url = resolve(href);
|
|
2780
|
+
return url && leavesPage(url) ? url.href : null;
|
|
2781
|
+
}
|
|
2782
|
+
function submitterAttr(submitter, attr) {
|
|
2783
|
+
if (!submitter?.hasAttribute(attr)) return null;
|
|
2784
|
+
if (attr === "formaction") return submitter.formAction;
|
|
2785
|
+
if (attr === "formmethod") return submitter.formMethod;
|
|
2786
|
+
return submitter.formTarget;
|
|
2787
|
+
}
|
|
2788
|
+
function submissionUrl(form, submitter) {
|
|
2789
|
+
const method = (submitterAttr(submitter, "formmethod") ?? form.method).toLowerCase();
|
|
2790
|
+
if (method === "dialog") return null;
|
|
2791
|
+
const target = submitterAttr(submitter, "formtarget") ?? form.target;
|
|
2792
|
+
if (target && target !== "_self") return null;
|
|
2793
|
+
const url = resolve(
|
|
2794
|
+
submitterAttr(submitter, "formaction") ?? (form.action || location.href)
|
|
2795
|
+
);
|
|
2796
|
+
if (!url) return null;
|
|
2797
|
+
if (method === "get") {
|
|
2798
|
+
let data;
|
|
2799
|
+
try {
|
|
2800
|
+
data = new FormData(form, submitter);
|
|
2801
|
+
} catch {
|
|
2802
|
+
data = new FormData(form);
|
|
2803
|
+
}
|
|
2804
|
+
const query = new URLSearchParams();
|
|
2805
|
+
data.forEach((value, key) => {
|
|
2806
|
+
query.append(key, typeof value === "string" ? value : value.name);
|
|
2807
|
+
});
|
|
2808
|
+
url.search = query.toString();
|
|
2809
|
+
}
|
|
2810
|
+
return url;
|
|
2811
|
+
}
|
|
2812
|
+
function createNavigationGuard(opts) {
|
|
2813
|
+
let active = false;
|
|
2814
|
+
let permitted = null;
|
|
2815
|
+
let pendingForm = null;
|
|
2816
|
+
let originalPush = null;
|
|
2817
|
+
let guardedPush = null;
|
|
2818
|
+
const isPermitted = (url) => permitted === url.href;
|
|
2819
|
+
const guarding = () => active && opts.showsEditedContent();
|
|
2820
|
+
const report = (href) => {
|
|
2821
|
+
debug7.log("blocked navigation to", href);
|
|
2822
|
+
opts.onBlocked(href);
|
|
2823
|
+
};
|
|
2824
|
+
const onSubmit = (e2) => {
|
|
2825
|
+
if (e2.defaultPrevented || !(e2.target instanceof HTMLFormElement)) return;
|
|
2826
|
+
if (!guarding()) return;
|
|
2827
|
+
const submitter = e2.submitter ?? null;
|
|
2828
|
+
const url = submissionUrl(e2.target, submitter);
|
|
2829
|
+
if (!url || !leavesPage(url) || isPermitted(url)) return;
|
|
2830
|
+
e2.preventDefault();
|
|
2831
|
+
pendingForm = { form: e2.target, submitter, href: url.href };
|
|
2832
|
+
report(url.href);
|
|
2833
|
+
};
|
|
2834
|
+
const onNavigate = (event) => {
|
|
2835
|
+
const e2 = event;
|
|
2836
|
+
if (e2.navigationType === "traverse" || e2.navigationType === "reload") {
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
if (!e2.cancelable || e2.hashChange || e2.downloadRequest !== null) return;
|
|
2840
|
+
if (!guarding()) return;
|
|
2841
|
+
if (e2.navigationType === "replace" && e2.destination.sameDocument) return;
|
|
2842
|
+
const url = resolve(e2.destination.url);
|
|
2843
|
+
if (!url || !leavesPage(url) || isPermitted(url)) return;
|
|
2844
|
+
e2.preventDefault();
|
|
2845
|
+
report(url.href);
|
|
2846
|
+
if (e2.destination.sameDocument) window.location.reload();
|
|
2847
|
+
};
|
|
2848
|
+
const navigation = () => window.navigation;
|
|
2849
|
+
function installPushGuard() {
|
|
2850
|
+
const original = history.pushState;
|
|
2851
|
+
const guard = function(data, unused, url) {
|
|
2852
|
+
if (guarding() && guardedPush === guard && url != null) {
|
|
2853
|
+
const target = resolve(url);
|
|
2854
|
+
if (target && leavesPage(target) && !isPermitted(target)) {
|
|
2855
|
+
report(target.href);
|
|
2856
|
+
window.location.reload();
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
return original.call(this, data, unused, url);
|
|
2861
|
+
};
|
|
2862
|
+
originalPush = original;
|
|
2863
|
+
guardedPush = guard;
|
|
2864
|
+
history.pushState = guard;
|
|
2865
|
+
}
|
|
2866
|
+
function uninstallPushGuard() {
|
|
2867
|
+
if (history.pushState === guardedPush && originalPush) {
|
|
2868
|
+
history.pushState = originalPush;
|
|
2869
|
+
}
|
|
2870
|
+
originalPush = null;
|
|
2871
|
+
guardedPush = null;
|
|
2872
|
+
}
|
|
2873
|
+
return {
|
|
2874
|
+
start() {
|
|
2875
|
+
if (active) return;
|
|
2876
|
+
active = true;
|
|
2877
|
+
installPushGuard();
|
|
2878
|
+
window.addEventListener("submit", onSubmit);
|
|
2879
|
+
navigation()?.addEventListener("navigate", onNavigate);
|
|
2880
|
+
},
|
|
2881
|
+
stop() {
|
|
2882
|
+
if (!active) return;
|
|
2883
|
+
active = false;
|
|
2884
|
+
uninstallPushGuard();
|
|
2885
|
+
window.removeEventListener("submit", onSubmit);
|
|
2886
|
+
navigation()?.removeEventListener("navigate", onNavigate);
|
|
2887
|
+
pendingForm = null;
|
|
2888
|
+
},
|
|
2889
|
+
isActive: guarding,
|
|
2890
|
+
block(href) {
|
|
2891
|
+
report(href);
|
|
2892
|
+
},
|
|
2893
|
+
permit(href) {
|
|
2894
|
+
permitted = resolve(href)?.href ?? null;
|
|
2895
|
+
},
|
|
2896
|
+
allow(href) {
|
|
2897
|
+
const url = resolve(href);
|
|
2898
|
+
if (!url || url.protocol !== "http:" && url.protocol !== "https:") {
|
|
2899
|
+
debug7.warn("ignored navigation to unsupported URL");
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
permitted = url.href;
|
|
2903
|
+
const form = pendingForm;
|
|
2904
|
+
pendingForm = null;
|
|
2905
|
+
if (form && form.href === url.href && form.form.isConnected) {
|
|
2906
|
+
try {
|
|
2907
|
+
form.form.requestSubmit(form.submitter ?? void 0);
|
|
2908
|
+
} catch {
|
|
2909
|
+
form.form.requestSubmit();
|
|
2910
|
+
}
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
window.location.assign(url.href);
|
|
2914
|
+
}
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
// src/core/visual-editor/index.ts
|
|
2919
|
+
var debug8 = createScopedLogger("visual-editor");
|
|
2920
|
+
var ENCODED_SELECTOR2 = "[data-prepr-encoded]";
|
|
2921
|
+
var REFERENCE_ATTR = "data-prepr-reference";
|
|
2922
|
+
var REFERENCE_SELECTOR = `[${REFERENCE_ATTR}]`;
|
|
2923
|
+
var CURSOR_STYLE_MARKER = "data-prepr-visual-editor";
|
|
2924
|
+
var CURSOR_STYLES = `${ENCODED_SELECTOR2}{cursor:pointer}html[data-prepr-ve-suspended] ${ENCODED_SELECTOR2}{cursor:auto}`;
|
|
2925
|
+
var SUSPENDED_ATTR = "data-prepr-ve-suspended";
|
|
2926
|
+
var FIND_RETRY_MS = 150;
|
|
2927
|
+
var FIND_RETRIES = 6;
|
|
2928
|
+
var MAX_CACHED_KEYS = 2e3;
|
|
2929
|
+
var LAYOUT_MUTATIONS = {
|
|
2930
|
+
childList: true,
|
|
2931
|
+
subtree: true,
|
|
2932
|
+
characterData: true,
|
|
2933
|
+
attributes: true,
|
|
2934
|
+
attributeFilter: [
|
|
2935
|
+
"class",
|
|
2936
|
+
"style",
|
|
2937
|
+
"hidden",
|
|
2938
|
+
"open",
|
|
2939
|
+
"data-prepr-encoded",
|
|
2940
|
+
// An image swapped by a carousel or lazy loader can show another asset.
|
|
2941
|
+
...ASSET_SOURCE_ATTRIBUTES
|
|
2942
|
+
]
|
|
2943
|
+
};
|
|
2944
|
+
function scrollIntoFrame(el) {
|
|
2945
|
+
const rect = el.getBoundingClientRect();
|
|
2946
|
+
const offset = Math.max(0, (window.innerHeight - rect.height) / 2);
|
|
2947
|
+
window.scrollTo({
|
|
2948
|
+
top: Math.max(0, window.scrollY + rect.top - offset),
|
|
2949
|
+
behavior: "smooth"
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
var isVisible = (el) => typeof el.checkVisibility === "function" ? el.checkVisibility() : el.getClientRects().length > 0;
|
|
2953
|
+
function createVisualEditor(opts) {
|
|
2954
|
+
const overlay = new VisualEditorOverlay();
|
|
2955
|
+
let running = false;
|
|
2956
|
+
let currentItemId = null;
|
|
2957
|
+
let locale = opts.locale ?? "en";
|
|
2958
|
+
let suspended = false;
|
|
2959
|
+
let pointerFrame = null;
|
|
2960
|
+
let lastPointerTarget = null;
|
|
2961
|
+
let lastPointerX = 0;
|
|
2962
|
+
let lastPointerY = 0;
|
|
2963
|
+
let mutationFrame = null;
|
|
2964
|
+
let mutationObserver = null;
|
|
2965
|
+
let selection = null;
|
|
2966
|
+
let findTimeout = null;
|
|
2967
|
+
let settlePendingFind = null;
|
|
2968
|
+
let guardEnabled = false;
|
|
2969
|
+
let assetIndex = /* @__PURE__ */ new Map();
|
|
2970
|
+
const assetKeys = /* @__PURE__ */ new Map();
|
|
2971
|
+
const guard = createNavigationGuard({
|
|
2972
|
+
onBlocked: (href) => opts.onNavigationBlocked?.(href),
|
|
2973
|
+
showsEditedContent: () => showsEditedContent()
|
|
2974
|
+
});
|
|
2975
|
+
const encodedFrom = (node) => node instanceof Element ? node.closest(ENCODED_SELECTOR2) : null;
|
|
2976
|
+
const labelFor = (el) => fieldLabel(el.getAttribute("data-prepr-field"), locale);
|
|
2977
|
+
let references = null;
|
|
2978
|
+
let ownComponents = /* @__PURE__ */ new Set();
|
|
2979
|
+
const isCurrentItem = (el) => {
|
|
2980
|
+
if (currentItemId === null) return true;
|
|
2981
|
+
const id = el.getAttribute("data-prepr-id");
|
|
2982
|
+
return !id || id === currentItemId || ownComponents.has(id);
|
|
2983
|
+
};
|
|
2984
|
+
const showsEditedContent = () => {
|
|
2985
|
+
for (const el of document.querySelectorAll(ENCODED_SELECTOR2)) {
|
|
2986
|
+
if (isCurrentItem(el) && isVisible(el)) return true;
|
|
2987
|
+
}
|
|
2988
|
+
return false;
|
|
2989
|
+
};
|
|
2990
|
+
const linkedIdOf = (el) => {
|
|
2991
|
+
if (!references) return null;
|
|
2992
|
+
const id = el.getAttribute("data-prepr-id") ?? "";
|
|
2993
|
+
if (references.has(id)) return id;
|
|
2994
|
+
let container = el.closest(REFERENCE_SELECTOR);
|
|
2995
|
+
while (container) {
|
|
2996
|
+
const linked = container.getAttribute(REFERENCE_ATTR) ?? "";
|
|
2997
|
+
if (references.has(linked)) return linked;
|
|
2998
|
+
container = container.parentElement?.closest(REFERENCE_SELECTOR) ?? null;
|
|
2999
|
+
}
|
|
3000
|
+
return null;
|
|
3001
|
+
};
|
|
3002
|
+
const itemKind = (el) => {
|
|
3003
|
+
if (isCurrentItem(el)) return "current";
|
|
3004
|
+
if (!references) return "other";
|
|
3005
|
+
return linkedIdOf(el) ? "referenced" : null;
|
|
3006
|
+
};
|
|
3007
|
+
const referenceFieldOf = (el) => {
|
|
3008
|
+
const linked = linkedIdOf(el);
|
|
3009
|
+
return linked ? references?.get(linked) : void 0;
|
|
3010
|
+
};
|
|
3011
|
+
const hoverLabel = (el, kind) => {
|
|
3012
|
+
if (kind === "other") {
|
|
3013
|
+
return `${labelFor(el)} \xB7 ${t("visualEditor.otherItem", locale)}`;
|
|
3014
|
+
}
|
|
3015
|
+
if (kind === "referenced") {
|
|
3016
|
+
const via = referenceFieldOf(el);
|
|
3017
|
+
const prefix = via ? `${fieldLabel(via, locale)} \u203A ` : "";
|
|
3018
|
+
return `${prefix}${labelFor(el)} \xB7 ${t("visualEditor.linkedItem", locale)}`;
|
|
3019
|
+
}
|
|
3020
|
+
return labelFor(el);
|
|
3021
|
+
};
|
|
3022
|
+
const setSuspended = (value) => {
|
|
3023
|
+
if (suspended === value) return;
|
|
3024
|
+
suspended = value;
|
|
3025
|
+
document.documentElement.toggleAttribute(SUSPENDED_ATTR, value);
|
|
3026
|
+
if (value) overlay.setHover(null, "");
|
|
3027
|
+
};
|
|
3028
|
+
const updateHover = () => {
|
|
3029
|
+
pointerFrame = null;
|
|
3030
|
+
overlay.schedule();
|
|
3031
|
+
if (suspended) return;
|
|
3032
|
+
const encoded = encodedFrom(lastPointerTarget);
|
|
3033
|
+
const target = encoded === overlay.getSelectedTarget() ? null : encoded;
|
|
3034
|
+
if (!target) {
|
|
3035
|
+
overlay.setHover(null, "");
|
|
3036
|
+
return;
|
|
3037
|
+
}
|
|
3038
|
+
const kind = itemKind(target);
|
|
3039
|
+
if (!kind) {
|
|
3040
|
+
overlay.setHover(null, "");
|
|
3041
|
+
return;
|
|
3042
|
+
}
|
|
3043
|
+
overlay.setHover(target, hoverLabel(target, kind), { kind });
|
|
3044
|
+
};
|
|
3045
|
+
const handlePointerMove = (e2) => {
|
|
3046
|
+
setSuspended(e2.altKey);
|
|
3047
|
+
lastPointerTarget = e2.target;
|
|
3048
|
+
lastPointerX = e2.clientX;
|
|
3049
|
+
lastPointerY = e2.clientY;
|
|
3050
|
+
if (pointerFrame === null) {
|
|
3051
|
+
pointerFrame = requestAnimationFrame(updateHover);
|
|
3052
|
+
}
|
|
3053
|
+
};
|
|
3054
|
+
const handlePointerLeave = () => {
|
|
3055
|
+
lastPointerTarget = null;
|
|
3056
|
+
overlay.setHover(null, "");
|
|
3057
|
+
};
|
|
3058
|
+
const handleClick = (e2) => {
|
|
3059
|
+
if (e2.altKey) {
|
|
3060
|
+
const href = leavingLinkHref(e2.target);
|
|
3061
|
+
if (href && guard.isActive()) guard.permit(href);
|
|
3062
|
+
return;
|
|
3063
|
+
}
|
|
3064
|
+
const encoded = encodedFrom(e2.target);
|
|
3065
|
+
const kind = encoded ? itemKind(encoded) : null;
|
|
3066
|
+
if (!encoded || !kind) {
|
|
3067
|
+
clearSelection();
|
|
3068
|
+
if (e2.ctrlKey || e2.metaKey || e2.shiftKey || !showsEditedContent()) return;
|
|
3069
|
+
const href = leavingLinkHref(e2.target);
|
|
3070
|
+
if (!href) return;
|
|
3071
|
+
e2.preventDefault();
|
|
3072
|
+
if (guard.isActive()) {
|
|
3073
|
+
e2.stopPropagation();
|
|
3074
|
+
guard.block(href);
|
|
3075
|
+
}
|
|
3076
|
+
return;
|
|
3077
|
+
}
|
|
3078
|
+
e2.preventDefault();
|
|
3079
|
+
e2.stopPropagation();
|
|
3080
|
+
const item = listItemIn(encoded, e2.target);
|
|
3081
|
+
if (kind === "current")
|
|
3082
|
+
select(item?.element ?? encoded, encoded, item?.index);
|
|
3083
|
+
const referenceField = kind === "referenced" ? referenceFieldOf(encoded) : void 0;
|
|
3084
|
+
const linkedId = kind === "referenced" ? linkedIdOf(encoded) : null;
|
|
3085
|
+
opts.onEdit({
|
|
3086
|
+
...readEditPayload(encoded),
|
|
3087
|
+
...linkedId && { id: linkedId },
|
|
3088
|
+
...item && { block: item.index },
|
|
3089
|
+
...referenceField && { referenceField }
|
|
3090
|
+
});
|
|
3091
|
+
};
|
|
3092
|
+
const handleKey = (e2) => {
|
|
3093
|
+
if (e2.key === "Alt") setSuspended(e2.type === "keydown");
|
|
3094
|
+
if (e2.type === "keydown" && e2.key === "Escape") {
|
|
3095
|
+
clearSelection();
|
|
3096
|
+
}
|
|
3097
|
+
};
|
|
3098
|
+
const handleBlur = () => setSuspended(false);
|
|
3099
|
+
const handleScroll = () => overlay.onScroll();
|
|
3100
|
+
const handleResize = () => overlay.schedule();
|
|
3101
|
+
const handleSettled = () => overlay.schedule();
|
|
3102
|
+
function listItemIn(encoded, target) {
|
|
3103
|
+
if (!(target instanceof Element)) return null;
|
|
3104
|
+
const li = target.closest("li");
|
|
3105
|
+
if (!li || !encoded.contains(li) || !li.parentElement) return null;
|
|
3106
|
+
const items = Array.from(li.parentElement.children).filter(
|
|
3107
|
+
(child) => child.tagName === "LI"
|
|
3108
|
+
);
|
|
3109
|
+
return { element: li, index: items.indexOf(li) };
|
|
3110
|
+
}
|
|
3111
|
+
function nthListItem(encoded, block) {
|
|
3112
|
+
const list = encoded.querySelector("ul, ol");
|
|
3113
|
+
if (!list) return null;
|
|
3114
|
+
const items = Array.from(list.children).filter(
|
|
3115
|
+
(child) => child.tagName === "LI"
|
|
3116
|
+
);
|
|
3117
|
+
return items[block] ?? null;
|
|
3118
|
+
}
|
|
3119
|
+
function select(el, field, block) {
|
|
3120
|
+
const entryId = field.getAttribute("data-prepr-entry-id");
|
|
3121
|
+
selection = {
|
|
3122
|
+
lookup: {
|
|
3123
|
+
field: field.getAttribute("data-prepr-field") ?? "",
|
|
3124
|
+
id: field.getAttribute("data-prepr-id") ?? void 0,
|
|
3125
|
+
...entryId && { entryId },
|
|
3126
|
+
...block !== void 0 && { block }
|
|
3127
|
+
},
|
|
3128
|
+
label: field
|
|
3129
|
+
};
|
|
3130
|
+
overlay.setSelected(el, labelFor(field));
|
|
3131
|
+
if (overlay.getHoverTarget() === el) overlay.setHover(null, "");
|
|
3132
|
+
}
|
|
3133
|
+
function clearSelection() {
|
|
3134
|
+
selection = null;
|
|
3135
|
+
overlay.setSelected(null, "");
|
|
3136
|
+
}
|
|
3137
|
+
function reselect() {
|
|
3138
|
+
if (!selection) return;
|
|
3139
|
+
const current = overlay.getSelectedTarget();
|
|
3140
|
+
if (current?.isConnected && selection.label.isConnected) return;
|
|
3141
|
+
const found = findFieldElement(selection.lookup);
|
|
3142
|
+
if (!found || !isCurrentItem(found)) {
|
|
3143
|
+
overlay.setSelected(null, "");
|
|
3144
|
+
return;
|
|
3145
|
+
}
|
|
3146
|
+
const { block } = selection.lookup;
|
|
3147
|
+
const el = block !== void 0 ? nthListItem(found, block) ?? found : found;
|
|
3148
|
+
select(el, found, block);
|
|
3149
|
+
}
|
|
3150
|
+
function tagAssets() {
|
|
3151
|
+
if (assetKeys.size > MAX_CACHED_KEYS) assetKeys.clear();
|
|
3152
|
+
tagAssetImages(assetIndex, currentItemId, assetKeys);
|
|
3153
|
+
}
|
|
3154
|
+
const handleLayoutFrame = () => {
|
|
3155
|
+
mutationFrame = null;
|
|
3156
|
+
if (assetIndex.size) tagAssets();
|
|
3157
|
+
reselect();
|
|
3158
|
+
const hovered = overlay.getHoverTarget();
|
|
3159
|
+
if (hovered && !hovered.isConnected) {
|
|
3160
|
+
lastPointerTarget = document.elementFromPoint(lastPointerX, lastPointerY);
|
|
3161
|
+
updateHover();
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
overlay.schedule();
|
|
3165
|
+
};
|
|
3166
|
+
const handleMutations = () => {
|
|
3167
|
+
if (mutationFrame !== null) return;
|
|
3168
|
+
if (!selection && !overlay.getHoverTarget() && !assetIndex.size) return;
|
|
3169
|
+
mutationFrame = requestAnimationFrame(handleLayoutFrame);
|
|
3170
|
+
};
|
|
3171
|
+
function injectCursorStyles() {
|
|
3172
|
+
if (document.querySelector(`style[${CURSOR_STYLE_MARKER}]`)) return;
|
|
3173
|
+
const style = document.createElement("style");
|
|
3174
|
+
style.setAttribute(CURSOR_STYLE_MARKER, "");
|
|
3175
|
+
style.textContent = CURSOR_STYLES;
|
|
3176
|
+
document.head.appendChild(style);
|
|
3177
|
+
}
|
|
3178
|
+
function start() {
|
|
3179
|
+
if (running) return;
|
|
3180
|
+
running = true;
|
|
3181
|
+
overlay.mount();
|
|
3182
|
+
injectCursorStyles();
|
|
3183
|
+
document.addEventListener("pointermove", handlePointerMove, {
|
|
3184
|
+
passive: true
|
|
3185
|
+
});
|
|
3186
|
+
document.documentElement.addEventListener(
|
|
3187
|
+
"pointerleave",
|
|
3188
|
+
handlePointerLeave
|
|
3189
|
+
);
|
|
3190
|
+
document.addEventListener("click", handleClick, { capture: true });
|
|
3191
|
+
window.addEventListener("keydown", handleKey);
|
|
3192
|
+
window.addEventListener("keyup", handleKey);
|
|
3193
|
+
window.addEventListener("blur", handleBlur);
|
|
3194
|
+
window.addEventListener("scroll", handleScroll, {
|
|
3195
|
+
capture: true,
|
|
3196
|
+
passive: true
|
|
3197
|
+
});
|
|
3198
|
+
window.addEventListener("resize", handleResize, { passive: true });
|
|
3199
|
+
document.addEventListener("transitionend", handleSettled, {
|
|
3200
|
+
capture: true,
|
|
3201
|
+
passive: true
|
|
3202
|
+
});
|
|
3203
|
+
document.addEventListener("animationend", handleSettled, {
|
|
3204
|
+
capture: true,
|
|
3205
|
+
passive: true
|
|
3206
|
+
});
|
|
3207
|
+
mutationObserver = new MutationObserver(handleMutations);
|
|
3208
|
+
mutationObserver.observe(document.body, LAYOUT_MUTATIONS);
|
|
3209
|
+
if (assetIndex.size) tagAssets();
|
|
3210
|
+
syncGuard();
|
|
3211
|
+
debug8.log("visual editor started");
|
|
3212
|
+
}
|
|
3213
|
+
function stop() {
|
|
3214
|
+
if (!running) return;
|
|
3215
|
+
running = false;
|
|
3216
|
+
if (pointerFrame !== null) cancelAnimationFrame(pointerFrame);
|
|
3217
|
+
if (mutationFrame !== null) cancelAnimationFrame(mutationFrame);
|
|
3218
|
+
mutationObserver?.disconnect();
|
|
3219
|
+
mutationObserver = null;
|
|
3220
|
+
cancelFind();
|
|
3221
|
+
pointerFrame = null;
|
|
3222
|
+
mutationFrame = null;
|
|
3223
|
+
selection = null;
|
|
3224
|
+
lastPointerTarget = null;
|
|
3225
|
+
setSuspended(false);
|
|
3226
|
+
document.removeEventListener("pointermove", handlePointerMove);
|
|
3227
|
+
document.documentElement.removeEventListener(
|
|
3228
|
+
"pointerleave",
|
|
3229
|
+
handlePointerLeave
|
|
3230
|
+
);
|
|
3231
|
+
document.removeEventListener("click", handleClick, { capture: true });
|
|
3232
|
+
window.removeEventListener("keydown", handleKey);
|
|
3233
|
+
window.removeEventListener("keyup", handleKey);
|
|
3234
|
+
window.removeEventListener("blur", handleBlur);
|
|
3235
|
+
window.removeEventListener("scroll", handleScroll, { capture: true });
|
|
3236
|
+
window.removeEventListener("resize", handleResize);
|
|
3237
|
+
document.removeEventListener("transitionend", handleSettled, {
|
|
3238
|
+
capture: true
|
|
3239
|
+
});
|
|
3240
|
+
document.removeEventListener("animationend", handleSettled, {
|
|
3241
|
+
capture: true
|
|
3242
|
+
});
|
|
3243
|
+
document.querySelectorAll(`style[${CURSOR_STYLE_MARKER}]`).forEach((node) => node.remove());
|
|
3244
|
+
overlay.unmount();
|
|
3245
|
+
untagAssetImages();
|
|
3246
|
+
guard.stop();
|
|
3247
|
+
debug8.log("visual editor stopped");
|
|
3248
|
+
}
|
|
3249
|
+
function cancelFind() {
|
|
3250
|
+
if (findTimeout) clearTimeout(findTimeout);
|
|
3251
|
+
findTimeout = null;
|
|
3252
|
+
settlePendingFind?.();
|
|
3253
|
+
settlePendingFind = null;
|
|
3254
|
+
}
|
|
3255
|
+
function focusField(lookup) {
|
|
3256
|
+
cancelFind();
|
|
3257
|
+
return new Promise((resolve2) => {
|
|
3258
|
+
let attempt = 0;
|
|
3259
|
+
const finish = (found) => {
|
|
3260
|
+
settlePendingFind = null;
|
|
3261
|
+
resolve2(found);
|
|
3262
|
+
};
|
|
3263
|
+
settlePendingFind = () => resolve2(false);
|
|
3264
|
+
const tryFind = () => {
|
|
3265
|
+
findTimeout = null;
|
|
3266
|
+
const found = findFieldElement(lookup);
|
|
3267
|
+
if (found) {
|
|
3268
|
+
const el = lookup.block !== void 0 ? nthListItem(found, lookup.block) ?? found : found;
|
|
3269
|
+
scrollIntoFrame(el);
|
|
3270
|
+
if (running && isCurrentItem(found)) select(el, found, lookup.block);
|
|
3271
|
+
finish(true);
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
if (++attempt >= FIND_RETRIES) {
|
|
3275
|
+
debug8.log("no element renders field", lookup.field);
|
|
3276
|
+
finish(false);
|
|
3277
|
+
return;
|
|
3278
|
+
}
|
|
3279
|
+
findTimeout = setTimeout(tryFind, FIND_RETRY_MS);
|
|
3280
|
+
};
|
|
3281
|
+
tryFind();
|
|
3282
|
+
});
|
|
3283
|
+
}
|
|
3284
|
+
function setCurrentItem(itemId, referenceList, componentIds) {
|
|
3285
|
+
currentItemId = itemId;
|
|
3286
|
+
if (referenceList) setReferences(referenceList, componentIds);
|
|
3287
|
+
else if (componentIds) ownComponents = new Set(componentIds);
|
|
3288
|
+
if (running && assetIndex.size) tagAssets();
|
|
3289
|
+
const selected = overlay.getSelectedTarget();
|
|
3290
|
+
const owner = selected ? encodedFrom(selected) : null;
|
|
3291
|
+
if (owner && !isCurrentItem(owner)) clearSelection();
|
|
3292
|
+
}
|
|
3293
|
+
function setAssets(assets) {
|
|
3294
|
+
const hadAssets = assetIndex.size > 0;
|
|
3295
|
+
assetIndex = createAssetIndex(assets);
|
|
3296
|
+
if (running && (hadAssets || assetIndex.size)) tagAssets();
|
|
3297
|
+
}
|
|
3298
|
+
function setReferences(referenceList, componentIds) {
|
|
3299
|
+
if (componentIds) ownComponents = new Set(componentIds);
|
|
3300
|
+
references = /* @__PURE__ */ new Map();
|
|
3301
|
+
for (const { id, field } of referenceList) {
|
|
3302
|
+
if (!references.has(id)) references.set(id, field);
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
function setLocale(value) {
|
|
3306
|
+
locale = value;
|
|
3307
|
+
}
|
|
3308
|
+
function syncGuard() {
|
|
3309
|
+
if (running && guardEnabled) guard.start();
|
|
3310
|
+
else guard.stop();
|
|
3311
|
+
}
|
|
3312
|
+
function setNavigationGuard(enabled) {
|
|
3313
|
+
guardEnabled = enabled;
|
|
3314
|
+
syncGuard();
|
|
3315
|
+
}
|
|
3316
|
+
return {
|
|
3317
|
+
start,
|
|
3318
|
+
stop,
|
|
3319
|
+
setCurrentItem,
|
|
3320
|
+
setReferences,
|
|
3321
|
+
setAssets,
|
|
3322
|
+
setLocale,
|
|
3323
|
+
focusField,
|
|
3324
|
+
setNavigationGuard,
|
|
3325
|
+
allowNavigation: (href) => guard.allow(href)
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
|
|
2196
3329
|
// src/core/create-preview.ts
|
|
2197
3330
|
var debug9 = createScopedLogger("create-preview");
|
|
2198
3331
|
var controllerStores = /* @__PURE__ */ new WeakMap();
|
|
@@ -2265,29 +3398,45 @@ function createPreprPreview(opts = {}) {
|
|
|
2265
3398
|
el.connect(store, (key) => t(key, locale));
|
|
2266
3399
|
}
|
|
2267
3400
|
const editingEnabled = features.editMode || isIframe;
|
|
2268
|
-
const
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
if (isIframe) {
|
|
2282
|
-
sendPreprEvent("field_edit_requested", {
|
|
2283
|
-
id,
|
|
2284
|
-
field,
|
|
2285
|
-
locale: contentLocale
|
|
2286
|
-
});
|
|
2287
|
-
} else if (safeHref) {
|
|
2288
|
-
window.open(safeHref, "_blank", "noopener,noreferrer");
|
|
2289
|
-
}
|
|
3401
|
+
const onEdit = ({
|
|
3402
|
+
href,
|
|
3403
|
+
id,
|
|
3404
|
+
field,
|
|
3405
|
+
locale: contentLocale,
|
|
3406
|
+
block,
|
|
3407
|
+
entryId,
|
|
3408
|
+
referenceField
|
|
3409
|
+
}) => {
|
|
3410
|
+
const safeHref = href ? safeEditUrl(href) : null;
|
|
3411
|
+
if (href && !safeHref) {
|
|
3412
|
+
debug9.warn("ignored edit request with unsupported href scheme");
|
|
3413
|
+
return;
|
|
2290
3414
|
}
|
|
3415
|
+
if (isIframe) {
|
|
3416
|
+
sendPreprEvent("field_edit_requested", {
|
|
3417
|
+
id,
|
|
3418
|
+
field,
|
|
3419
|
+
locale: contentLocale,
|
|
3420
|
+
...block !== void 0 && { block },
|
|
3421
|
+
...entryId && { entryId },
|
|
3422
|
+
...referenceField && { referenceField }
|
|
3423
|
+
});
|
|
3424
|
+
} else if (safeHref) {
|
|
3425
|
+
window.open(safeHref, "_blank", "noopener,noreferrer");
|
|
3426
|
+
}
|
|
3427
|
+
};
|
|
3428
|
+
const visualEditor = isIframe ? createVisualEditor({
|
|
3429
|
+
onEdit,
|
|
3430
|
+
locale,
|
|
3431
|
+
onNavigationBlocked: (href) => sendPreprEvent("navigation_blocked", { href })
|
|
3432
|
+
}) : null;
|
|
3433
|
+
const stega = visualEditor ?? createStegaController({
|
|
3434
|
+
// The edit-mode marks and hover label are chrome, so they follow the
|
|
3435
|
+
// same decision as the bar: unwanted in a headless preview that asked
|
|
3436
|
+
// for no UI.
|
|
3437
|
+
tooltip: mountUi,
|
|
3438
|
+
onEdit,
|
|
3439
|
+
locale
|
|
2291
3440
|
});
|
|
2292
3441
|
const autoClean = createStegaAutoClean({
|
|
2293
3442
|
enabled: options?.autoClean
|
|
@@ -2310,10 +3459,30 @@ function createPreprPreview(opts = {}) {
|
|
|
2310
3459
|
});
|
|
2311
3460
|
const bridge = createIframeBridge(store, {
|
|
2312
3461
|
allowedEditorOrigins: options?.allowedEditorOrigins,
|
|
3462
|
+
onHandshake: ({
|
|
3463
|
+
itemId,
|
|
3464
|
+
referencedIds,
|
|
3465
|
+
componentIds,
|
|
3466
|
+
uiLocale,
|
|
3467
|
+
navigationGuard,
|
|
3468
|
+
assets
|
|
3469
|
+
}) => {
|
|
3470
|
+
if (uiLocale) visualEditor?.setLocale(uiLocale);
|
|
3471
|
+
visualEditor?.setCurrentItem(itemId ?? null, referencedIds, componentIds);
|
|
3472
|
+
visualEditor?.setNavigationGuard(navigationGuard === true);
|
|
3473
|
+
if (assets) visualEditor?.setAssets(assets);
|
|
3474
|
+
},
|
|
3475
|
+
onAssets: (assets) => visualEditor?.setAssets(assets),
|
|
3476
|
+
onAllowNavigation: (href) => visualEditor?.allowNavigation(href),
|
|
3477
|
+
onReferences: (referencedIds, componentIds) => visualEditor?.setReferences(referencedIds, componentIds),
|
|
2313
3478
|
// The editor selecting a field scrolls the matching element into view. The
|
|
2314
3479
|
// lookup relies on the attributes the stega scan writes, so it finds
|
|
2315
3480
|
// nothing until encoded content has been tagged.
|
|
2316
|
-
onScrollToField: ({ field, id }) => {
|
|
3481
|
+
onScrollToField: ({ field, id, block, entryId, fieldById }) => {
|
|
3482
|
+
if (visualEditor) {
|
|
3483
|
+
void visualEditor.focusField({ field, id, block, entryId, fieldById });
|
|
3484
|
+
return;
|
|
3485
|
+
}
|
|
2317
3486
|
if (!scrollToField({ field, id })) {
|
|
2318
3487
|
debug9.log("no element on this page renders field", field);
|
|
2319
3488
|
}
|
|
@@ -2348,7 +3517,7 @@ function createPreprPreview(opts = {}) {
|
|
|
2348
3517
|
var import_react = require("react");
|
|
2349
3518
|
|
|
2350
3519
|
// src/version.ts
|
|
2351
|
-
var VERSION = "0.5.0-rc.
|
|
3520
|
+
var VERSION = "0.5.0-rc.3";
|
|
2352
3521
|
|
|
2353
3522
|
// src/core/pixel.ts
|
|
2354
3523
|
var PIXEL_SCRIPT_URL = "https://cdn.tracking.prepr.io/js/prepr.min.js";
|