@einblick/sdk 0.5.2 → 0.5.5
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/LICENSE +21 -0
- package/README.md +2 -10
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +18 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +18 -31
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +56 -68
- package/dist/client.test.js.map +1 -1
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +51 -12
- package/dist/codegen.js.map +1 -1
- package/dist/codegen.test.js +51 -7
- package/dist/codegen.test.js.map +1 -1
- package/dist/next/edit-boot.d.ts.map +1 -1
- package/dist/next/edit-boot.js +2 -6
- package/dist/next/edit-boot.js.map +1 -1
- package/dist/next/index.d.ts +2 -2
- package/dist/next/index.d.ts.map +1 -1
- package/dist/next/index.js +1 -1
- package/dist/next/index.js.map +1 -1
- package/dist/next/revalidate.d.ts.map +1 -1
- package/dist/next/revalidate.js +2 -7
- package/dist/next/revalidate.js.map +1 -1
- package/dist/react/i18n.d.ts +5 -0
- package/dist/react/i18n.d.ts.map +1 -1
- package/dist/react/i18n.js +14 -14
- package/dist/react/i18n.js.map +1 -1
- package/dist/react/image.d.ts.map +1 -1
- package/dist/react/image.js +1 -1
- package/dist/react/image.js.map +1 -1
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -2
- package/dist/react/index.js.map +1 -1
- package/dist/react/markers.d.ts.map +1 -1
- package/dist/react/markers.js +6 -12
- package/dist/react/markers.js.map +1 -1
- package/dist/react/provider.d.ts.map +1 -1
- package/dist/react/provider.js +5 -3
- package/dist/react/provider.js.map +1 -1
- package/dist/react/runtime.d.ts.map +1 -1
- package/dist/react/runtime.js +727 -379
- package/dist/react/runtime.js.map +1 -1
- package/dist/react/shared.d.ts +2 -0
- package/dist/react/shared.d.ts.map +1 -1
- package/dist/react/shared.js +18 -17
- package/dist/react/shared.js.map +1 -1
- package/package.json +11 -4
package/dist/react/runtime.js
CHANGED
|
@@ -1,98 +1,10 @@
|
|
|
1
|
-
import { EINBLICK_AUTH_SOURCE, EINBLICK_BRIDGE_SOURCE, EINBLICK_EDITOR_SOURCE, EINBLICK_PARENT_SOURCE, formatValueForText, isEditSessionExpired, readBindingFromElement, resolveAppOrigin, serializeBindingValue, shouldUseInlineEditor, } from './shared.js';
|
|
1
|
+
import { EINBLICK_AUTH_SOURCE, EINBLICK_BRIDGE_SOURCE, EINBLICK_EDITOR_SOURCE, EINBLICK_PARENT_SOURCE, formatValueForText, isMediaEditableBinding, isEditSessionExpired, readBindingFromElement, resolveAppOrigin, serializeBindingValue, shouldUseInlineEditor, } from './shared.js';
|
|
2
2
|
import { getEinblickSdkMessages, resolveEinblickSdkLocale, } from './i18n.js';
|
|
3
3
|
const RUNTIME_STYLE_ID = 'einblick-edit-runtime-styles';
|
|
4
|
-
const RELATIVE_POSITION_SENTINEL = '__einblick_runtime_original_static__';
|
|
5
4
|
const EINBLICK_ACCENT_RGB = '250, 105, 62';
|
|
6
5
|
const EINBLICK_ACCENT_COLOR = `rgb(${EINBLICK_ACCENT_RGB})`;
|
|
7
6
|
const EINBLICK_ACCENT_BORDER = `rgba(${EINBLICK_ACCENT_RGB}, 0.92)`;
|
|
8
7
|
const CONTROL_BAR_HEIGHT = 44;
|
|
9
|
-
const INLINE_EDITOR_SHADOW_STYLES = `
|
|
10
|
-
:host {
|
|
11
|
-
display: block;
|
|
12
|
-
width: 100%;
|
|
13
|
-
color: rgb(15, 23, 42);
|
|
14
|
-
font-family: system-ui, sans-serif;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
*, *::before, *::after {
|
|
18
|
-
box-sizing: border-box;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
[data-einblick-inline-shell] {
|
|
22
|
-
display: flex;
|
|
23
|
-
flex-direction: column;
|
|
24
|
-
gap: 0.75rem;
|
|
25
|
-
width: 100%;
|
|
26
|
-
color: rgb(15, 23, 42);
|
|
27
|
-
font-family: system-ui, sans-serif;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
[data-einblick-inline-control],
|
|
31
|
-
[data-einblick-inline-textarea],
|
|
32
|
-
[data-einblick-inline-select] {
|
|
33
|
-
appearance: none;
|
|
34
|
-
width: 100%;
|
|
35
|
-
min-width: 0;
|
|
36
|
-
margin: 0;
|
|
37
|
-
border-radius: 0.9rem;
|
|
38
|
-
border: 1px solid rgba(148, 163, 184, 0.6);
|
|
39
|
-
background: rgb(255, 255, 255);
|
|
40
|
-
padding: 0.75rem 0.9rem;
|
|
41
|
-
font: 400 0.95rem/1.5 system-ui, sans-serif;
|
|
42
|
-
color: rgb(15, 23, 42);
|
|
43
|
-
box-shadow: none;
|
|
44
|
-
letter-spacing: normal;
|
|
45
|
-
text-transform: none;
|
|
46
|
-
text-indent: 0;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
[data-einblick-inline-control]:focus,
|
|
50
|
-
[data-einblick-inline-textarea]:focus,
|
|
51
|
-
[data-einblick-inline-select]:focus {
|
|
52
|
-
outline: 2px solid rgba(${EINBLICK_ACCENT_RGB}, 0.3);
|
|
53
|
-
outline-offset: 2px;
|
|
54
|
-
border-color: ${EINBLICK_ACCENT_COLOR};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
[data-einblick-inline-textarea] {
|
|
58
|
-
min-height: 8rem;
|
|
59
|
-
resize: vertical;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
[data-einblick-inline-actions] {
|
|
63
|
-
display: flex;
|
|
64
|
-
flex-wrap: wrap;
|
|
65
|
-
justify-content: flex-end;
|
|
66
|
-
gap: 0.5rem;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
[data-einblick-edit-action] {
|
|
70
|
-
appearance: none;
|
|
71
|
-
border: 1px solid rgba(148, 163, 184, 0.6);
|
|
72
|
-
background: rgb(255, 255, 255);
|
|
73
|
-
color: rgb(15, 23, 42);
|
|
74
|
-
border-radius: 999px;
|
|
75
|
-
padding: 0.65rem 1rem;
|
|
76
|
-
font: 600 0.9rem/1 system-ui, sans-serif;
|
|
77
|
-
cursor: pointer;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
[data-einblick-edit-action]:disabled {
|
|
81
|
-
cursor: wait;
|
|
82
|
-
opacity: 0.72;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
[data-einblick-edit-action="primary"] {
|
|
86
|
-
border-color: ${EINBLICK_ACCENT_COLOR};
|
|
87
|
-
background: ${EINBLICK_ACCENT_COLOR};
|
|
88
|
-
color: rgb(255, 255, 255);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
[data-einblick-inline-error] {
|
|
92
|
-
font: 400 0.85rem/1.5 system-ui, sans-serif;
|
|
93
|
-
color: rgb(185, 28, 28);
|
|
94
|
-
}
|
|
95
|
-
`;
|
|
96
8
|
function getBrandBadgeSvg(size) {
|
|
97
9
|
return `
|
|
98
10
|
<svg viewBox="0 0 40 40" width="${size}" height="${size}" aria-hidden="true" focusable="false">
|
|
@@ -135,6 +47,32 @@ function createRandomId() {
|
|
|
135
47
|
}
|
|
136
48
|
return Math.random().toString(36).slice(2);
|
|
137
49
|
}
|
|
50
|
+
function stopEvent(event) {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
event.stopPropagation();
|
|
53
|
+
}
|
|
54
|
+
function clamp(value, min, max) {
|
|
55
|
+
return Math.min(Math.max(value, min), max);
|
|
56
|
+
}
|
|
57
|
+
function isTextInputEvent(event) {
|
|
58
|
+
return (event.key.length === 1 ||
|
|
59
|
+
event.key === 'Enter' ||
|
|
60
|
+
event.key === 'Backspace' ||
|
|
61
|
+
event.key === 'Delete');
|
|
62
|
+
}
|
|
63
|
+
function fieldTypeAllowsMultiline(binding) {
|
|
64
|
+
return binding.fieldType === 'text' || binding.fieldType === 'markdown';
|
|
65
|
+
}
|
|
66
|
+
function getMediaAcceptValue(binding) {
|
|
67
|
+
return binding.fieldType === 'image' || binding.fieldType === 'images'
|
|
68
|
+
? 'image/*'
|
|
69
|
+
: '';
|
|
70
|
+
}
|
|
71
|
+
function getMediaActionLabel(binding, messages) {
|
|
72
|
+
return binding.fieldType === 'image' || binding.fieldType === 'images'
|
|
73
|
+
? messages.actions.replaceImage
|
|
74
|
+
: messages.actions.replaceFile;
|
|
75
|
+
}
|
|
138
76
|
function injectRuntimeStyles() {
|
|
139
77
|
if (typeof document === 'undefined') {
|
|
140
78
|
return;
|
|
@@ -149,15 +87,27 @@ function injectRuntimeStyles() {
|
|
|
149
87
|
scroll-margin-top: 96px;
|
|
150
88
|
}
|
|
151
89
|
|
|
152
|
-
[data-einblick-
|
|
153
|
-
|
|
154
|
-
|
|
90
|
+
[data-einblick-edit-outline],
|
|
91
|
+
[data-einblick-edit-field-outline] {
|
|
92
|
+
position: fixed;
|
|
93
|
+
z-index: 2147483643;
|
|
94
|
+
box-sizing: border-box;
|
|
95
|
+
pointer-events: none;
|
|
96
|
+
border-radius: var(--einblick-overlay-radius, 0.35rem);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
[data-einblick-edit-outline] {
|
|
100
|
+
border: 1px solid ${EINBLICK_ACCENT_BORDER};
|
|
101
|
+
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.7);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
[data-einblick-edit-field-outline] {
|
|
105
|
+
border: 1px dashed rgba(${EINBLICK_ACCENT_RGB}, 0.78);
|
|
106
|
+
background: rgba(${EINBLICK_ACCENT_RGB}, 0.04);
|
|
155
107
|
}
|
|
156
108
|
|
|
157
109
|
[data-einblick-edit-controls] {
|
|
158
|
-
position:
|
|
159
|
-
top: 0.5rem;
|
|
160
|
-
right: 0.5rem;
|
|
110
|
+
position: fixed;
|
|
161
111
|
z-index: 2147483644;
|
|
162
112
|
display: inline-flex;
|
|
163
113
|
align-items: center;
|
|
@@ -165,7 +115,8 @@ function injectRuntimeStyles() {
|
|
|
165
115
|
}
|
|
166
116
|
|
|
167
117
|
[data-einblick-edit-button],
|
|
168
|
-
[data-einblick-create-button]
|
|
118
|
+
[data-einblick-create-button],
|
|
119
|
+
[data-einblick-field-button] {
|
|
169
120
|
border: 1px solid ${EINBLICK_ACCENT_COLOR};
|
|
170
121
|
border-radius: 999px;
|
|
171
122
|
padding: 0.3rem 0.7rem;
|
|
@@ -175,22 +126,46 @@ function injectRuntimeStyles() {
|
|
|
175
126
|
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.16);
|
|
176
127
|
}
|
|
177
128
|
|
|
178
|
-
[data-einblick-edit-button]
|
|
129
|
+
[data-einblick-edit-button],
|
|
130
|
+
[data-einblick-field-button="media"] {
|
|
179
131
|
background: ${EINBLICK_ACCENT_COLOR};
|
|
180
132
|
color: rgb(255, 255, 255);
|
|
181
133
|
}
|
|
182
134
|
|
|
183
|
-
[data-einblick-
|
|
135
|
+
[data-einblick-edit-button] {
|
|
136
|
+
padding: 0.42rem 0.85rem;
|
|
137
|
+
font-size: 13px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
[data-einblick-create-button],
|
|
141
|
+
[data-einblick-field-button="text"] {
|
|
184
142
|
background: rgb(255, 255, 255);
|
|
185
143
|
color: ${EINBLICK_ACCENT_COLOR};
|
|
186
144
|
}
|
|
187
145
|
|
|
188
146
|
[data-einblick-edit-button]:hover,
|
|
189
|
-
[data-einblick-create-button]:hover
|
|
147
|
+
[data-einblick-create-button]:hover,
|
|
148
|
+
[data-einblick-field-button]:hover {
|
|
190
149
|
background: rgb(255, 255, 255);
|
|
191
150
|
color: ${EINBLICK_ACCENT_COLOR};
|
|
192
151
|
}
|
|
193
152
|
|
|
153
|
+
[data-einblick-field-overlays] {
|
|
154
|
+
position: fixed;
|
|
155
|
+
inset: 0;
|
|
156
|
+
z-index: 2147483644;
|
|
157
|
+
pointer-events: none;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
[data-einblick-field-button] {
|
|
161
|
+
position: fixed;
|
|
162
|
+
z-index: 2147483644;
|
|
163
|
+
pointer-events: auto;
|
|
164
|
+
padding: 0.24rem 0.58rem;
|
|
165
|
+
font-size: 11px;
|
|
166
|
+
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.12);
|
|
167
|
+
}
|
|
168
|
+
|
|
194
169
|
[data-einblick-brand-badge] {
|
|
195
170
|
position: fixed;
|
|
196
171
|
right: 1rem;
|
|
@@ -340,64 +315,11 @@ function injectRuntimeStyles() {
|
|
|
340
315
|
}
|
|
341
316
|
|
|
342
317
|
[data-einblick-inline-editing="true"] {
|
|
343
|
-
|
|
344
|
-
outline-offset: 4px;
|
|
345
|
-
border-radius: 1rem;
|
|
346
|
-
background: rgba(${EINBLICK_ACCENT_RGB}, 0.08);
|
|
347
|
-
padding: 0.8rem;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
[data-einblick-inline-shell] {
|
|
351
|
-
display: flex;
|
|
352
|
-
flex-direction: column;
|
|
353
|
-
gap: 0.75rem;
|
|
354
|
-
width: 100%;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
[data-einblick-inline-control],
|
|
358
|
-
[data-einblick-inline-textarea],
|
|
359
|
-
[data-einblick-inline-select] {
|
|
360
|
-
width: 100%;
|
|
361
|
-
min-width: 0;
|
|
362
|
-
border-radius: 0.9rem;
|
|
363
|
-
border: 1px solid rgba(148, 163, 184, 0.6);
|
|
364
|
-
background: rgb(255, 255, 255);
|
|
365
|
-
padding: 0.75rem 0.9rem;
|
|
366
|
-
font: 400 0.95rem/1.5 system-ui, sans-serif;
|
|
367
|
-
color: inherit;
|
|
368
|
-
box-sizing: border-box;
|
|
318
|
+
cursor: text;
|
|
369
319
|
}
|
|
370
320
|
|
|
371
|
-
[data-einblick-inline-
|
|
372
|
-
|
|
373
|
-
resize: vertical;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
[data-einblick-inline-actions] {
|
|
377
|
-
display: flex;
|
|
378
|
-
flex-wrap: wrap;
|
|
379
|
-
justify-content: flex-end;
|
|
380
|
-
gap: 0.5rem;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
[data-einblick-edit-action] {
|
|
384
|
-
border-radius: 999px;
|
|
385
|
-
border: 1px solid rgba(148, 163, 184, 0.6);
|
|
386
|
-
background: rgb(255, 255, 255);
|
|
387
|
-
padding: 0.65rem 1rem;
|
|
388
|
-
font: 600 0.9rem/1 system-ui, sans-serif;
|
|
389
|
-
cursor: pointer;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
[data-einblick-edit-action="primary"] {
|
|
393
|
-
border-color: ${EINBLICK_ACCENT_COLOR};
|
|
394
|
-
background: ${EINBLICK_ACCENT_COLOR};
|
|
395
|
-
color: rgb(255, 255, 255);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
[data-einblick-inline-error] {
|
|
399
|
-
font: 400 0.85rem/1.5 system-ui, sans-serif;
|
|
400
|
-
color: rgb(185, 28, 28);
|
|
321
|
+
[data-einblick-inline-editing="true"]:focus {
|
|
322
|
+
outline: none;
|
|
401
323
|
}
|
|
402
324
|
|
|
403
325
|
[data-einblick-editor-backdrop] {
|
|
@@ -485,9 +407,12 @@ class EinblickEditRuntime {
|
|
|
485
407
|
bridgeFrame = null;
|
|
486
408
|
bridgePingInterval = null;
|
|
487
409
|
activeElement = null;
|
|
410
|
+
activeOutline = null;
|
|
488
411
|
hoverButtonContainer = null;
|
|
489
412
|
hoverButton = null;
|
|
490
413
|
hoverCreateButton = null;
|
|
414
|
+
quickFieldOverlayContainer = null;
|
|
415
|
+
quickFieldOverlays = [];
|
|
491
416
|
brandBadge = null;
|
|
492
417
|
brandPopover = null;
|
|
493
418
|
brandPopoverSiteName = null;
|
|
@@ -510,6 +435,7 @@ class EinblickEditRuntime {
|
|
|
510
435
|
currentInlineTarget = null;
|
|
511
436
|
currentInlineBinding = null;
|
|
512
437
|
currentInlineRestoreState = null;
|
|
438
|
+
inlineSaveInFlight = null;
|
|
513
439
|
editorBackdrop = null;
|
|
514
440
|
editorNonce = null;
|
|
515
441
|
editorTarget = null;
|
|
@@ -519,7 +445,9 @@ class EinblickEditRuntime {
|
|
|
519
445
|
navigatorNonce = null;
|
|
520
446
|
navigatorOpen = false;
|
|
521
447
|
pendingInlineSaves = new Map();
|
|
448
|
+
pendingInlineMediaSaves = new Map();
|
|
522
449
|
pendingLogin = null;
|
|
450
|
+
pendingLogout = null;
|
|
523
451
|
constructor(options) {
|
|
524
452
|
this.siteKey = options.siteKey;
|
|
525
453
|
this.appOrigin = resolveAppOrigin(options.appOrigin, options.appUrl);
|
|
@@ -603,15 +531,23 @@ class EinblickEditRuntime {
|
|
|
603
531
|
this.mutationObserver?.disconnect();
|
|
604
532
|
this.mutationObserver = null;
|
|
605
533
|
this.clearBridgePing();
|
|
534
|
+
if (this.pendingLogout) {
|
|
535
|
+
window.clearTimeout(this.pendingLogout.timeoutId);
|
|
536
|
+
this.pendingLogout.resolve(false);
|
|
537
|
+
this.pendingLogout = null;
|
|
538
|
+
}
|
|
606
539
|
this.bridgeFrame?.remove();
|
|
607
540
|
this.bridgeFrame = null;
|
|
608
541
|
this.setActiveElement(null);
|
|
542
|
+
this.activeOutline?.remove();
|
|
543
|
+
this.activeOutline = null;
|
|
609
544
|
this.hoverButtonContainer?.remove();
|
|
610
545
|
this.hoverButtonContainer = null;
|
|
611
546
|
this.hoverButton?.remove();
|
|
612
547
|
this.hoverButton = null;
|
|
613
548
|
this.hoverCreateButton?.remove();
|
|
614
549
|
this.hoverCreateButton = null;
|
|
550
|
+
this.clearQuickFieldOverlays();
|
|
615
551
|
this.brandBadge?.remove();
|
|
616
552
|
this.brandBadge = null;
|
|
617
553
|
this.brandPopover?.remove();
|
|
@@ -645,6 +581,11 @@ class EinblickEditRuntime {
|
|
|
645
581
|
pending.reject(new Error(this.messages.errors.editingInterrupted));
|
|
646
582
|
}
|
|
647
583
|
this.pendingInlineSaves.clear();
|
|
584
|
+
for (const pending of this.pendingInlineMediaSaves.values()) {
|
|
585
|
+
window.clearTimeout(pending.timeoutId);
|
|
586
|
+
pending.reject(new Error(this.messages.errors.editingInterrupted));
|
|
587
|
+
}
|
|
588
|
+
this.pendingInlineMediaSaves.clear();
|
|
648
589
|
this.setState({
|
|
649
590
|
enabled: true,
|
|
650
591
|
active: false,
|
|
@@ -656,7 +597,7 @@ class EinblickEditRuntime {
|
|
|
656
597
|
}
|
|
657
598
|
refreshLocalizedUi() {
|
|
658
599
|
if (this.hoverButton) {
|
|
659
|
-
this.
|
|
600
|
+
this.updateHoverButtonLabel();
|
|
660
601
|
}
|
|
661
602
|
if (this.hoverCreateButton) {
|
|
662
603
|
this.hoverCreateButton.textContent = this.messages.actions.addRecord;
|
|
@@ -673,11 +614,11 @@ class EinblickEditRuntime {
|
|
|
673
614
|
this.messages.actions.goToEinblick;
|
|
674
615
|
}
|
|
675
616
|
if (this.brandPopoverLogoutButton) {
|
|
676
|
-
this.brandPopoverLogoutButton.textContent =
|
|
677
|
-
this.messages.actions.logout;
|
|
617
|
+
this.brandPopoverLogoutButton.textContent = this.messages.actions.logout;
|
|
678
618
|
}
|
|
679
619
|
this.updateBrandPopover();
|
|
680
620
|
this.updateBottomBar();
|
|
621
|
+
this.updateHoverChrome();
|
|
681
622
|
}
|
|
682
623
|
async login(options) {
|
|
683
624
|
if (typeof window === 'undefined') {
|
|
@@ -716,12 +657,7 @@ class EinblickEditRuntime {
|
|
|
716
657
|
}
|
|
717
658
|
}
|
|
718
659
|
}, 400);
|
|
719
|
-
this.pendingLogin = {
|
|
720
|
-
nonce: loginNonce,
|
|
721
|
-
popup,
|
|
722
|
-
resolve,
|
|
723
|
-
closePollId,
|
|
724
|
-
};
|
|
660
|
+
this.pendingLogin = { nonce: loginNonce, popup, resolve, closePollId };
|
|
725
661
|
});
|
|
726
662
|
}
|
|
727
663
|
openEditor(binding, element) {
|
|
@@ -744,13 +680,24 @@ class EinblickEditRuntime {
|
|
|
744
680
|
this.setActiveElement(null);
|
|
745
681
|
return;
|
|
746
682
|
}
|
|
683
|
+
if (this.isSdkChromeTarget(event.target)) {
|
|
684
|
+
this.updateHoverChrome();
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
747
687
|
const target = this.findEditableTarget(event.target);
|
|
748
688
|
this.setActiveElement(target);
|
|
749
689
|
};
|
|
750
690
|
handleViewportChange = () => {
|
|
691
|
+
if (this.currentInlineTarget?.isConnected) {
|
|
692
|
+
this.ensureActiveOutline();
|
|
693
|
+
this.positionOutline(this.activeOutline, this.currentInlineTarget);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
751
696
|
if (this.activeElement && !this.activeElement.isConnected) {
|
|
752
697
|
this.setActiveElement(null);
|
|
698
|
+
return;
|
|
753
699
|
}
|
|
700
|
+
this.updateHoverChrome();
|
|
754
701
|
};
|
|
755
702
|
handleWindowBlur = () => {
|
|
756
703
|
this.setBrandPopoverOpen(false);
|
|
@@ -791,8 +738,7 @@ class EinblickEditRuntime {
|
|
|
791
738
|
this.setActiveElement(null);
|
|
792
739
|
};
|
|
793
740
|
handleHoverButtonClick = (event) => {
|
|
794
|
-
event
|
|
795
|
-
event.stopPropagation();
|
|
741
|
+
stopEvent(event);
|
|
796
742
|
if (!this.activeElement) {
|
|
797
743
|
return;
|
|
798
744
|
}
|
|
@@ -800,11 +746,14 @@ class EinblickEditRuntime {
|
|
|
800
746
|
if (!binding) {
|
|
801
747
|
return;
|
|
802
748
|
}
|
|
749
|
+
if (binding.fieldKey && isMediaEditableBinding(binding)) {
|
|
750
|
+
this.openMediaPicker(this.activeElement, binding, this.hoverButton);
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
803
753
|
this.openEditor(binding, this.activeElement);
|
|
804
754
|
};
|
|
805
755
|
handleCreateButtonClick = (event) => {
|
|
806
|
-
event
|
|
807
|
-
event.stopPropagation();
|
|
756
|
+
stopEvent(event);
|
|
808
757
|
if (!this.activeElement) {
|
|
809
758
|
return;
|
|
810
759
|
}
|
|
@@ -821,6 +770,16 @@ class EinblickEditRuntime {
|
|
|
821
770
|
this.setBottomBarMenuOpen(false);
|
|
822
771
|
return;
|
|
823
772
|
}
|
|
773
|
+
if (this.currentInlineTarget) {
|
|
774
|
+
if (this.currentInlineTarget.contains(target) ||
|
|
775
|
+
this.isSdkChromeTarget(target)) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
event.preventDefault();
|
|
779
|
+
event.stopImmediatePropagation();
|
|
780
|
+
void this.commitInlineEditor();
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
824
783
|
if (this.isBrandPopoverOpen() &&
|
|
825
784
|
!this.brandBadge?.contains(target) &&
|
|
826
785
|
!this.brandPopover?.contains(target)) {
|
|
@@ -895,6 +854,35 @@ class EinblickEditRuntime {
|
|
|
895
854
|
}
|
|
896
855
|
return;
|
|
897
856
|
}
|
|
857
|
+
if (message.type === 'logout-result') {
|
|
858
|
+
const pending = this.pendingLogout;
|
|
859
|
+
if (!pending || pending.requestId !== message.requestId) {
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
window.clearTimeout(pending.timeoutId);
|
|
863
|
+
this.pendingLogout = null;
|
|
864
|
+
if (!message.ok && message.message) {
|
|
865
|
+
this.callbacks.onError?.(message.message);
|
|
866
|
+
}
|
|
867
|
+
pending.resolve(message.ok);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
if (message.type === 'inline-media-result') {
|
|
871
|
+
const pending = this.pendingInlineMediaSaves.get(message.requestId);
|
|
872
|
+
if (!pending) {
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
this.pendingInlineMediaSaves.delete(message.requestId);
|
|
876
|
+
window.clearTimeout(pending.timeoutId);
|
|
877
|
+
if (message.ok) {
|
|
878
|
+
pending.resolve({ fileId: message.fileId, value: message.value });
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const error = new Error(message.error || this.messages.errors.saveFieldFailed);
|
|
882
|
+
pending.reject(error);
|
|
883
|
+
this.callbacks.onError?.(error.message);
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
898
886
|
const pending = this.pendingInlineSaves.get(message.requestId);
|
|
899
887
|
if (!pending) {
|
|
900
888
|
return;
|
|
@@ -921,11 +909,7 @@ class EinblickEditRuntime {
|
|
|
921
909
|
if (message.ok) {
|
|
922
910
|
if (!message.sessionToken) {
|
|
923
911
|
const errorMessage = this.messages.errors.signInDidNotReturnSession;
|
|
924
|
-
this.setState({
|
|
925
|
-
...this.state,
|
|
926
|
-
errorMessage,
|
|
927
|
-
status: 'error',
|
|
928
|
-
});
|
|
912
|
+
this.setState({ ...this.state, errorMessage, status: 'error' });
|
|
929
913
|
this.callbacks.onError?.(errorMessage);
|
|
930
914
|
pending.resolve(false);
|
|
931
915
|
return;
|
|
@@ -943,11 +927,7 @@ class EinblickEditRuntime {
|
|
|
943
927
|
return;
|
|
944
928
|
}
|
|
945
929
|
const errorMessage = message.message || this.messages.errors.signInFailed;
|
|
946
|
-
this.setState({
|
|
947
|
-
...this.state,
|
|
948
|
-
errorMessage,
|
|
949
|
-
status: 'error',
|
|
950
|
-
});
|
|
930
|
+
this.setState({ ...this.state, errorMessage, status: 'error' });
|
|
951
931
|
this.callbacks.onError?.(errorMessage);
|
|
952
932
|
pending.resolve(false);
|
|
953
933
|
}
|
|
@@ -1074,16 +1054,23 @@ class EinblickEditRuntime {
|
|
|
1074
1054
|
container.hidden = true;
|
|
1075
1055
|
const editButton = document.createElement('button');
|
|
1076
1056
|
editButton.type = 'button';
|
|
1077
|
-
editButton.textContent = this.messages.actions.
|
|
1057
|
+
editButton.textContent = this.messages.actions.editElement;
|
|
1078
1058
|
editButton.setAttribute('data-einblick-edit-button', 'true');
|
|
1059
|
+
editButton.addEventListener('pointerdown', (event) => {
|
|
1060
|
+
event.stopPropagation();
|
|
1061
|
+
});
|
|
1079
1062
|
editButton.addEventListener('click', this.handleHoverButtonClick);
|
|
1080
1063
|
const createButton = document.createElement('button');
|
|
1081
1064
|
createButton.type = 'button';
|
|
1082
1065
|
createButton.textContent = this.messages.actions.addRecord;
|
|
1083
1066
|
createButton.hidden = true;
|
|
1084
1067
|
createButton.setAttribute('data-einblick-create-button', 'true');
|
|
1068
|
+
createButton.addEventListener('pointerdown', (event) => {
|
|
1069
|
+
event.stopPropagation();
|
|
1070
|
+
});
|
|
1085
1071
|
createButton.addEventListener('click', this.handleCreateButtonClick);
|
|
1086
1072
|
container.append(editButton, createButton);
|
|
1073
|
+
document.body.append(container);
|
|
1087
1074
|
this.hoverButtonContainer = container;
|
|
1088
1075
|
this.hoverButton = editButton;
|
|
1089
1076
|
this.hoverCreateButton = createButton;
|
|
@@ -1635,6 +1622,48 @@ class EinblickEditRuntime {
|
|
|
1635
1622
|
siteName: this.state.siteName,
|
|
1636
1623
|
errorMessage: undefined,
|
|
1637
1624
|
});
|
|
1625
|
+
const loggedOutViaBridge = await this.requestBridgeLogout();
|
|
1626
|
+
if (!loggedOutViaBridge) {
|
|
1627
|
+
await this.logoutViaPopup(sessionToken);
|
|
1628
|
+
}
|
|
1629
|
+
this.reloadBridge();
|
|
1630
|
+
}
|
|
1631
|
+
requestBridgeLogout() {
|
|
1632
|
+
if (typeof window === 'undefined' || this.pendingLogout) {
|
|
1633
|
+
return Promise.resolve(false);
|
|
1634
|
+
}
|
|
1635
|
+
const target = this.bridgeFrame?.contentWindow;
|
|
1636
|
+
if (!target) {
|
|
1637
|
+
return Promise.resolve(false);
|
|
1638
|
+
}
|
|
1639
|
+
const requestId = createRandomId();
|
|
1640
|
+
return new Promise((resolve) => {
|
|
1641
|
+
const timeoutId = window.setTimeout(() => {
|
|
1642
|
+
if (this.pendingLogout?.requestId !== requestId) {
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
this.pendingLogout = null;
|
|
1646
|
+
resolve(false);
|
|
1647
|
+
}, 2500);
|
|
1648
|
+
this.pendingLogout = { requestId, resolve, timeoutId };
|
|
1649
|
+
try {
|
|
1650
|
+
target.postMessage({
|
|
1651
|
+
source: EINBLICK_PARENT_SOURCE,
|
|
1652
|
+
type: 'logout',
|
|
1653
|
+
nonce: this.bridgeNonce,
|
|
1654
|
+
requestId,
|
|
1655
|
+
}, this.appOrigin);
|
|
1656
|
+
}
|
|
1657
|
+
catch {
|
|
1658
|
+
window.clearTimeout(timeoutId);
|
|
1659
|
+
if (this.pendingLogout?.requestId === requestId) {
|
|
1660
|
+
this.pendingLogout = null;
|
|
1661
|
+
}
|
|
1662
|
+
resolve(false);
|
|
1663
|
+
}
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
async logoutViaPopup(sessionToken) {
|
|
1638
1667
|
const logoutUrl = this.buildBridgeUrl({
|
|
1639
1668
|
mode: 'logout',
|
|
1640
1669
|
nonce: createRandomId(),
|
|
@@ -1661,28 +1690,11 @@ class EinblickEditRuntime {
|
|
|
1661
1690
|
}
|
|
1662
1691
|
}, 400);
|
|
1663
1692
|
});
|
|
1664
|
-
this.reloadBridge();
|
|
1665
1693
|
}
|
|
1666
1694
|
setActiveElement(element) {
|
|
1667
1695
|
if (this.activeElement === element) {
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
!element.contains(this.hoverButtonContainer)) {
|
|
1671
|
-
element.append(this.hoverButtonContainer);
|
|
1672
|
-
}
|
|
1673
|
-
this.updateHoverButtonVisibility(element);
|
|
1674
|
-
return;
|
|
1675
|
-
}
|
|
1676
|
-
if (this.activeElement) {
|
|
1677
|
-
this.activeElement.removeAttribute('data-einblick-editable-active');
|
|
1678
|
-
const originalPosition = this.activeElement.dataset.einblickRuntimeOriginalPosition;
|
|
1679
|
-
if (originalPosition !== undefined) {
|
|
1680
|
-
this.activeElement.style.position =
|
|
1681
|
-
originalPosition === RELATIVE_POSITION_SENTINEL
|
|
1682
|
-
? ''
|
|
1683
|
-
: originalPosition;
|
|
1684
|
-
delete this.activeElement.dataset.einblickRuntimeOriginalPosition;
|
|
1685
|
-
}
|
|
1696
|
+
this.updateHoverChrome();
|
|
1697
|
+
return;
|
|
1686
1698
|
}
|
|
1687
1699
|
this.activeElement = element;
|
|
1688
1700
|
if (!element ||
|
|
@@ -1691,22 +1703,176 @@ class EinblickEditRuntime {
|
|
|
1691
1703
|
!this.state.isAuthenticated) {
|
|
1692
1704
|
if (this.hoverButtonContainer) {
|
|
1693
1705
|
this.hoverButtonContainer.hidden = true;
|
|
1694
|
-
this.hoverButtonContainer.remove();
|
|
1695
1706
|
}
|
|
1707
|
+
this.activeOutline?.remove();
|
|
1708
|
+
this.activeOutline = null;
|
|
1709
|
+
this.clearQuickFieldOverlays();
|
|
1696
1710
|
if (this.hoverCreateButton) {
|
|
1697
1711
|
this.hoverCreateButton.hidden = true;
|
|
1698
1712
|
}
|
|
1699
1713
|
return;
|
|
1700
1714
|
}
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
element.style.position || RELATIVE_POSITION_SENTINEL;
|
|
1705
|
-
element.style.position = 'relative';
|
|
1706
|
-
}
|
|
1715
|
+
this.ensureActiveOutline();
|
|
1716
|
+
this.hoverButtonContainer.hidden = false;
|
|
1717
|
+
this.updateHoverButtonLabel();
|
|
1707
1718
|
this.updateHoverButtonVisibility(element);
|
|
1719
|
+
this.renderQuickFieldOverlays(element);
|
|
1720
|
+
this.updateHoverChrome();
|
|
1721
|
+
}
|
|
1722
|
+
ensureActiveOutline() {
|
|
1723
|
+
if (this.activeOutline || typeof document === 'undefined') {
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
const outline = document.createElement('div');
|
|
1727
|
+
outline.setAttribute('data-einblick-edit-outline', 'true');
|
|
1728
|
+
document.body.append(outline);
|
|
1729
|
+
this.activeOutline = outline;
|
|
1730
|
+
}
|
|
1731
|
+
updateHoverChrome() {
|
|
1732
|
+
if (!this.activeElement || !this.state.isAuthenticated) {
|
|
1733
|
+
this.activeOutline?.remove();
|
|
1734
|
+
this.activeOutline = null;
|
|
1735
|
+
if (this.hoverButtonContainer) {
|
|
1736
|
+
this.hoverButtonContainer.hidden = true;
|
|
1737
|
+
}
|
|
1738
|
+
this.clearQuickFieldOverlays();
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
if (!this.activeElement.isConnected) {
|
|
1742
|
+
this.setActiveElement(null);
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
this.ensureActiveOutline();
|
|
1746
|
+
const rect = this.activeElement.getBoundingClientRect();
|
|
1747
|
+
const isVisible = this.positionOutline(this.activeOutline, this.activeElement);
|
|
1748
|
+
if (!isVisible || rect.width <= 0 || rect.height <= 0) {
|
|
1749
|
+
if (this.hoverButtonContainer) {
|
|
1750
|
+
this.hoverButtonContainer.hidden = true;
|
|
1751
|
+
}
|
|
1752
|
+
this.clearQuickFieldOverlays();
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
this.updateHoverButtonLabel();
|
|
1756
|
+
this.updateHoverButtonVisibility(this.activeElement);
|
|
1757
|
+
this.positionHoverControls(rect);
|
|
1758
|
+
this.updateQuickFieldOverlayPositions();
|
|
1759
|
+
}
|
|
1760
|
+
positionOutline(outline, target) {
|
|
1761
|
+
if (!outline) {
|
|
1762
|
+
return false;
|
|
1763
|
+
}
|
|
1764
|
+
const rect = target.getBoundingClientRect();
|
|
1765
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
1766
|
+
outline.hidden = true;
|
|
1767
|
+
return false;
|
|
1768
|
+
}
|
|
1769
|
+
const style = window.getComputedStyle(target);
|
|
1770
|
+
outline.hidden = false;
|
|
1771
|
+
outline.style.setProperty('--einblick-overlay-radius', style.borderRadius || '0.35rem');
|
|
1772
|
+
outline.style.left = `${Math.round(rect.left)}px`;
|
|
1773
|
+
outline.style.top = `${Math.round(rect.top)}px`;
|
|
1774
|
+
outline.style.width = `${Math.round(rect.width)}px`;
|
|
1775
|
+
outline.style.height = `${Math.round(rect.height)}px`;
|
|
1776
|
+
return true;
|
|
1777
|
+
}
|
|
1778
|
+
positionHoverControls(rect) {
|
|
1779
|
+
if (!this.hoverButtonContainer) {
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1708
1782
|
this.hoverButtonContainer.hidden = false;
|
|
1709
|
-
|
|
1783
|
+
const margin = 8;
|
|
1784
|
+
const width = this.hoverButtonContainer.offsetWidth || 180;
|
|
1785
|
+
const height = this.hoverButtonContainer.offsetHeight || 32;
|
|
1786
|
+
const left = clamp(rect.right - width - margin, margin, Math.max(margin, window.innerWidth - width - margin));
|
|
1787
|
+
const top = clamp(rect.top + margin, margin, Math.max(margin, window.innerHeight - height - margin));
|
|
1788
|
+
this.hoverButtonContainer.style.left = `${Math.round(left)}px`;
|
|
1789
|
+
this.hoverButtonContainer.style.top = `${Math.round(top)}px`;
|
|
1790
|
+
}
|
|
1791
|
+
clearQuickFieldOverlays() {
|
|
1792
|
+
this.quickFieldOverlayContainer?.remove();
|
|
1793
|
+
this.quickFieldOverlayContainer = null;
|
|
1794
|
+
this.quickFieldOverlays = [];
|
|
1795
|
+
}
|
|
1796
|
+
renderQuickFieldOverlays(element) {
|
|
1797
|
+
this.clearQuickFieldOverlays();
|
|
1798
|
+
const targets = this.getQuickEditableTargets(element);
|
|
1799
|
+
if (targets.length === 0 || typeof document === 'undefined') {
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
const container = document.createElement('div');
|
|
1803
|
+
container.setAttribute('data-einblick-field-overlays', 'true');
|
|
1804
|
+
this.quickFieldOverlays = targets.map((target) => {
|
|
1805
|
+
const binding = readBindingFromElement(target);
|
|
1806
|
+
const outline = document.createElement('div');
|
|
1807
|
+
outline.setAttribute('data-einblick-edit-field-outline', 'true');
|
|
1808
|
+
const button = document.createElement('button');
|
|
1809
|
+
button.type = 'button';
|
|
1810
|
+
button.setAttribute('data-einblick-field-button', binding && isMediaEditableBinding(binding) ? 'media' : 'text');
|
|
1811
|
+
button.textContent =
|
|
1812
|
+
binding && isMediaEditableBinding(binding)
|
|
1813
|
+
? getMediaActionLabel(binding, this.messages)
|
|
1814
|
+
: this.messages.actions.editText;
|
|
1815
|
+
button.addEventListener('pointerdown', (event) => {
|
|
1816
|
+
event.stopPropagation();
|
|
1817
|
+
});
|
|
1818
|
+
button.addEventListener('click', (event) => {
|
|
1819
|
+
stopEvent(event);
|
|
1820
|
+
const nextBinding = readBindingFromElement(target);
|
|
1821
|
+
if (!nextBinding) {
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
if (isMediaEditableBinding(nextBinding)) {
|
|
1825
|
+
this.openMediaPicker(target, nextBinding, button);
|
|
1826
|
+
return;
|
|
1827
|
+
}
|
|
1828
|
+
this.openInlineEditor(target, nextBinding);
|
|
1829
|
+
});
|
|
1830
|
+
container.append(outline, button);
|
|
1831
|
+
return { target, outline, button };
|
|
1832
|
+
});
|
|
1833
|
+
document.body.append(container);
|
|
1834
|
+
this.quickFieldOverlayContainer = container;
|
|
1835
|
+
this.updateQuickFieldOverlayPositions();
|
|
1836
|
+
}
|
|
1837
|
+
updateQuickFieldOverlayPositions() {
|
|
1838
|
+
if (!this.quickFieldOverlayContainer) {
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
for (const overlay of this.quickFieldOverlays) {
|
|
1842
|
+
if (!overlay.target.isConnected) {
|
|
1843
|
+
overlay.outline.hidden = true;
|
|
1844
|
+
overlay.button.hidden = true;
|
|
1845
|
+
continue;
|
|
1846
|
+
}
|
|
1847
|
+
const rect = overlay.target.getBoundingClientRect();
|
|
1848
|
+
const isVisible = this.positionOutline(overlay.outline, overlay.target);
|
|
1849
|
+
overlay.button.hidden = !isVisible;
|
|
1850
|
+
if (!isVisible) {
|
|
1851
|
+
continue;
|
|
1852
|
+
}
|
|
1853
|
+
const margin = 4;
|
|
1854
|
+
const width = overlay.button.offsetWidth || 96;
|
|
1855
|
+
const height = overlay.button.offsetHeight || 26;
|
|
1856
|
+
const left = clamp(rect.left + margin, margin, Math.max(margin, window.innerWidth - width - margin));
|
|
1857
|
+
const top = clamp(rect.top + margin, margin, Math.max(margin, window.innerHeight - height - margin));
|
|
1858
|
+
overlay.button.style.left = `${Math.round(left)}px`;
|
|
1859
|
+
overlay.button.style.top = `${Math.round(top)}px`;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
updateHoverButtonLabel() {
|
|
1863
|
+
if (!this.hoverButton || !this.activeElement) {
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
const binding = readBindingFromElement(this.activeElement);
|
|
1867
|
+
if (!binding?.fieldKey) {
|
|
1868
|
+
this.hoverButton.textContent = this.messages.actions.editElement;
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
this.hoverButton.textContent = isMediaEditableBinding(binding)
|
|
1872
|
+
? getMediaActionLabel(binding, this.messages)
|
|
1873
|
+
: shouldUseInlineEditor(binding)
|
|
1874
|
+
? this.messages.actions.editText
|
|
1875
|
+
: this.messages.actions.edit;
|
|
1710
1876
|
}
|
|
1711
1877
|
reloadBridge() {
|
|
1712
1878
|
this.clearBridgePing();
|
|
@@ -1859,7 +2025,67 @@ class EinblickEditRuntime {
|
|
|
1859
2025
|
return null;
|
|
1860
2026
|
}
|
|
1861
2027
|
const editableTarget = target.closest('[data-einblick-editable="true"]');
|
|
1862
|
-
|
|
2028
|
+
if (!editableTarget) {
|
|
2029
|
+
return null;
|
|
2030
|
+
}
|
|
2031
|
+
const binding = readBindingFromElement(editableTarget);
|
|
2032
|
+
if (!binding?.fieldKey) {
|
|
2033
|
+
return editableTarget;
|
|
2034
|
+
}
|
|
2035
|
+
const entryTarget = this.findEntryTargetForField(editableTarget, binding);
|
|
2036
|
+
return entryTarget ?? editableTarget;
|
|
2037
|
+
}
|
|
2038
|
+
findEntryTargetForField(fieldElement, binding) {
|
|
2039
|
+
let parent = fieldElement.parentElement;
|
|
2040
|
+
while (parent) {
|
|
2041
|
+
const parentBinding = readBindingFromElement(parent);
|
|
2042
|
+
if (parentBinding &&
|
|
2043
|
+
!parentBinding.fieldKey &&
|
|
2044
|
+
parentBinding.resourceSlug === binding.resourceSlug &&
|
|
2045
|
+
parentBinding.recordId === binding.recordId) {
|
|
2046
|
+
return parent;
|
|
2047
|
+
}
|
|
2048
|
+
parent = parent.parentElement;
|
|
2049
|
+
}
|
|
2050
|
+
return null;
|
|
2051
|
+
}
|
|
2052
|
+
getQuickEditableTargets(element) {
|
|
2053
|
+
const binding = readBindingFromElement(element);
|
|
2054
|
+
if (!binding) {
|
|
2055
|
+
return [];
|
|
2056
|
+
}
|
|
2057
|
+
if (binding.fieldKey) {
|
|
2058
|
+
return shouldUseInlineEditor(binding) || isMediaEditableBinding(binding)
|
|
2059
|
+
? [element]
|
|
2060
|
+
: [];
|
|
2061
|
+
}
|
|
2062
|
+
const selector = [
|
|
2063
|
+
'[data-einblick-editable="true"]',
|
|
2064
|
+
`[data-einblick-resource="${CSS.escape(binding.resourceSlug)}"]`,
|
|
2065
|
+
`[data-einblick-record-id="${CSS.escape(binding.recordId)}"]`,
|
|
2066
|
+
'[data-einblick-field-key]',
|
|
2067
|
+
].join('');
|
|
2068
|
+
const fields = Array.from(element.querySelectorAll(selector));
|
|
2069
|
+
return fields
|
|
2070
|
+
.filter((candidate) => {
|
|
2071
|
+
const candidateBinding = readBindingFromElement(candidate);
|
|
2072
|
+
return (!!candidateBinding &&
|
|
2073
|
+
(shouldUseInlineEditor(candidateBinding) ||
|
|
2074
|
+
isMediaEditableBinding(candidateBinding)));
|
|
2075
|
+
})
|
|
2076
|
+
.slice(0, 24);
|
|
2077
|
+
}
|
|
2078
|
+
isSdkChromeTarget(target) {
|
|
2079
|
+
if (!(target instanceof Node)) {
|
|
2080
|
+
return false;
|
|
2081
|
+
}
|
|
2082
|
+
return (!!this.hoverButtonContainer?.contains(target) ||
|
|
2083
|
+
!!this.quickFieldOverlayContainer?.contains(target) ||
|
|
2084
|
+
!!this.activeOutline?.contains(target) ||
|
|
2085
|
+
!!this.brandBadge?.contains(target) ||
|
|
2086
|
+
!!this.brandPopover?.contains(target) ||
|
|
2087
|
+
!!this.bottomBarHost?.contains(target) ||
|
|
2088
|
+
!!this.bottomBarMoreMenu?.contains(target));
|
|
1863
2089
|
}
|
|
1864
2090
|
findElementForBinding(binding) {
|
|
1865
2091
|
const selector = [
|
|
@@ -1888,217 +2114,286 @@ class EinblickEditRuntime {
|
|
|
1888
2114
|
this.closeDrawer();
|
|
1889
2115
|
this.closeInlineEditor();
|
|
1890
2116
|
this.setActiveElement(null);
|
|
1891
|
-
const
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
gap: target.style.gap,
|
|
1903
|
-
minWidth: target.style.minWidth,
|
|
2117
|
+
const restoreState = {
|
|
2118
|
+
childNodes: Array.from(target.childNodes).map((node) => node.cloneNode(true)),
|
|
2119
|
+
contentEditable: target.getAttribute('contenteditable'),
|
|
2120
|
+
spellcheck: target.getAttribute('spellcheck'),
|
|
2121
|
+
tabIndex: target.tabIndex,
|
|
2122
|
+
hadTabIndex: target.hasAttribute('tabindex'),
|
|
2123
|
+
role: target.getAttribute('role'),
|
|
2124
|
+
ariaMultiline: target.getAttribute('aria-multiline'),
|
|
2125
|
+
title: target.getAttribute('title'),
|
|
2126
|
+
value: binding.value,
|
|
2127
|
+
cleanup: [],
|
|
1904
2128
|
};
|
|
2129
|
+
this.currentInlineRestoreState = restoreState;
|
|
1905
2130
|
target.dataset.einblickInlineEditing = 'true';
|
|
1906
|
-
target.
|
|
1907
|
-
target.
|
|
1908
|
-
target.
|
|
1909
|
-
target.
|
|
1910
|
-
target.
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
const errorMessage = document.createElement('div');
|
|
1922
|
-
errorMessage.setAttribute('data-einblick-inline-error', 'true');
|
|
1923
|
-
errorMessage.hidden = true;
|
|
1924
|
-
const actions = document.createElement('div');
|
|
1925
|
-
actions.setAttribute('data-einblick-inline-actions', 'true');
|
|
1926
|
-
const cancelButton = document.createElement('button');
|
|
1927
|
-
cancelButton.type = 'button';
|
|
1928
|
-
cancelButton.textContent = this.messages.actions.cancel;
|
|
1929
|
-
cancelButton.setAttribute('data-einblick-edit-action', 'secondary');
|
|
1930
|
-
cancelButton.addEventListener('click', () => this.closeInlineEditor());
|
|
1931
|
-
const saveButton = document.createElement('button');
|
|
1932
|
-
saveButton.type = 'button';
|
|
1933
|
-
saveButton.textContent = this.messages.actions.save;
|
|
1934
|
-
saveButton.setAttribute('data-einblick-edit-action', 'primary');
|
|
1935
|
-
const runSave = async () => {
|
|
1936
|
-
errorMessage.hidden = true;
|
|
1937
|
-
errorMessage.textContent = '';
|
|
1938
|
-
saveButton.disabled = true;
|
|
1939
|
-
saveButton.textContent = this.messages.actions.saving;
|
|
1940
|
-
cancelButton.disabled = true;
|
|
1941
|
-
try {
|
|
1942
|
-
const nextValue = this.getControlValue(control, binding);
|
|
1943
|
-
const savedValue = await this.saveInlineField(binding, nextValue);
|
|
1944
|
-
this.closeInlineEditor();
|
|
1945
|
-
this.applyValueToElement(target, binding, savedValue);
|
|
1946
|
-
await this.callbacks.onSave?.({
|
|
1947
|
-
binding,
|
|
1948
|
-
value: savedValue,
|
|
1949
|
-
source: 'inline',
|
|
1950
|
-
});
|
|
1951
|
-
}
|
|
1952
|
-
catch (error) {
|
|
1953
|
-
errorMessage.hidden = false;
|
|
1954
|
-
errorMessage.textContent =
|
|
1955
|
-
error instanceof Error
|
|
1956
|
-
? error.message
|
|
1957
|
-
: this.messages.errors.saveFieldFailed;
|
|
1958
|
-
saveButton.disabled = false;
|
|
1959
|
-
saveButton.textContent = this.messages.actions.save;
|
|
1960
|
-
cancelButton.disabled = false;
|
|
1961
|
-
}
|
|
2131
|
+
target.setAttribute('contenteditable', 'plaintext-only');
|
|
2132
|
+
target.setAttribute('spellcheck', 'true');
|
|
2133
|
+
target.setAttribute('role', 'textbox');
|
|
2134
|
+
target.setAttribute('aria-multiline', fieldTypeAllowsMultiline(binding) ? 'true' : 'false');
|
|
2135
|
+
if (!target.hasAttribute('tabindex')) {
|
|
2136
|
+
target.tabIndex = 0;
|
|
2137
|
+
}
|
|
2138
|
+
if ((target.textContent ?? '').trim().length === 0 &&
|
|
2139
|
+
binding.value !== undefined &&
|
|
2140
|
+
binding.value !== null) {
|
|
2141
|
+
target.textContent = formatValueForText(binding.value);
|
|
2142
|
+
}
|
|
2143
|
+
const handleClick = (event) => {
|
|
2144
|
+
event.preventDefault();
|
|
2145
|
+
event.stopPropagation();
|
|
1962
2146
|
};
|
|
1963
|
-
|
|
1964
|
-
void runSave();
|
|
1965
|
-
});
|
|
1966
|
-
control.addEventListener('keydown', (event) => {
|
|
1967
|
-
const keyboardEvent = event;
|
|
2147
|
+
const handleKeyDown = (keyboardEvent) => {
|
|
1968
2148
|
if (keyboardEvent.key === 'Escape') {
|
|
1969
2149
|
keyboardEvent.preventDefault();
|
|
2150
|
+
keyboardEvent.stopPropagation();
|
|
1970
2151
|
this.closeInlineEditor();
|
|
1971
2152
|
return;
|
|
1972
2153
|
}
|
|
1973
2154
|
if (keyboardEvent.key === 'Enter' &&
|
|
1974
|
-
(!(
|
|
2155
|
+
(!fieldTypeAllowsMultiline(binding) ||
|
|
1975
2156
|
keyboardEvent.metaKey ||
|
|
1976
2157
|
keyboardEvent.ctrlKey)) {
|
|
1977
2158
|
keyboardEvent.preventDefault();
|
|
1978
|
-
|
|
2159
|
+
keyboardEvent.stopPropagation();
|
|
2160
|
+
void this.commitInlineEditor();
|
|
2161
|
+
return;
|
|
1979
2162
|
}
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
2163
|
+
if (isTextInputEvent(keyboardEvent)) {
|
|
2164
|
+
keyboardEvent.stopPropagation();
|
|
2165
|
+
}
|
|
2166
|
+
};
|
|
2167
|
+
const handlePaste = (event) => {
|
|
2168
|
+
const text = event.clipboardData?.getData('text/plain');
|
|
2169
|
+
if (text === undefined) {
|
|
2170
|
+
return;
|
|
2171
|
+
}
|
|
2172
|
+
event.preventDefault();
|
|
2173
|
+
event.stopPropagation();
|
|
2174
|
+
this.insertPlainTextAtSelection(text);
|
|
2175
|
+
};
|
|
2176
|
+
const handleBlur = () => {
|
|
2177
|
+
window.setTimeout(() => {
|
|
2178
|
+
if (this.currentInlineTarget === target) {
|
|
2179
|
+
void this.commitInlineEditor();
|
|
2180
|
+
}
|
|
2181
|
+
}, 0);
|
|
2182
|
+
};
|
|
2183
|
+
target.addEventListener('click', handleClick, true);
|
|
2184
|
+
target.addEventListener('keydown', handleKeyDown, true);
|
|
2185
|
+
target.addEventListener('paste', handlePaste, true);
|
|
2186
|
+
target.addEventListener('blur', handleBlur, true);
|
|
2187
|
+
restoreState.cleanup.push(() => target.removeEventListener('click', handleClick, true), () => target.removeEventListener('keydown', handleKeyDown, true), () => target.removeEventListener('paste', handlePaste, true), () => target.removeEventListener('blur', handleBlur, true));
|
|
1985
2188
|
this.currentInlineTarget = target;
|
|
1986
2189
|
this.currentInlineBinding = binding;
|
|
2190
|
+
this.ensureActiveOutline();
|
|
2191
|
+
this.activeOutline?.setAttribute('data-einblick-inline-active', 'true');
|
|
2192
|
+
this.positionOutline(this.activeOutline, target);
|
|
1987
2193
|
window.setTimeout(() => {
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
}
|
|
2194
|
+
target.focus({ preventScroll: true });
|
|
2195
|
+
this.placeCaretAtEnd(target);
|
|
1991
2196
|
}, 0);
|
|
1992
2197
|
}
|
|
1993
|
-
closeInlineEditor() {
|
|
2198
|
+
closeInlineEditor(options = {}) {
|
|
2199
|
+
const restore = options.restore ?? true;
|
|
1994
2200
|
if (this.currentInlineTarget && this.currentInlineRestoreState) {
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
this.currentInlineRestoreState.
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
this.currentInlineTarget.
|
|
2004
|
-
this.currentInlineRestoreState.minWidth;
|
|
2201
|
+
for (const cleanup of this.currentInlineRestoreState.cleanup) {
|
|
2202
|
+
cleanup();
|
|
2203
|
+
}
|
|
2204
|
+
if (restore) {
|
|
2205
|
+
this.currentInlineTarget.replaceChildren(...this.currentInlineRestoreState.childNodes.map((node) => node.cloneNode(true)));
|
|
2206
|
+
this.currentInlineTarget.dataset.einblickValue =
|
|
2207
|
+
serializeBindingValue(this.currentInlineRestoreState.value) ?? '';
|
|
2208
|
+
}
|
|
2209
|
+
this.restoreInlineTargetAttributes(this.currentInlineTarget, this.currentInlineRestoreState);
|
|
2005
2210
|
delete this.currentInlineTarget.dataset.einblickInlineEditing;
|
|
2211
|
+
delete this.currentInlineTarget.dataset.einblickInlineState;
|
|
2212
|
+
this.currentInlineTarget.removeAttribute('aria-busy');
|
|
2006
2213
|
}
|
|
2007
2214
|
this.currentInlineRestoreState = null;
|
|
2008
2215
|
this.currentInlineTarget = null;
|
|
2009
2216
|
this.currentInlineBinding = null;
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
const textarea = document.createElement('textarea');
|
|
2016
|
-
textarea.setAttribute('data-einblick-inline-textarea', 'true');
|
|
2017
|
-
return textarea;
|
|
2018
|
-
}
|
|
2019
|
-
case 'boolean': {
|
|
2020
|
-
const select = document.createElement('select');
|
|
2021
|
-
select.setAttribute('data-einblick-inline-select', 'true');
|
|
2022
|
-
const trueOption = document.createElement('option');
|
|
2023
|
-
trueOption.value = 'true';
|
|
2024
|
-
trueOption.textContent = this.messages.values.true;
|
|
2025
|
-
const falseOption = document.createElement('option');
|
|
2026
|
-
falseOption.value = 'false';
|
|
2027
|
-
falseOption.textContent = this.messages.values.false;
|
|
2028
|
-
select.append(trueOption, falseOption);
|
|
2029
|
-
return select;
|
|
2030
|
-
}
|
|
2031
|
-
default: {
|
|
2032
|
-
const textValue = typeof binding.value === 'string'
|
|
2033
|
-
? binding.value
|
|
2034
|
-
: target?.textContent ?? '';
|
|
2035
|
-
if (binding.fieldType === 'string' &&
|
|
2036
|
-
typeof textValue === 'string' &&
|
|
2037
|
-
textValue.includes('\n')) {
|
|
2038
|
-
const textarea = document.createElement('textarea');
|
|
2039
|
-
textarea.setAttribute('data-einblick-inline-textarea', 'true');
|
|
2040
|
-
return textarea;
|
|
2041
|
-
}
|
|
2042
|
-
const input = document.createElement('input');
|
|
2043
|
-
input.setAttribute('data-einblick-inline-control', 'true');
|
|
2044
|
-
if (binding.fieldType === 'number') {
|
|
2045
|
-
input.type = 'number';
|
|
2046
|
-
}
|
|
2047
|
-
else if (binding.fieldType === 'date') {
|
|
2048
|
-
input.type = 'date';
|
|
2049
|
-
}
|
|
2050
|
-
else {
|
|
2051
|
-
input.type = 'text';
|
|
2052
|
-
}
|
|
2053
|
-
return input;
|
|
2054
|
-
}
|
|
2217
|
+
this.inlineSaveInFlight = null;
|
|
2218
|
+
this.activeOutline?.removeAttribute('data-einblick-inline-active');
|
|
2219
|
+
if (!this.activeElement) {
|
|
2220
|
+
this.activeOutline?.remove();
|
|
2221
|
+
this.activeOutline = null;
|
|
2055
2222
|
}
|
|
2056
2223
|
}
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
control.value = currentValue === true ? 'true' : 'false';
|
|
2061
|
-
return;
|
|
2224
|
+
restoreInlineTargetAttributes(target, restoreState) {
|
|
2225
|
+
if (restoreState.contentEditable === null) {
|
|
2226
|
+
target.removeAttribute('contenteditable');
|
|
2062
2227
|
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
? currentValue
|
|
2066
|
-
.map((entry) => (typeof entry === 'string' ? entry : String(entry)))
|
|
2067
|
-
.join(', ')
|
|
2068
|
-
: currentValue === undefined || currentValue === null
|
|
2069
|
-
? ''
|
|
2070
|
-
: String(currentValue);
|
|
2071
|
-
return;
|
|
2228
|
+
else {
|
|
2229
|
+
target.setAttribute('contenteditable', restoreState.contentEditable);
|
|
2072
2230
|
}
|
|
2073
|
-
if (
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2231
|
+
if (restoreState.spellcheck === null) {
|
|
2232
|
+
target.removeAttribute('spellcheck');
|
|
2233
|
+
}
|
|
2234
|
+
else {
|
|
2235
|
+
target.setAttribute('spellcheck', restoreState.spellcheck);
|
|
2236
|
+
}
|
|
2237
|
+
if (restoreState.role === null) {
|
|
2238
|
+
target.removeAttribute('role');
|
|
2239
|
+
}
|
|
2240
|
+
else {
|
|
2241
|
+
target.setAttribute('role', restoreState.role);
|
|
2242
|
+
}
|
|
2243
|
+
if (restoreState.ariaMultiline === null) {
|
|
2244
|
+
target.removeAttribute('aria-multiline');
|
|
2245
|
+
}
|
|
2246
|
+
else {
|
|
2247
|
+
target.setAttribute('aria-multiline', restoreState.ariaMultiline);
|
|
2248
|
+
}
|
|
2249
|
+
if (restoreState.title === null) {
|
|
2250
|
+
target.removeAttribute('title');
|
|
2251
|
+
}
|
|
2252
|
+
else {
|
|
2253
|
+
target.setAttribute('title', restoreState.title);
|
|
2254
|
+
}
|
|
2255
|
+
if (restoreState.hadTabIndex) {
|
|
2256
|
+
target.tabIndex = restoreState.tabIndex;
|
|
2257
|
+
}
|
|
2258
|
+
else {
|
|
2259
|
+
target.removeAttribute('tabindex');
|
|
2077
2260
|
}
|
|
2078
|
-
control.value =
|
|
2079
|
-
currentValue === undefined || currentValue === null
|
|
2080
|
-
? ''
|
|
2081
|
-
: typeof currentValue === 'string'
|
|
2082
|
-
? currentValue
|
|
2083
|
-
: String(currentValue);
|
|
2084
2261
|
}
|
|
2085
|
-
|
|
2086
|
-
const rawValue =
|
|
2262
|
+
getInlineEditableValue(target, binding) {
|
|
2263
|
+
const rawValue = fieldTypeAllowsMultiline(binding)
|
|
2264
|
+
? target.innerText.replace(/\n$/, '')
|
|
2265
|
+
: (target.textContent ?? '').replace(/\s+/g, ' ').trim();
|
|
2087
2266
|
switch (binding.fieldType) {
|
|
2088
2267
|
case 'number':
|
|
2089
2268
|
return rawValue.trim().length === 0 ? null : Number(rawValue);
|
|
2090
|
-
case 'boolean':
|
|
2091
|
-
return rawValue === 'true';
|
|
2092
2269
|
case 'tags':
|
|
2093
2270
|
return rawValue
|
|
2094
2271
|
.split(',')
|
|
2095
2272
|
.map((entry) => entry.trim())
|
|
2096
2273
|
.filter(Boolean);
|
|
2274
|
+
case 'date':
|
|
2275
|
+
return normalizeDateValue(rawValue.trim());
|
|
2097
2276
|
default:
|
|
2098
2277
|
return rawValue;
|
|
2099
2278
|
}
|
|
2100
2279
|
}
|
|
2101
|
-
async
|
|
2280
|
+
async commitInlineEditor() {
|
|
2281
|
+
if (this.inlineSaveInFlight) {
|
|
2282
|
+
return await this.inlineSaveInFlight;
|
|
2283
|
+
}
|
|
2284
|
+
const target = this.currentInlineTarget;
|
|
2285
|
+
const binding = this.currentInlineBinding;
|
|
2286
|
+
if (!target || !binding) {
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
this.inlineSaveInFlight = (async () => {
|
|
2290
|
+
target.setAttribute('aria-busy', 'true');
|
|
2291
|
+
target.dataset.einblickInlineState = 'saving';
|
|
2292
|
+
try {
|
|
2293
|
+
const nextValue = this.getInlineEditableValue(target, binding);
|
|
2294
|
+
const savedValue = await this.saveInlineField(binding, nextValue);
|
|
2295
|
+
this.closeInlineEditor({ restore: false });
|
|
2296
|
+
this.applyValueToElement(target, binding, savedValue);
|
|
2297
|
+
await this.callbacks.onSave?.({
|
|
2298
|
+
binding,
|
|
2299
|
+
value: savedValue,
|
|
2300
|
+
source: 'inline',
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
catch (error) {
|
|
2304
|
+
target.dataset.einblickInlineState = 'error';
|
|
2305
|
+
target.removeAttribute('aria-busy');
|
|
2306
|
+
this.inlineSaveInFlight = null;
|
|
2307
|
+
const message = error instanceof Error
|
|
2308
|
+
? error.message
|
|
2309
|
+
: this.messages.errors.saveFieldFailed;
|
|
2310
|
+
target.title = message;
|
|
2311
|
+
this.callbacks.onError?.(message);
|
|
2312
|
+
}
|
|
2313
|
+
})();
|
|
2314
|
+
await this.inlineSaveInFlight;
|
|
2315
|
+
}
|
|
2316
|
+
insertPlainTextAtSelection(text) {
|
|
2317
|
+
const selection = window.getSelection();
|
|
2318
|
+
if (!selection || selection.rangeCount === 0) {
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2321
|
+
selection.deleteFromDocument();
|
|
2322
|
+
selection.getRangeAt(0).insertNode(document.createTextNode(text));
|
|
2323
|
+
selection.collapseToEnd();
|
|
2324
|
+
}
|
|
2325
|
+
placeCaretAtEnd(target) {
|
|
2326
|
+
const selection = window.getSelection();
|
|
2327
|
+
if (!selection) {
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
const range = document.createRange();
|
|
2331
|
+
range.selectNodeContents(target);
|
|
2332
|
+
range.collapse(false);
|
|
2333
|
+
selection.removeAllRanges();
|
|
2334
|
+
selection.addRange(range);
|
|
2335
|
+
}
|
|
2336
|
+
openMediaPicker(target, binding, button) {
|
|
2337
|
+
if (!this.state.isAuthenticated) {
|
|
2338
|
+
this.callbacks.onError?.(this.messages.errors.signInToEditField);
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
if (!binding.fieldKey) {
|
|
2342
|
+
this.callbacks.onError?.(this.messages.errors.fieldBindingRequired);
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
const input = document.createElement('input');
|
|
2346
|
+
input.type = 'file';
|
|
2347
|
+
input.accept = getMediaAcceptValue(binding);
|
|
2348
|
+
input.style.position = 'fixed';
|
|
2349
|
+
input.style.left = '-9999px';
|
|
2350
|
+
input.style.top = '0';
|
|
2351
|
+
const previousText = button?.textContent ?? '';
|
|
2352
|
+
const resetButton = () => {
|
|
2353
|
+
if (button) {
|
|
2354
|
+
button.disabled = false;
|
|
2355
|
+
button.textContent =
|
|
2356
|
+
previousText || getMediaActionLabel(binding, this.messages);
|
|
2357
|
+
}
|
|
2358
|
+
};
|
|
2359
|
+
input.addEventListener('change', () => {
|
|
2360
|
+
const file = input.files?.[0];
|
|
2361
|
+
input.remove();
|
|
2362
|
+
if (!file) {
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
if (button) {
|
|
2366
|
+
button.disabled = true;
|
|
2367
|
+
button.textContent = this.messages.actions.uploading;
|
|
2368
|
+
}
|
|
2369
|
+
void (async () => {
|
|
2370
|
+
try {
|
|
2371
|
+
const result = await this.saveInlineMediaField(binding, file);
|
|
2372
|
+
this.applyMediaPreviewToElement(target, file);
|
|
2373
|
+
await this.callbacks.onSave?.({
|
|
2374
|
+
binding,
|
|
2375
|
+
value: result.value,
|
|
2376
|
+
source: 'inline',
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
catch (error) {
|
|
2380
|
+
const message = error instanceof Error
|
|
2381
|
+
? error.message
|
|
2382
|
+
: this.messages.errors.saveFieldFailed;
|
|
2383
|
+
this.callbacks.onError?.(message);
|
|
2384
|
+
}
|
|
2385
|
+
finally {
|
|
2386
|
+
resetButton();
|
|
2387
|
+
}
|
|
2388
|
+
})();
|
|
2389
|
+
});
|
|
2390
|
+
input.addEventListener('cancel', () => {
|
|
2391
|
+
input.remove();
|
|
2392
|
+
});
|
|
2393
|
+
document.body.append(input);
|
|
2394
|
+
input.click();
|
|
2395
|
+
}
|
|
2396
|
+
async saveInlineMediaField(binding, file) {
|
|
2102
2397
|
if (!this.bridgeFrame?.contentWindow) {
|
|
2103
2398
|
throw new Error(this.messages.errors.bridgeNotConnected);
|
|
2104
2399
|
}
|
|
@@ -2111,15 +2406,68 @@ class EinblickEditRuntime {
|
|
|
2111
2406
|
const requestId = createRandomId();
|
|
2112
2407
|
const promise = new Promise((resolve, reject) => {
|
|
2113
2408
|
const timeoutId = window.setTimeout(() => {
|
|
2114
|
-
this.
|
|
2409
|
+
this.pendingInlineMediaSaves.delete(requestId);
|
|
2115
2410
|
reject(new Error(this.messages.errors.savingTimedOut));
|
|
2116
|
-
},
|
|
2117
|
-
this.
|
|
2411
|
+
}, 60000);
|
|
2412
|
+
this.pendingInlineMediaSaves.set(requestId, {
|
|
2118
2413
|
resolve,
|
|
2119
2414
|
reject,
|
|
2120
2415
|
timeoutId,
|
|
2121
2416
|
});
|
|
2122
2417
|
});
|
|
2418
|
+
this.bridgeFrame.contentWindow.postMessage({
|
|
2419
|
+
source: EINBLICK_PARENT_SOURCE,
|
|
2420
|
+
type: 'replace-inline-media',
|
|
2421
|
+
nonce: this.bridgeNonce,
|
|
2422
|
+
requestId,
|
|
2423
|
+
payload: {
|
|
2424
|
+
resourceSlug: binding.resourceSlug,
|
|
2425
|
+
recordId: binding.recordId,
|
|
2426
|
+
fieldKey: binding.fieldKey,
|
|
2427
|
+
file,
|
|
2428
|
+
},
|
|
2429
|
+
}, this.appOrigin);
|
|
2430
|
+
return await promise;
|
|
2431
|
+
}
|
|
2432
|
+
applyMediaPreviewToElement(element, file) {
|
|
2433
|
+
if (!file.type.startsWith('image/')) {
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
const image = element instanceof HTMLImageElement
|
|
2437
|
+
? element
|
|
2438
|
+
: element.querySelector('img');
|
|
2439
|
+
if (!image) {
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2442
|
+
const objectUrl = URL.createObjectURL(file);
|
|
2443
|
+
const previousUrl = image.src;
|
|
2444
|
+
image.addEventListener('load', () => {
|
|
2445
|
+
URL.revokeObjectURL(objectUrl);
|
|
2446
|
+
}, { once: true });
|
|
2447
|
+
image.addEventListener('error', () => {
|
|
2448
|
+
image.src = previousUrl;
|
|
2449
|
+
URL.revokeObjectURL(objectUrl);
|
|
2450
|
+
}, { once: true });
|
|
2451
|
+
image.src = objectUrl;
|
|
2452
|
+
}
|
|
2453
|
+
async saveInlineField(binding, value) {
|
|
2454
|
+
if (!this.bridgeFrame?.contentWindow) {
|
|
2455
|
+
throw new Error(this.messages.errors.bridgeNotConnected);
|
|
2456
|
+
}
|
|
2457
|
+
if (!this.state.isAuthenticated) {
|
|
2458
|
+
throw new Error(this.messages.errors.signInToEditField);
|
|
2459
|
+
}
|
|
2460
|
+
if (!binding.fieldKey) {
|
|
2461
|
+
throw new Error(this.messages.errors.fieldBindingRequired);
|
|
2462
|
+
}
|
|
2463
|
+
const requestId = createRandomId();
|
|
2464
|
+
const promise = new Promise((resolve, reject) => {
|
|
2465
|
+
const timeoutId = window.setTimeout(() => {
|
|
2466
|
+
this.pendingInlineSaves.delete(requestId);
|
|
2467
|
+
reject(new Error(this.messages.errors.savingTimedOut));
|
|
2468
|
+
}, 15000);
|
|
2469
|
+
this.pendingInlineSaves.set(requestId, { resolve, reject, timeoutId });
|
|
2470
|
+
});
|
|
2123
2471
|
this.bridgeFrame.contentWindow.postMessage({
|
|
2124
2472
|
source: EINBLICK_PARENT_SOURCE,
|
|
2125
2473
|
type: 'save-inline-field',
|