@accelerated-agency/visual-editor 0.2.3 → 0.2.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/dist/index.js +20 -10
- package/dist/vite.cjs +2426 -203
- package/dist/vite.js +2426 -203
- package/package.json +1 -1
package/dist/vite.cjs
CHANGED
|
@@ -93,12 +93,12 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
93
93
|
--bg-hover: #f1f5f9;
|
|
94
94
|
--border: #e2e8f0;
|
|
95
95
|
--border-sub: #f1f5f9;
|
|
96
|
-
--text: #
|
|
96
|
+
--text: #404040;
|
|
97
97
|
--text-2: #475569;
|
|
98
98
|
--text-3: #94a3b8;
|
|
99
99
|
--accent: #6366f1;
|
|
100
100
|
--accent-bg: #eef2ff;
|
|
101
|
-
--accent-txt: #
|
|
101
|
+
--accent-txt: #404040;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/* \u2500\u2500 Layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
@@ -107,15 +107,113 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
107
107
|
|
|
108
108
|
/* \u2500\u2500 Toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
109
109
|
#toolbar{
|
|
110
|
-
height:
|
|
111
|
-
display:flex;align-items:center;padding:0
|
|
112
|
-
|
|
110
|
+
height:52px;background:#FFFFFF;border-bottom:1px solid #E5E5E5;
|
|
111
|
+
display:flex;align-items:center;padding:0 16px;gap:0;flex-shrink:0;z-index:100;
|
|
112
|
+
overflow:visible
|
|
113
113
|
}
|
|
114
|
+
/* Toolbar layout sections */
|
|
115
|
+
.tb-left{display:flex;align-items:center;gap:0;flex-shrink:0}
|
|
116
|
+
.tb-center{display:flex;align-items:center;gap:4px;flex:1;justify-content:center}
|
|
117
|
+
.tb-right{display:flex;align-items:center;gap:6px;flex-shrink:0;margin-left:auto}
|
|
118
|
+
/* Logo */
|
|
119
|
+
.tb-logo{font-size:24px;color:#404040;flex-shrink:0;margin-right:10px;line-height:1}
|
|
120
|
+
/* Breadcrumb */
|
|
121
|
+
.tb-bc-item{font-size:14px;font-weight:500;color:#737373;white-space:nowrap}
|
|
122
|
+
.tb-bc-sep{font-size:14px;color:#737373;padding:0 6px;user-select:none}
|
|
123
|
+
.tb-bc-page{display:flex;align-items:center;gap:3px;cursor:pointer;color:#e4e4e7;font-size:13px;font-weight:500;background:none;border:none;padding:3px 6px;border-radius:5px;font-family:inherit;line-height:1}
|
|
124
|
+
.tb-bc-page:hover{background:rgba(255,255,255,.06)}
|
|
125
|
+
.tb-bc-page i{font-size:9px;color:#71717a}
|
|
126
|
+
/* DRAFT status badge */
|
|
127
|
+
.tb-draft-badge{display:flex;align-items:center;gap:5px;background:#E5E5E5;padding:5px 4px;border-radius:4px}
|
|
128
|
+
.tb-draft-dot{width:6px;height:6px;border-radius:50%;background:#404040;flex-shrink:0}
|
|
129
|
+
.tb-draft-lbl{font-size:12px;font-weight:700;color:##404040;letter-spacing:.04em}
|
|
130
|
+
/* Viewport selector */
|
|
131
|
+
.tb-viewport{display:flex;align-items:center;gap:4px;padding:6px 10px;border-radius:6px;border:1px solid #e5e7eb;background:#FFFFFF;height:30px;flex-shrink:0}
|
|
132
|
+
.tb-viewport #dev-label{font-size:14px;font-weight:500;color:##404040;min-width:auto;background:none;border:none;padding:0;overflow:visible;white-space:nowrap;text-overflow:clip;border-radius:0;max-width:none}
|
|
133
|
+
.tb-viewport i{font-size:9px;color:##404040}
|
|
134
|
+
/* Device toggle buttons */
|
|
135
|
+
.tb-dev-btns{display:flex;align-items:center;gap:1px;padding:8px 4px;background:#F0F0F0;border-radius:7px; height: 30px;width: 116px;}
|
|
136
|
+
.tb-dev-3btns{display:flex;align-items:center;gap:2px;padding:8px 4px;background:#F0F0F0;border-radius:7px; height: 30px;width: 100px;}
|
|
137
|
+
/* Dark icon buttons */
|
|
138
|
+
.tb-dk-btn{width:28px;height:28px;background:transparent;border:none;border-radius:5px;cursor:pointer;color:#71717a;display:flex;align-items:center;justify-content:center;font-size:13px;transition:all .12s;flex-shrink:0}
|
|
139
|
+
.tb-dk-btn:hover{color:#e4e4e7;background:rgba(255,255,255,.07)}
|
|
140
|
+
.tb-dk-btn.active{background:#FFFFFF}
|
|
141
|
+
/* Dark separator */
|
|
142
|
+
.tb-dk-sep{width:1px;height:16px;background:#3f3f46;flex-shrink:0;margin:0 2px}
|
|
143
|
+
/* Save status */
|
|
144
|
+
#dirty-dot{width:6px;height:6px;border-radius:50%;background:#22c55e;flex-shrink:0;transition:background .2s}
|
|
145
|
+
#dirty-dot.on{background:#f59e0b}
|
|
146
|
+
.tb-save-area{display:flex;align-items:center;gap:5px}
|
|
147
|
+
.tb-save-txt{font-size:14px;color:#00C951;white-space:nowrap}
|
|
148
|
+
.tb-save-txt::before{content:'Saved'}
|
|
149
|
+
#dirty-dot.on~.tb-save-txt::before{content:'Unsaved'}
|
|
150
|
+
.tb-save-time{font-size:12px;color:#52525b;white-space:nowrap}
|
|
151
|
+
#dirty-dot.on~.tb-save-time{display:none}
|
|
152
|
+
/* Simulate + Finalize buttons */
|
|
153
|
+
.tb-sim-btn{background:transparent;border:1px solid #e5e7eb;border-radius:6px;color:#404040;cursor:pointer;font-size:14px;font-weight:500;padding:6px 10px;display:flex;align-items:center;gap:5px;transition:all .15s;white-space:nowrap;font-family:inherit}
|
|
154
|
+
.tb-sim-btn:hover{background:rgba(255,255,255,.06);border-color:#52525b}
|
|
155
|
+
.tb-sim-btn i{font-size:11px}
|
|
156
|
+
.tb-fin-btn{background:#09090b;border:1px solid #3f3f46;border-radius:6px;color:#fafafa;cursor:pointer;font-size:14px;font-weight:600;padding:5px 14px;transition:all .15s;white-space:nowrap;font-family:inherit}
|
|
157
|
+
.tb-fin-btn:hover{background:#000;border-color:#52525b}
|
|
158
|
+
|
|
159
|
+
/* \u2500\u2500 Page loading hint (toolbar + sidebar) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
160
|
+
.tb-page-loading{
|
|
161
|
+
display:none;flex-shrink:0;align-items:center;margin:0 10px 0 4px;
|
|
162
|
+
max-width:min(340px,36vw);position:relative;z-index:120
|
|
163
|
+
}
|
|
164
|
+
.ve-page-loading-msg{
|
|
165
|
+
display:flex;align-items:center;gap:5px;font-size:9px;font-weight:500;
|
|
166
|
+
color:#b45309;white-space:nowrap;user-select:none
|
|
167
|
+
}
|
|
168
|
+
.ve-pl-dot{color:#d97706;font-size:9px;line-height:1;flex-shrink:0;margin-top:1px}
|
|
169
|
+
.ve-pl-tip{
|
|
170
|
+
position:relative;display:inline-flex;align-items:center;cursor:help;
|
|
171
|
+
color:#b45309;padding:2px;border-radius:4px;outline:none
|
|
172
|
+
}
|
|
173
|
+
.ve-pl-tip:hover,.ve-pl-tip:focus{background:rgba(217,119,6,.12)}
|
|
174
|
+
.ve-pl-tip .bi{font-size:13px}
|
|
175
|
+
/* Fixed viewport positioning \u2014 avoids clipping by #main / #left-panel overflow */
|
|
176
|
+
.ve-pl-tooltip{
|
|
177
|
+
display:none;position:fixed;left:0;top:0;
|
|
178
|
+
width:min(280px,calc(100vw - 40px));padding:10px 12px 11px;border-radius:8px;
|
|
179
|
+
background:#27272a;color:#fafafa;font-size:11px;font-weight:400;line-height:1.45;
|
|
180
|
+
box-shadow:0 8px 24px rgba(0,0,0,.35),0 0 0 1px rgba(255,255,255,.06);
|
|
181
|
+
z-index:10050;pointer-events:none;text-align:left;white-space:normal;
|
|
182
|
+
transform:translateX(-50%);margin:0
|
|
183
|
+
}
|
|
184
|
+
.ve-pl-tooltip::after{
|
|
185
|
+
content:'';position:absolute;left:50%;margin-left:-6px;
|
|
186
|
+
border:6px solid transparent
|
|
187
|
+
}
|
|
188
|
+
/* Sidebar: tooltip below trigger \u2014 arrow points up */
|
|
189
|
+
.ve-pl-tip-below:not(.is-tip-flip) .ve-pl-tooltip::after{
|
|
190
|
+
top:auto;bottom:100%;border-bottom-color:#27272a;border-top-color:transparent
|
|
191
|
+
}
|
|
192
|
+
/* Toolbar: tooltip above trigger \u2014 arrow points down */
|
|
193
|
+
.ve-pl-tip:not(.ve-pl-tip-below):not(.is-tip-flip) .ve-pl-tooltip::after{
|
|
194
|
+
top:100%;border-top-color:#27272a;border-bottom-color:transparent
|
|
195
|
+
}
|
|
196
|
+
/* Flipped: sidebar showed above */
|
|
197
|
+
.ve-pl-tip-below.is-tip-flip .ve-pl-tooltip::after{
|
|
198
|
+
top:100%;bottom:auto;border-top-color:#27272a;border-bottom-color:transparent
|
|
199
|
+
}
|
|
200
|
+
/* Flipped: toolbar showed below */
|
|
201
|
+
.ve-pl-tip:not(.ve-pl-tip-below).is-tip-flip .ve-pl-tooltip::after{
|
|
202
|
+
top:auto;bottom:100%;border-bottom-color:#27272a;border-top-color:transparent
|
|
203
|
+
}
|
|
204
|
+
.ve-pl-tip.is-tip-open .ve-pl-tooltip{display:block}
|
|
205
|
+
.ve-pl-tip-hd{display:block;font-weight:700;margin-bottom:6px;color:#fff;font-size:12px}
|
|
206
|
+
.ve-pl-tip-body{color:#e4e4e7}
|
|
207
|
+
.lp-loading-banner{
|
|
208
|
+
display:none;flex-shrink:0;padding:8px 16px 10px;border-bottom:1px solid var(--border);
|
|
209
|
+
background:#fffbeb;position:relative;z-index:115;overflow:visible
|
|
210
|
+
}
|
|
211
|
+
.lp-loading-banner .ve-page-loading-msg{white-space:normal;flex-wrap:wrap;font-size:9px}
|
|
114
212
|
|
|
115
213
|
/* \u2500\u2500 Left panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
116
214
|
#left-panel{
|
|
117
|
-
width:232px;background
|
|
118
|
-
display:flex;flex-direction:column;flex-shrink:0;overflow:hidden
|
|
215
|
+
width:232px;background:#FFFFFF;border-right:1px solid var(--border);
|
|
216
|
+
display:flex;flex-direction:column;flex-shrink:0;min-height:0;overflow:hidden
|
|
119
217
|
}
|
|
120
218
|
|
|
121
219
|
/* \u2500\u2500 Iframe panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
@@ -124,6 +222,42 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
124
222
|
align-items:center;justify-content:flex-start;overflow:auto;position:relative
|
|
125
223
|
}
|
|
126
224
|
|
|
225
|
+
/* \u2500\u2500 Floating selection toolbar (above iframe selection) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
226
|
+
#selection-floater{
|
|
227
|
+
display:none;position:absolute;z-index:60;align-items:center;gap:1px;
|
|
228
|
+
background:#fff;border:1px solid var(--border);border-radius:8px;padding:3px 4px;
|
|
229
|
+
box-shadow:0 4px 20px rgba(0,0,0,.12),0 0 0 1px rgba(0,0,0,.04);
|
|
230
|
+
pointer-events:auto
|
|
231
|
+
}
|
|
232
|
+
#selection-floater .sf-btn{
|
|
233
|
+
width:30px;height:28px;border:none;background:transparent;border-radius:6px;
|
|
234
|
+
cursor:pointer;color:var(--text-2);display:flex;align-items:center;justify-content:center;
|
|
235
|
+
font-size:14px;transition:background .12s,color .12s
|
|
236
|
+
}
|
|
237
|
+
#selection-floater .sf-btn:hover{background:var(--bg-hover);color:var(--text)}
|
|
238
|
+
#selection-floater .sf-btn.active{background:var(--accent-bg);color:var(--accent-txt)}
|
|
239
|
+
#selection-floater .sf-btn:disabled{opacity:.35;cursor:not-allowed}
|
|
240
|
+
#selection-floater .sf-sep{width:1px;height:16px;background:var(--border);margin:0 2px;flex-shrink:0}
|
|
241
|
+
|
|
242
|
+
/* \u2500\u2500 DOM tree (Elements tab) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
243
|
+
.dt-tree{font-size:11px;padding:0px 0 0px 20px;user-select:none}
|
|
244
|
+
.dt-row{
|
|
245
|
+
display:flex;align-items:center;gap:2px;min-height:26px;padding:2px 8px 2px 4px;
|
|
246
|
+
cursor:pointer;color:var(--text-2);border-radius:4px;margin:0 4px
|
|
247
|
+
}
|
|
248
|
+
.dt-row:hover{background:var(--bg-hover);color:var(--text)}
|
|
249
|
+
.dt-row.dt-selected{background:#e0e7ff!important;color:var(--accent-txt)!important;outline:1px solid #a5b4fc}
|
|
250
|
+
.dt-chev{
|
|
251
|
+
width:16px;height:16px;flex-shrink:0;border:none;background:transparent;
|
|
252
|
+
cursor:pointer;color:var(--text-3);display:flex;align-items:center;justify-content:center;
|
|
253
|
+
padding:0;font-size:10px;border-radius:3px
|
|
254
|
+
}
|
|
255
|
+
.dt-chev:hover{color:var(--text)}
|
|
256
|
+
.dt-chev.dt-spacer{visibility:hidden;pointer-events:none}
|
|
257
|
+
.dt-ico{width:16px;flex-shrink:0;text-align:center;color:var(--text-3);font-size:12px}
|
|
258
|
+
.dt-lbl{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,monospace;font-size:10px}
|
|
259
|
+
.dt-muted{padding:16px 12px;text-align:center;color:var(--text-3);font-size:11px;line-height:1.4}
|
|
260
|
+
|
|
127
261
|
/* \u2500\u2500 Right panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
128
262
|
#right-panel{
|
|
129
263
|
width:252px;background:var(--bg);border-left:1px solid var(--border);
|
|
@@ -137,16 +271,6 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
137
271
|
color:var(--text-3);flex-shrink:0;gap:5px;overflow:hidden
|
|
138
272
|
}
|
|
139
273
|
|
|
140
|
-
/* \u2500\u2500 Loading overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
141
|
-
#loading{
|
|
142
|
-
position:absolute;inset:0;background:rgba(248,250,252,.95);display:flex;
|
|
143
|
-
flex-direction:column;align-items:center;justify-content:center;gap:12px;z-index:50;
|
|
144
|
-
backdrop-filter:blur(2px)
|
|
145
|
-
}
|
|
146
|
-
#loading.hidden{display:none}
|
|
147
|
-
.spinner{width:32px;height:32px;border:2.5px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .7s linear infinite}
|
|
148
|
-
@keyframes spin{to{transform:rotate(360deg)}}
|
|
149
|
-
|
|
150
274
|
/* \u2500\u2500 No-URL overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
151
275
|
#no-url{
|
|
152
276
|
display:none;position:absolute;inset:0;flex-direction:column;
|
|
@@ -195,9 +319,7 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
195
319
|
.tg-btn.active{background:#fff;color:var(--accent-txt);box-shadow:0 1px 3px rgba(0,0,0,.1)}
|
|
196
320
|
.tg-btn.icon-only{padding:4px 8px}
|
|
197
321
|
|
|
198
|
-
/* \u2500\u2500 Dirty dot \
|
|
199
|
-
#dirty-dot{width:6px;height:6px;border-radius:50%;background:transparent;flex-shrink:0;transition:background .2s}
|
|
200
|
-
#dirty-dot.on{background:#f59e0b}
|
|
322
|
+
/* \u2500\u2500 Dirty dot \u2014 styles defined in toolbar block above \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
201
323
|
|
|
202
324
|
/* \u2500\u2500 URL bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
203
325
|
#url-bar{
|
|
@@ -206,22 +328,44 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
206
328
|
}
|
|
207
329
|
|
|
208
330
|
/* \u2500\u2500 Left panel sections \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
209
|
-
.lp-sec{border-bottom:1px solid var(--border);flex-shrink:0}
|
|
331
|
+
.lp-sec{border-bottom:1px solid var(--border);flex-shrink:0;padding: 24px;}
|
|
332
|
+
.lp-sec-no-border{border-bottom:none!important}
|
|
210
333
|
.lp-sec-hd{
|
|
211
|
-
padding:
|
|
212
|
-
|
|
334
|
+
padding:10px 12px 8px;font-size:14px;font-weight:600;
|
|
335
|
+
color:#404040;display:flex;align-items:center;justify-content:space-between;gap:5px
|
|
213
336
|
}
|
|
214
|
-
.lp-sec-hd
|
|
337
|
+
.lp-sec-hd-left{display:flex;align-items:center;gap:5px}
|
|
338
|
+
#active-var-label{display:none;color:var(--accent-txt);font-size:10px;font-weight:500}
|
|
339
|
+
.lp-info-icon{font-size:11px;color:var(--text-3);cursor:default;opacity:.7}
|
|
340
|
+
.lp-add-btn{
|
|
341
|
+
display:none;
|
|
342
|
+
background:none;border:none;color:var(--text);font-size:14px;font-weight:500;
|
|
343
|
+
cursor:pointer;padding:2px 5px;border-radius:4px;transition:all .12s;flex-shrink:0
|
|
344
|
+
}
|
|
345
|
+
.lp-add-btn:hover{background:var(--bg-hover);color:var(--accent-txt)}
|
|
215
346
|
|
|
216
347
|
/* \u2500\u2500 Variation tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
217
|
-
#variation-tabs{padding:
|
|
348
|
+
#variation-tabs{padding:2px 0 6px;display:flex;flex-direction:column; gap:8px;}
|
|
218
349
|
.var-tab{
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
350
|
+
border-radius: 4px;
|
|
351
|
+
border: 1px solid #e5e7eb;
|
|
352
|
+
background:transparent;color:var(--text);
|
|
353
|
+
cursor:pointer;font-size:12px;font-weight:500;padding:6px 12px;transition:background .12s,color .12s;
|
|
354
|
+
width:100%;text-align:left;display:flex;align-items:center;gap:8px;
|
|
355
|
+
}
|
|
356
|
+
.var-tab:hover{background:var(--bg-hover);color:var(--text)}
|
|
357
|
+
.var-tab.active{background:var(--accent-bg);color:var(--accent-txt);font-weight: 700;}
|
|
358
|
+
.var-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
|
359
|
+
.var-add-row{
|
|
360
|
+
display:none!important;
|
|
361
|
+
border-radius: 4px;
|
|
362
|
+
border: 1px solid #e5e7eb;
|
|
363
|
+
background:transparent;color:var(--text);
|
|
364
|
+
cursor:pointer;font-size:12px;padding:6px 12px;
|
|
365
|
+
width:100%;text-align:left;display:flex;align-items:center;gap:8px;
|
|
366
|
+
border-radius:0;transition:color .12s
|
|
222
367
|
}
|
|
223
|
-
.var-
|
|
224
|
-
.var-tab.active{background:var(--accent);border-color:var(--accent);color:#fff;box-shadow:0 1px 4px rgba(99,102,241,.3)}
|
|
368
|
+
.var-add-row:hover{color:var(--accent-txt)}
|
|
225
369
|
|
|
226
370
|
/* \u2500\u2500 Search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
227
371
|
#comp-search{
|
|
@@ -234,11 +378,12 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
234
378
|
/* \u2500\u2500 Left tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
235
379
|
.lp-tabs{display:flex;border-bottom:1px solid var(--border);flex-shrink:0;background:#fff}
|
|
236
380
|
.lp-tab{
|
|
237
|
-
flex:1;padding:8px
|
|
238
|
-
cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;font-weight:600
|
|
381
|
+
flex:1;padding:8px 2px;text-align:center;font-size:10px;color:var(--text-3);
|
|
382
|
+
cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;font-weight:600;line-height:1.15
|
|
239
383
|
}
|
|
240
384
|
.lp-tab:hover{color:var(--text-2)}
|
|
241
385
|
.lp-tab.active{color:var(--accent-txt);border-bottom-color:var(--accent)}
|
|
386
|
+
.future-hidden{display:none!important}
|
|
242
387
|
|
|
243
388
|
/* \u2500\u2500 Left panel body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
244
389
|
.lp-body{flex:1;overflow-y:auto}
|
|
@@ -341,8 +486,9 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
341
486
|
.adv-key{font-size:10px;color:var(--text-3);margin-bottom:3px;font-weight:700;text-transform:uppercase;letter-spacing:.05em}
|
|
342
487
|
.adv-val{font-size:11px;color:var(--text-2);word-break:break-all;background:var(--bg-sub);padding:5px 7px;border-radius:5px;font-family:monospace;border:1px solid var(--border)}
|
|
343
488
|
|
|
344
|
-
/* \u2500\u2500 Selected element ring \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
345
|
-
|
|
489
|
+
/* \u2500\u2500 Selected element ring + drag affordance \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
490
|
+
/* Selection chrome is injected into the iframe (see injectIframeSelectionStyles); rules here are fallback only */
|
|
491
|
+
.vve-dragging{opacity:0.92!important;outline:2px dashed #f59e0b!important;outline-offset:2px!important;cursor:grabbing!important}
|
|
346
492
|
|
|
347
493
|
/* iframe is always interactive \u2014 mode behaviour is controlled by JS handlers */
|
|
348
494
|
#iframeId{pointer-events:all}
|
|
@@ -411,11 +557,13 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
411
557
|
transition:all .15s
|
|
412
558
|
}
|
|
413
559
|
#states-clear:hover{border-color:#fca5a5;color:#ef4444;background:#fef2f2}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
560
|
+
#history-clear{
|
|
561
|
+
display:block;width:calc(100% - 24px);margin:10px 12px;
|
|
562
|
+
background:none;border:1px solid var(--border);border-radius:6px;
|
|
563
|
+
padding:6px;font-size:11px;color:var(--text-2);cursor:pointer;font-family:inherit;
|
|
564
|
+
transition:all .15s
|
|
565
|
+
}
|
|
566
|
+
#history-clear:hover{border-color:#fca5a5;color:#ef4444;background:#fef2f2}
|
|
419
567
|
</style>
|
|
420
568
|
</head>
|
|
421
569
|
<body class="mode-editor">
|
|
@@ -423,47 +571,68 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
423
571
|
|
|
424
572
|
<!-- \u2500\u2500 Toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->
|
|
425
573
|
<div id="toolbar">
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
<div class="tb-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
<
|
|
433
|
-
<
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
<i class="bi bi-cursor"></i> Navigate
|
|
437
|
-
</button>
|
|
574
|
+
|
|
575
|
+
<!-- Left: Logo + Breadcrumb -->
|
|
576
|
+
<div class="tb-left">
|
|
577
|
+
<span class="tb-logo">\u2733</span>
|
|
578
|
+
<span class="tb-bc-item" id="tb-exp-name">Visual Editor</span>
|
|
579
|
+
<span class="tb-bc-sep">/</span>
|
|
580
|
+
<span class="tb-draft-badge">
|
|
581
|
+
<span class="tb-draft-dot"></span>
|
|
582
|
+
<span class="tb-draft-lbl">DRAFT</span>
|
|
583
|
+
</span>
|
|
438
584
|
</div>
|
|
439
585
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
<i class="bi bi-tablet"></i>
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
586
|
+
<!-- Center: Viewport + Device icons + More + Undo/Redo -->
|
|
587
|
+
<div class="tb-center">
|
|
588
|
+
<div class="tb-viewport">
|
|
589
|
+
<span id="dev-label">1440px</span>
|
|
590
|
+
<i class="bi bi-chevron-down"></i>
|
|
591
|
+
</div>
|
|
592
|
+
<div class="tb-dev-btns">
|
|
593
|
+
<button class="tb-dk-btn active" id="dev-desktop" onclick="setDevice('desktop')" title="Desktop \u2014 full width"><i class="bi bi-display"></i></button>
|
|
594
|
+
<button class="tb-dk-btn" id="dev-tablet" onclick="setDevice('tablet')" title="Tablet \u2014 768px"><i class="bi bi-tablet"></i></button>
|
|
595
|
+
<button class="tb-dk-btn" id="dev-mobile" onclick="setDevice('mobile')" title="Mobile \u2014 390px"><i class="bi bi-phone"></i></button>
|
|
596
|
+
<button class="tb-dk-btn" title="More options"><i class="bi bi-three-dots"></i></button>
|
|
597
|
+
</div>
|
|
598
|
+
<button class="tb-dk-btn" id="btn-undo" title="Undo (\u2318Z)"><i class="bi bi-arrow-counterclockwise"></i></button>
|
|
599
|
+
<button class="tb-dk-btn" id="btn-redo" title="Redo (\u2318\u21E7Z)"><i class="bi bi-arrow-clockwise"></i></button>
|
|
453
600
|
</div>
|
|
454
|
-
<span id="dev-label" style="font-size:11px;color:var(--text-3);min-width:56px">Desktop</span>
|
|
455
601
|
|
|
456
|
-
<div class="tb-
|
|
457
|
-
|
|
458
|
-
|
|
602
|
+
<div id="iframe-loading-toolbar" class="tb-page-loading" aria-live="polite" aria-atomic="true">
|
|
603
|
+
<span class="ve-page-loading-msg">
|
|
604
|
+
<span class="ve-pl-dot" aria-hidden="true">\u25CF</span>
|
|
605
|
+
<span>Page Loading. Content may shift</span>
|
|
606
|
+
<span class="ve-pl-tip" tabindex="0" aria-label="More about page loading">
|
|
607
|
+
<i class="bi bi-info-circle" aria-hidden="true"></i>
|
|
608
|
+
<span class="ve-pl-tooltip" role="tooltip">
|
|
609
|
+
<span class="ve-pl-tip-hd">Heads up! The page is still loading.</span>
|
|
610
|
+
<span class="ve-pl-tip-body">You can already start, but we recommend waiting a moment so you don't have to redo your edits.</span>
|
|
611
|
+
</span>
|
|
612
|
+
</span>
|
|
613
|
+
</span>
|
|
614
|
+
</div>
|
|
459
615
|
|
|
460
|
-
<!--
|
|
461
|
-
<
|
|
462
|
-
|
|
463
|
-
|
|
616
|
+
<!-- Right: Save status + Mode icons + Simulate + Finalize -->
|
|
617
|
+
<div class="tb-right">
|
|
618
|
+
<div class="tb-save-area">
|
|
619
|
+
<span id="dirty-dot" title="Unsaved changes"></span>
|
|
620
|
+
<span class="tb-save-txt"></span>
|
|
621
|
+
<span id="tb-save-time" class="tb-save-time"></span>
|
|
622
|
+
</div>
|
|
623
|
+
<div class="tb-dev-3btns">
|
|
624
|
+
<button class="tb-dk-btn active" id="btn-mode-editor" onclick="setMode('editor')" title="Edit mode \u2014 click elements to edit"><i class="bi bi-pencil"></i></button>
|
|
625
|
+
<button class="tb-dk-btn" id="btn-mode-nav" onclick="setMode('navigate')" title="Navigate mode \u2014 interact with page normally"><i class="bi bi-magic"></i></button>
|
|
626
|
+
<button class="tb-dk-btn" title="Comments"><i class="bi bi-chat-dots"></i></button>
|
|
627
|
+
</div>
|
|
628
|
+
<!-- btn-close: hidden visually, kept for JS event listener -->
|
|
629
|
+
<button id="btn-close" style="display:none" title="Close editor"></button>
|
|
630
|
+
<button class="tb-sim-btn" id="btn-simulate"><i class="bi bi-lightning-charge-fill"></i> Simulate</button>
|
|
631
|
+
<button class="tb-fin-btn" id="btn-save">Finalize</button>
|
|
632
|
+
</div>
|
|
464
633
|
|
|
465
|
-
|
|
466
|
-
<
|
|
634
|
+
<!-- url-bar: hidden, kept for JS compatibility -->
|
|
635
|
+
<div id="url-bar" style="display:none" title="">No page loaded</div>
|
|
467
636
|
</div>
|
|
468
637
|
|
|
469
638
|
<!-- \u2500\u2500 Main \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->
|
|
@@ -475,27 +644,51 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
475
644
|
<!-- Variations -->
|
|
476
645
|
<div class="lp-sec">
|
|
477
646
|
<div class="lp-sec-hd">
|
|
478
|
-
Variations
|
|
479
|
-
<
|
|
647
|
+
<span class="lp-sec-hd-left">Variations <span id="active-var-label"></span></span>
|
|
648
|
+
<button class="lp-add-btn" title="Add variation">+ Add</button>
|
|
480
649
|
</div>
|
|
481
650
|
<div id="variation-tabs"></div>
|
|
482
651
|
</div>
|
|
483
652
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
653
|
+
<div id="iframe-loading-sidebar" class="lp-loading-banner" aria-live="polite" aria-atomic="true">
|
|
654
|
+
<span class="ve-page-loading-msg">
|
|
655
|
+
<span class="ve-pl-dot" aria-hidden="true">\u25CF</span>
|
|
656
|
+
<span>Page Loading. Content may shift</span>
|
|
657
|
+
<span class="ve-pl-tip ve-pl-tip-below" tabindex="0" aria-label="More about page loading">
|
|
658
|
+
<i class="bi bi-info-circle" aria-hidden="true"></i>
|
|
659
|
+
<span class="ve-pl-tooltip" role="tooltip">
|
|
660
|
+
<span class="ve-pl-tip-hd">Heads up! The page is still loading.</span>
|
|
661
|
+
<span class="ve-pl-tip-body">You can already start, but we recommend waiting a moment so you don't have to redo your edits.</span>
|
|
662
|
+
</span>
|
|
663
|
+
</span>
|
|
664
|
+
</span>
|
|
665
|
+
</div>
|
|
666
|
+
|
|
667
|
+
<!-- Elements -->
|
|
668
|
+
<div class="lp-sec lp-sec-no-border">
|
|
669
|
+
<div class="lp-sec-hd">
|
|
670
|
+
<span class="lp-sec-hd-left">Elements <i class="bi bi-info-circle lp-info-icon" title="Page elements"></i></span>
|
|
671
|
+
<button class="lp-add-btn" title="Add element">+ Add</button>
|
|
672
|
+
</div>
|
|
673
|
+
</div>
|
|
674
|
+
|
|
675
|
+
<!-- Search (hidden, kept for JS) -->
|
|
676
|
+
<div style="display:none">
|
|
677
|
+
<input type="search" id="comp-search" placeholder="Search layers\u2026" autocomplete="off">
|
|
487
678
|
</div>
|
|
488
679
|
|
|
489
|
-
<!-- Tabs -->
|
|
490
|
-
<div class="lp-tabs" style="
|
|
491
|
-
<div class="lp-tab active" onclick="switchLeftTab('
|
|
492
|
-
<div class="lp-tab" onclick="switchLeftTab('
|
|
680
|
+
<!-- Tabs (hidden, kept for JS) -->
|
|
681
|
+
<div class="lp-tabs" style="display:none">
|
|
682
|
+
<div class="lp-tab active" onclick="switchLeftTab('elements')">Elements</div>
|
|
683
|
+
<div class="lp-tab future-hidden" onclick="switchLeftTab('components')">Components</div>
|
|
684
|
+
<div class="lp-tab future-hidden" onclick="switchLeftTab('sections')">Sections</div>
|
|
493
685
|
</div>
|
|
494
686
|
|
|
495
687
|
<!-- Tab content -->
|
|
496
688
|
<div class="lp-body">
|
|
497
|
-
<div id="tab-
|
|
498
|
-
<div id="tab-
|
|
689
|
+
<div id="tab-elements" class="tab-pane active"><div id="dom-tree-root" class="dt-tree"></div></div>
|
|
690
|
+
<div id="tab-components" class="tab-pane future-hidden"></div>
|
|
691
|
+
<div id="tab-sections" class="tab-pane future-hidden"></div>
|
|
499
692
|
</div>
|
|
500
693
|
|
|
501
694
|
</div><!-- #left-panel -->
|
|
@@ -503,10 +696,17 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
503
696
|
<!-- Center / iframe panel -->
|
|
504
697
|
<div id="iframe-panel">
|
|
505
698
|
|
|
506
|
-
<!--
|
|
507
|
-
<div id="
|
|
508
|
-
<
|
|
509
|
-
<
|
|
699
|
+
<!-- Floating toolbar for selected element (positioned over iframe) -->
|
|
700
|
+
<div id="selection-floater" aria-label="Selection actions">
|
|
701
|
+
<button type="button" class="sf-btn" id="sf-drag" title="Move up/down (drag on page after activating)"><i class="bi bi-arrows-move"></i></button>
|
|
702
|
+
<span class="sf-sep"></span>
|
|
703
|
+
<button type="button" class="sf-btn" id="sf-resize" disabled title="Resize (coming soon)"><i class="bi bi-arrows-angle-expand"></i></button>
|
|
704
|
+
<button type="button" class="sf-btn" id="sf-rotate" disabled title="Rotate (coming soon)"><i class="bi bi-arrow-repeat"></i></button>
|
|
705
|
+
<button type="button" class="sf-btn" id="sf-dup" title="Duplicate"><i class="bi bi-files"></i></button>
|
|
706
|
+
<button type="button" class="sf-btn" id="sf-hide" title="Hide"><i class="bi bi-eye-slash"></i></button>
|
|
707
|
+
<button type="button" class="sf-btn" id="sf-del" title="Delete"><i class="bi bi-trash"></i></button>
|
|
708
|
+
<span class="sf-sep"></span>
|
|
709
|
+
<button type="button" class="sf-btn" id="sf-close" title="Deselect"><i class="bi bi-x-lg"></i></button>
|
|
510
710
|
</div>
|
|
511
711
|
|
|
512
712
|
<!-- No-URL overlay (absolute, covers center) -->
|
|
@@ -621,12 +821,13 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
621
821
|
</div>
|
|
622
822
|
</div><!-- #tab-states -->
|
|
623
823
|
|
|
624
|
-
<!-- \u2500\u2500 History pane (
|
|
824
|
+
<!-- \u2500\u2500 History pane (saved DB changesets for active variation) \u2500\u2500 -->
|
|
625
825
|
<div id="tab-history" class="rp-pane">
|
|
626
|
-
<div
|
|
627
|
-
<
|
|
628
|
-
|
|
629
|
-
|
|
826
|
+
<div id="history-list">
|
|
827
|
+
<div class="states-empty">
|
|
828
|
+
<i class="bi bi-clock-history"></i>
|
|
829
|
+
No saved changesets for this variation
|
|
830
|
+
</div>
|
|
630
831
|
</div>
|
|
631
832
|
</div><!-- #tab-history -->
|
|
632
833
|
|
|
@@ -649,14 +850,32 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
649
850
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/inputs.js"></script>
|
|
650
851
|
<!-- components.js defines shared colour-class arrays used by bootstrap5/widgets components -->
|
|
651
852
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/components.js"></script>
|
|
853
|
+
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/components-html.js"></script>
|
|
652
854
|
<script>
|
|
653
855
|
/* Safety stub: if components.js didn't define these, create empty arrays so
|
|
654
856
|
components-bootstrap5.js doesn't throw ReferenceError on load. */
|
|
655
|
-
if (typeof bgcolorClasses
|
|
656
|
-
if (typeof colorClasses
|
|
657
|
-
if (typeof textColorClasses=== 'undefined') window.textColorClasses= [];
|
|
658
|
-
if (typeof borderClasses
|
|
659
|
-
if (typeof sizeClasses
|
|
857
|
+
if (typeof bgcolorClasses === 'undefined') window.bgcolorClasses = [];
|
|
858
|
+
if (typeof colorClasses === 'undefined') window.colorClasses = [];
|
|
859
|
+
if (typeof textColorClasses === 'undefined') window.textColorClasses = [];
|
|
860
|
+
if (typeof borderClasses === 'undefined') window.borderClasses = [];
|
|
861
|
+
if (typeof sizeClasses === 'undefined') window.sizeClasses = [];
|
|
862
|
+
if (typeof bgcolorSelectOptions === 'undefined') window.bgcolorSelectOptions = [];
|
|
863
|
+
if (typeof colorSelectOptions === 'undefined') window.colorSelectOptions = [];
|
|
864
|
+
if (typeof textColorSelectOptions=== 'undefined') window.textColorSelectOptions= [];
|
|
865
|
+
if (typeof borderSelectOptions === 'undefined') window.borderSelectOptions = [];
|
|
866
|
+
if (typeof sizeSelectOptions === 'undefined') window.sizeSelectOptions = [];
|
|
867
|
+
if (window.Vvveb && window.Vvveb.Components) {
|
|
868
|
+
if (!window.Vvveb.ComponentsGroup) window.Vvveb.ComponentsGroup = {};
|
|
869
|
+
if (!window.Vvveb.ComponentsGroup['Bootstrap 5']) window.Vvveb.ComponentsGroup['Bootstrap 5'] = [];
|
|
870
|
+
try {
|
|
871
|
+
var baseExists =
|
|
872
|
+
window.Vvveb.Components._components &&
|
|
873
|
+
Object.prototype.hasOwnProperty.call(window.Vvveb.Components._components, '_base');
|
|
874
|
+
if (!baseExists && typeof window.Vvveb.Components.add === 'function') {
|
|
875
|
+
window.Vvveb.Components.add('_base', { name: 'Base', properties: [] });
|
|
876
|
+
}
|
|
877
|
+
} catch(_) {}
|
|
878
|
+
}
|
|
660
879
|
</script>
|
|
661
880
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/components-bootstrap5.js"></script>
|
|
662
881
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/components-widgets.js"></script>
|
|
@@ -708,26 +927,143 @@ var experimentData = null;
|
|
|
708
927
|
var variations = [];
|
|
709
928
|
var activeVarId = null;
|
|
710
929
|
var varHtmlCache = {};
|
|
930
|
+
/** Per-variation chain rows from structural actions (insert/duplicate/delete/reorder/hide), merged on Finalize. */
|
|
931
|
+
var sessionStructuralChainRowsByVarId = {};
|
|
932
|
+
/** Last iframe proxy URL we navigated to \u2014 used to skip redundant reloads when parent re-sends load-experiment */
|
|
933
|
+
var lastLoadedProxyUrl = '';
|
|
934
|
+
/** API changeset rows (excluding __vvveb_body__) reapplied until selectors match late-hydrated DOM */
|
|
935
|
+
var pendingGranularChangesets = null;
|
|
936
|
+
var pendingGranularVarId = null;
|
|
937
|
+
var granularReapplyTimer = null;
|
|
938
|
+
var granularReapplyAttempts = 0;
|
|
939
|
+
var GRANULAR_REAPPLY_MAX = 80;
|
|
940
|
+
/** Bumped on each iframe navigation so stale apply timers exit */
|
|
941
|
+
var iframeContentNavGen = 0;
|
|
942
|
+
var iframeContentApplyTimer = null;
|
|
943
|
+
var iframeEarlyGranularPrimedForGen = null;
|
|
944
|
+
var iframeEarlySyncPrimedForGen = null;
|
|
945
|
+
/** insert/reorder entries are applied from early granular + full apply \u2014 skip exact duplicates per iframe nav */
|
|
946
|
+
var appliedStructuralChangesetKeys = {};
|
|
711
947
|
var isDirty = false;
|
|
712
948
|
var vvvebReady = false;
|
|
713
949
|
var currentMode = 'editor';
|
|
714
950
|
var currentDevice = 'desktop';
|
|
715
951
|
var selectedEl = null;
|
|
952
|
+
var suppressClickUntil = 0;
|
|
953
|
+
var dragAttachDoc = null;
|
|
716
954
|
var currentMainTab = 'design';
|
|
955
|
+
var currentLeftTab = 'elements';
|
|
956
|
+
var dragHandleActive = false;
|
|
957
|
+
var domTreeCollapsed = {};
|
|
958
|
+
var domTreeRefreshTimer = null;
|
|
959
|
+
var iframeSyncRetryTimer = null;
|
|
960
|
+
var iframeSyncAttempts = 0;
|
|
961
|
+
var selectionScrollWin = null;
|
|
962
|
+
var selectionResizeBound = false;
|
|
963
|
+
var clickAttachDoc = null;
|
|
964
|
+
var changeObserver = null;
|
|
965
|
+
var changeObserverDoc = null;
|
|
966
|
+
/** Incremented while applying changesets / selection chrome so MutationObserver does not mark dirty */
|
|
967
|
+
var suppressIframeMutationDirty = 0;
|
|
968
|
+
/** { doc, onRS } \u2014 iframe document readystate until complete */
|
|
969
|
+
var iframeDocLoadingListeners = null;
|
|
717
970
|
// Each entry: {selector, label, cssProp, value, targetEl}
|
|
718
971
|
// cssProp is null for non-CSS attributes (href, alt, classes\u2026)
|
|
719
972
|
var stateChanges = [];
|
|
973
|
+
/** Pre-apply DOM snapshots for granular + body changesets (used when removing History rows) */
|
|
974
|
+
var appliedChangesetSnapshots = {};
|
|
975
|
+
/** Canonical JSON fingerprints of persisted changesets per variation (last load / finalize) */
|
|
976
|
+
var baselineChangesetsByVarId = {};
|
|
977
|
+
|
|
978
|
+
// \u2500\u2500 Dirty tracking (compare DB baseline + session stateChanges vs current export) \u2500\u2500
|
|
979
|
+
function beginSuppressIframeMutationDirty() {
|
|
980
|
+
suppressIframeMutationDirty += 1;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function endSuppressIframeMutationDirty() {
|
|
984
|
+
suppressIframeMutationDirty = Math.max(0, suppressIframeMutationDirty - 1);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/** Stable stringify of a variation's changesets field (string or array from API). */
|
|
988
|
+
function fingerprintChangesetsField(raw) {
|
|
989
|
+
if (raw == null) return '[]';
|
|
990
|
+
if (Array.isArray(raw)) {
|
|
991
|
+
try {
|
|
992
|
+
return JSON.stringify(raw);
|
|
993
|
+
} catch(_) {
|
|
994
|
+
return '[]';
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
if (typeof raw !== 'string') return '[]';
|
|
998
|
+
var s = raw.trim();
|
|
999
|
+
if (!s) return '[]';
|
|
1000
|
+
try {
|
|
1001
|
+
var p = JSON.parse(s);
|
|
1002
|
+
return JSON.stringify(Array.isArray(p) ? p : []);
|
|
1003
|
+
} catch(_) {
|
|
1004
|
+
return '[]';
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function captureBaselineFromVariations(list) {
|
|
1009
|
+
baselineChangesetsByVarId = {};
|
|
1010
|
+
if (!list || !list.length) return;
|
|
1011
|
+
for (var i = 0; i < list.length; i++) {
|
|
1012
|
+
var v = list[i];
|
|
1013
|
+
if (!v || !v._id) continue;
|
|
1014
|
+
baselineChangesetsByVarId[v._id] = fingerprintChangesetsField(v.changesets);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/** Fingerprint of what Finalize would send for this variation (matches buildPersistedChainSetsForVariation). */
|
|
1019
|
+
function persistedExportFingerprintForVariation(v) {
|
|
1020
|
+
if (!v || !v._id) return '[]';
|
|
1021
|
+
try {
|
|
1022
|
+
var rows = buildPersistedChainSetsForVariation(v);
|
|
1023
|
+
return JSON.stringify(rows || []);
|
|
1024
|
+
} catch(_) {
|
|
1025
|
+
return '[]';
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
720
1028
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
isDirty =
|
|
724
|
-
document.getElementById('dirty-dot')
|
|
725
|
-
|
|
1029
|
+
function setEditorDirty(dirty) {
|
|
1030
|
+
var was = isDirty;
|
|
1031
|
+
isDirty = !!dirty;
|
|
1032
|
+
var dot = document.getElementById('dirty-dot');
|
|
1033
|
+
if (dot) dot.classList.toggle('on', isDirty);
|
|
1034
|
+
if (isDirty && !was) send('mutations-changed', {});
|
|
1035
|
+
if (!isDirty && was) send('editor-dirty', { dirty: false });
|
|
1036
|
+
if (!isDirty) {
|
|
1037
|
+
savedAt = Date.now();
|
|
1038
|
+
updateSaveTime();
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function recomputeEditorDirty() {
|
|
1043
|
+
var d = stateChanges.length > 0;
|
|
1044
|
+
if (!d && variations && variations.length) {
|
|
1045
|
+
for (var i = 0; i < variations.length; i++) {
|
|
1046
|
+
var v = variations[i];
|
|
1047
|
+
var vid = v._id;
|
|
1048
|
+
var cur = persistedExportFingerprintForVariation(v);
|
|
1049
|
+
var base = baselineChangesetsByVarId[vid];
|
|
1050
|
+
if (base == null) base = '[]';
|
|
1051
|
+
if (cur !== base) {
|
|
1052
|
+
d = true;
|
|
1053
|
+
break;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
setEditorDirty(d);
|
|
726
1058
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
document.getElementById('
|
|
1059
|
+
var savedAt = null;
|
|
1060
|
+
function updateSaveTime() {
|
|
1061
|
+
var el = document.getElementById('tb-save-time');
|
|
1062
|
+
if (!el || !savedAt) return;
|
|
1063
|
+
var s = Math.floor((Date.now() - savedAt) / 1000);
|
|
1064
|
+
el.textContent = s < 60 ? s + 's ago' : Math.floor(s / 60) + 'm ago';
|
|
730
1065
|
}
|
|
1066
|
+
setInterval(updateSaveTime, 10000);
|
|
731
1067
|
|
|
732
1068
|
// \u2500\u2500 Mode toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
733
1069
|
function setMode(mode) {
|
|
@@ -735,11 +1071,15 @@ function setMode(mode) {
|
|
|
735
1071
|
document.body.className = 'mode-' + mode;
|
|
736
1072
|
document.getElementById('btn-mode-editor').classList.toggle('active', mode === 'editor');
|
|
737
1073
|
document.getElementById('btn-mode-nav').classList.toggle('active', mode === 'navigate');
|
|
738
|
-
if (mode === 'navigate')
|
|
1074
|
+
if (mode === 'navigate') {
|
|
1075
|
+
setDragHandleActive(false);
|
|
1076
|
+
deselectElement();
|
|
1077
|
+
}
|
|
1078
|
+
updateSelectionToolbar();
|
|
739
1079
|
}
|
|
740
1080
|
|
|
741
1081
|
// \u2500\u2500 Device toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
742
|
-
var DEVICE_LABELS = { desktop: '
|
|
1082
|
+
var DEVICE_LABELS = { desktop: '1440px', tablet: '768px', mobile: '390px' };
|
|
743
1083
|
function setDevice(device) {
|
|
744
1084
|
currentDevice = device;
|
|
745
1085
|
var frame = document.getElementById('device-frame');
|
|
@@ -752,11 +1092,25 @@ function setDevice(device) {
|
|
|
752
1092
|
|
|
753
1093
|
// \u2500\u2500 Left panel tab switch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
754
1094
|
function switchLeftTab(tab) {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1095
|
+
currentLeftTab = tab;
|
|
1096
|
+
var tabs = document.querySelectorAll('.lp-tab');
|
|
1097
|
+
tabs[0].classList.toggle('active', tab === 'elements');
|
|
1098
|
+
tabs[1].classList.toggle('active', tab === 'components');
|
|
1099
|
+
tabs[2].classList.toggle('active', tab === 'sections');
|
|
1100
|
+
document.getElementById('tab-elements').classList.toggle('active', tab === 'elements');
|
|
758
1101
|
document.getElementById('tab-components').classList.toggle('active', tab === 'components');
|
|
759
1102
|
document.getElementById('tab-sections').classList.toggle('active', tab === 'sections');
|
|
1103
|
+
var inp = document.getElementById('comp-search');
|
|
1104
|
+
if (tab === 'elements') {
|
|
1105
|
+
inp.placeholder = 'Search layers\u2026';
|
|
1106
|
+
renderDomTree(inp.value);
|
|
1107
|
+
} else if (tab === 'sections') {
|
|
1108
|
+
inp.placeholder = 'Search sections\u2026';
|
|
1109
|
+
renderSidebar(inp.value);
|
|
1110
|
+
} else {
|
|
1111
|
+
inp.placeholder = 'Search components\u2026';
|
|
1112
|
+
renderSidebar(inp.value);
|
|
1113
|
+
}
|
|
760
1114
|
}
|
|
761
1115
|
|
|
762
1116
|
// \u2500\u2500 Accordion toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -776,6 +1130,7 @@ function switchMainTab(tab) {
|
|
|
776
1130
|
if (pane) pane.classList.toggle('active', t === tab);
|
|
777
1131
|
});
|
|
778
1132
|
if (tab === 'states') renderStatesTab();
|
|
1133
|
+
if (tab === 'history') renderHistoryTab();
|
|
779
1134
|
}
|
|
780
1135
|
|
|
781
1136
|
// \u2500\u2500 Change log (States tab) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -885,6 +1240,7 @@ function logChange(selector, inputId, value, targetEl, originalValue) {
|
|
|
885
1240
|
if (idx >= 0) { stateChanges[idx] = entry; } else { stateChanges.push(entry); }
|
|
886
1241
|
}
|
|
887
1242
|
if (currentMainTab === 'states') renderStatesTab();
|
|
1243
|
+
recomputeEditorDirty();
|
|
888
1244
|
}
|
|
889
1245
|
|
|
890
1246
|
function renderStatesTab() {
|
|
@@ -918,7 +1274,7 @@ function renderStatesTab() {
|
|
|
918
1274
|
|
|
919
1275
|
// Resolve a live DOM element for a state-change entry.
|
|
920
1276
|
// Tries the stored direct reference first; if it's detached or missing,
|
|
921
|
-
// falls back to querySelector(
|
|
1277
|
+
// falls back to querySelector (with .vve-* class stripped) inside the iframe document.
|
|
922
1278
|
function resolveChangeEl(change) {
|
|
923
1279
|
try {
|
|
924
1280
|
var iframeDoc = document.getElementById('iframeId').contentDocument;
|
|
@@ -928,7 +1284,7 @@ function resolveChangeEl(change) {
|
|
|
928
1284
|
return change.targetEl;
|
|
929
1285
|
}
|
|
930
1286
|
// Fallback: re-query by the stored CSS selector
|
|
931
|
-
return iframeDoc
|
|
1287
|
+
return querySelectorResolved(iframeDoc, change.selector);
|
|
932
1288
|
} catch (e) {
|
|
933
1289
|
console.warn('[V2] resolveChangeEl:', e);
|
|
934
1290
|
return null;
|
|
@@ -991,7 +1347,7 @@ function removeStateChange(idx) {
|
|
|
991
1347
|
syncDesignInput(change);
|
|
992
1348
|
stateChanges.splice(idx, 1);
|
|
993
1349
|
renderStatesTab();
|
|
994
|
-
|
|
1350
|
+
recomputeEditorDirty();
|
|
995
1351
|
}
|
|
996
1352
|
|
|
997
1353
|
function clearAllStates() {
|
|
@@ -1001,58 +1357,663 @@ function clearAllStates() {
|
|
|
1001
1357
|
});
|
|
1002
1358
|
stateChanges = [];
|
|
1003
1359
|
renderStatesTab();
|
|
1004
|
-
|
|
1360
|
+
recomputeEditorDirty();
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// \u2500\u2500 History tab (saved changesets from DB for active variation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1364
|
+
function getActiveVariationForHistory() {
|
|
1365
|
+
return variations.find(function(v) { return v._id === activeVarId; });
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
function persistActiveVariationChangesets(arr) {
|
|
1369
|
+
var v = variations.find(function(x) { return x._id === activeVarId; });
|
|
1370
|
+
if (!v) return;
|
|
1371
|
+
var json = JSON.stringify(arr);
|
|
1372
|
+
v.changesets = json;
|
|
1373
|
+
if (experimentData && Array.isArray(experimentData.variations)) {
|
|
1374
|
+
for (var i = 0; i < experimentData.variations.length; i++) {
|
|
1375
|
+
if (experimentData.variations[i]._id === activeVarId) {
|
|
1376
|
+
experimentData.variations[i].changesets = json;
|
|
1377
|
+
break;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
function entrySnapshotKey(entry) {
|
|
1384
|
+
if (!entry || !entry.selector) return '';
|
|
1385
|
+
var selKey = sanitizeSelectorForMatch(entry.selector) || entry.selector;
|
|
1386
|
+
return (
|
|
1387
|
+
selKey +
|
|
1388
|
+
'\0' +
|
|
1389
|
+
normalizeChangesetType(entry) +
|
|
1390
|
+
'\0' +
|
|
1391
|
+
String(entry.property || '') +
|
|
1392
|
+
'\0' +
|
|
1393
|
+
String(entry.attribute || '') +
|
|
1394
|
+
'\0' +
|
|
1395
|
+
String(entry.action || '') +
|
|
1396
|
+
'\0' +
|
|
1397
|
+
String(entry.html != null ? 'h' + String(entry.html).length : '') +
|
|
1398
|
+
'\0' +
|
|
1399
|
+
String(entry.value != null ? 'v:' + entry.value : '')
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
function captureChangesetSnapshotBeforeApply(entry, el, iframeDoc) {
|
|
1404
|
+
if (!entry || !el || entry.selector === '__vvveb_body__') return;
|
|
1405
|
+
var k = entrySnapshotKey(entry);
|
|
1406
|
+
if (appliedChangesetSnapshots[k]) return;
|
|
1407
|
+
switch (normalizeChangesetType(entry)) {
|
|
1408
|
+
case 'content':
|
|
1409
|
+
if (entry.html != null) {
|
|
1410
|
+
appliedChangesetSnapshots[k] = { kind: 'innerHTML', v: el.innerHTML };
|
|
1411
|
+
} else if (entry.value != null) {
|
|
1412
|
+
appliedChangesetSnapshots[k] = { kind: 'textContent', v: el.textContent };
|
|
1413
|
+
}
|
|
1414
|
+
break;
|
|
1415
|
+
case 'style':
|
|
1416
|
+
if (entry.property) {
|
|
1417
|
+
appliedChangesetSnapshots[k] = { kind: 'styleBlock', v: el.getAttribute('style') || '' };
|
|
1418
|
+
}
|
|
1419
|
+
break;
|
|
1420
|
+
case 'attribute':
|
|
1421
|
+
if (entry.attribute) {
|
|
1422
|
+
appliedChangesetSnapshots[k] = {
|
|
1423
|
+
kind: 'attribute',
|
|
1424
|
+
name: entry.attribute,
|
|
1425
|
+
v: el.getAttribute(entry.attribute),
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
break;
|
|
1429
|
+
case 'remove':
|
|
1430
|
+
appliedChangesetSnapshots[k] = { kind: 'display', v: el.style.display || '' };
|
|
1431
|
+
break;
|
|
1432
|
+
case 'insert':
|
|
1433
|
+
appliedChangesetSnapshots[k] = { kind: 'insert' };
|
|
1434
|
+
break;
|
|
1435
|
+
default:
|
|
1436
|
+
appliedChangesetSnapshots[k] = { kind: 'unknown' };
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
function softReloadEditorIframe() {
|
|
1441
|
+
var iframe = document.getElementById('iframeId');
|
|
1442
|
+
if (!iframe) return;
|
|
1443
|
+
var src = iframe.src || lastLoadedProxyUrl;
|
|
1444
|
+
if (!src || src === 'about:blank') return;
|
|
1445
|
+
var navGen = nextIframeContentNavGen();
|
|
1446
|
+
resetIframeBindings();
|
|
1447
|
+
setIframePageLoadingUi(true);
|
|
1448
|
+
iframe.src = '';
|
|
1449
|
+
iframe.src = appendIframeReloadBust(src);
|
|
1450
|
+
startIframeContentApplyWatcher(navGen);
|
|
1451
|
+
scheduleDomTreeRefresh();
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/** @returns {boolean} true if a full iframe reload was started */
|
|
1455
|
+
function revertChangesetEntryOnDom(entry) {
|
|
1456
|
+
if (!entry) return false;
|
|
1457
|
+
if (entry.selector === '__vvveb_body__') {
|
|
1458
|
+
var iframeDoc0 = document.getElementById('iframeId').contentDocument;
|
|
1459
|
+
if (!iframeDoc0 || !iframeDoc0.body) return false;
|
|
1460
|
+
var bk = '__vvveb_body__|snapshot';
|
|
1461
|
+
var snapBody = appliedChangesetSnapshots[bk];
|
|
1462
|
+
if (snapBody && snapBody.kind === 'bodyHTML') {
|
|
1463
|
+
iframeDoc0.body.innerHTML = snapBody.v;
|
|
1464
|
+
}
|
|
1465
|
+
delete appliedChangesetSnapshots[bk];
|
|
1466
|
+
try {
|
|
1467
|
+
delete varHtmlCache[activeVarId];
|
|
1468
|
+
} catch(_) {}
|
|
1469
|
+
return false;
|
|
1470
|
+
}
|
|
1471
|
+
var iframeDoc = document.getElementById('iframeId').contentDocument;
|
|
1472
|
+
if (!iframeDoc) return false;
|
|
1473
|
+
var k = entrySnapshotKey(entry);
|
|
1474
|
+
var snap = appliedChangesetSnapshots[k];
|
|
1475
|
+
var el = querySelectorResolved(iframeDoc, entry.selector);
|
|
1476
|
+
if (!snap || !el) {
|
|
1477
|
+
softReloadEditorIframe();
|
|
1478
|
+
delete appliedChangesetSnapshots[k];
|
|
1479
|
+
return true;
|
|
1480
|
+
}
|
|
1481
|
+
if (snap.kind === 'innerHTML') el.innerHTML = snap.v;
|
|
1482
|
+
else if (snap.kind === 'textContent') el.textContent = snap.v;
|
|
1483
|
+
else if (snap.kind === 'styleBlock') {
|
|
1484
|
+
if (snap.v) el.setAttribute('style', snap.v);
|
|
1485
|
+
else el.removeAttribute('style');
|
|
1486
|
+
} else if (snap.kind === 'attribute' && snap.name) {
|
|
1487
|
+
if (snap.v == null || snap.v === '') el.removeAttribute(snap.name);
|
|
1488
|
+
else el.setAttribute(snap.name, snap.v);
|
|
1489
|
+
} else if (snap.kind === 'display') el.style.display = snap.v;
|
|
1490
|
+
else {
|
|
1491
|
+
softReloadEditorIframe();
|
|
1492
|
+
delete appliedChangesetSnapshots[k];
|
|
1493
|
+
return true;
|
|
1494
|
+
}
|
|
1495
|
+
delete appliedChangesetSnapshots[k];
|
|
1496
|
+
return false;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
function historyEntryTypeLabel(entry) {
|
|
1500
|
+
if (!entry) return 'Change';
|
|
1501
|
+
if (entry.selector === '__vvveb_body__') return 'Full page HTML';
|
|
1502
|
+
var t = normalizeChangesetType(entry);
|
|
1503
|
+
if (t === 'content') return entry.html != null ? 'Inner HTML' : 'Text / content';
|
|
1504
|
+
if (t === 'style') return 'Style: ' + (entry.property || '');
|
|
1505
|
+
if (t === 'attribute') return 'Attribute: ' + (entry.attribute || '');
|
|
1506
|
+
if (t === 'insert') return 'Insert HTML';
|
|
1507
|
+
if (t === 'remove') return 'Hide element';
|
|
1508
|
+
return t || 'Change';
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function historyEntryValuePreview(entry) {
|
|
1512
|
+
if (!entry) return '';
|
|
1513
|
+
if (entry.selector === '__vvveb_body__') return '(body snapshot)';
|
|
1514
|
+
if (entry.html != null) return String(entry.html).slice(0, 120);
|
|
1515
|
+
if (entry.value != null) return String(entry.value).slice(0, 120);
|
|
1516
|
+
var nt = normalizeChangesetType(entry);
|
|
1517
|
+
if (nt === 'style' || nt === 'attribute') return String(entry.value != null ? entry.value : '').slice(0, 120);
|
|
1518
|
+
return '';
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
function renderHistoryTab() {
|
|
1522
|
+
var container = document.getElementById('history-list');
|
|
1523
|
+
if (!container) return;
|
|
1524
|
+
var v = getActiveVariationForHistory();
|
|
1525
|
+
var arr = v ? parseVariationChangesets(v) : [];
|
|
1526
|
+
if (!arr.length) {
|
|
1527
|
+
container.innerHTML =
|
|
1528
|
+
'<div class="states-empty"><i class="bi bi-clock-history"></i>No saved changesets for this variation \u2014 use Finalize to persist edits to the server</div>';
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
var groups = {};
|
|
1532
|
+
var order = [];
|
|
1533
|
+
for (var gi = 0; gi < arr.length; gi++) {
|
|
1534
|
+
var entry = arr[gi];
|
|
1535
|
+
var sel = entry.selector || '(unknown)';
|
|
1536
|
+
if (!groups[sel]) {
|
|
1537
|
+
groups[sel] = [];
|
|
1538
|
+
order.push(sel);
|
|
1539
|
+
}
|
|
1540
|
+
groups[sel].push({ entry: entry, idx: gi });
|
|
1541
|
+
}
|
|
1542
|
+
var html =
|
|
1543
|
+
'<button type="button" id="history-clear" onclick="clearAllHistoryChangesets()"><i class="bi bi-trash3"></i> Clear all saved changes</button>';
|
|
1544
|
+
order.forEach(function(sel) {
|
|
1545
|
+
html += '<div class="state-group"><div class="state-group-sel">' + esc(sel) + '</div>';
|
|
1546
|
+
groups[sel].forEach(function(item) {
|
|
1547
|
+
var lab = historyEntryTypeLabel(item.entry);
|
|
1548
|
+
var val = historyEntryValuePreview(item.entry);
|
|
1549
|
+
html +=
|
|
1550
|
+
'<div class="state-item">' +
|
|
1551
|
+
'<span class="state-item-idx" style="opacity:0.55;font-size:10px;min-width:2.2em;display:inline-block" title="Order in saved changesets">#' +
|
|
1552
|
+
item.idx +
|
|
1553
|
+
'</span>' +
|
|
1554
|
+
'<span class="state-item-label">' +
|
|
1555
|
+
esc(lab) +
|
|
1556
|
+
'</span>' +
|
|
1557
|
+
'<span class="state-item-val" title="' +
|
|
1558
|
+
esc(val) +
|
|
1559
|
+
'">' +
|
|
1560
|
+
esc(val) +
|
|
1561
|
+
'</span>' +
|
|
1562
|
+
'<button type="button" class="state-remove" title="Remove this saved row (#' +
|
|
1563
|
+
item.idx +
|
|
1564
|
+
')" onclick="removeHistoryChangeset(' +
|
|
1565
|
+
item.idx +
|
|
1566
|
+
')">✕</button>' +
|
|
1567
|
+
'</div>';
|
|
1568
|
+
});
|
|
1569
|
+
html += '</div>';
|
|
1570
|
+
});
|
|
1571
|
+
container.innerHTML = html;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
function changesetListHasStructural(arr) {
|
|
1575
|
+
if (!arr || !arr.length) return false;
|
|
1576
|
+
for (var i = 0; i < arr.length; i++) {
|
|
1577
|
+
var e = arr[i];
|
|
1578
|
+
var t = normalizeChangesetType(e);
|
|
1579
|
+
if (e && (t === 'insert' || t === 'reorder')) return true;
|
|
1580
|
+
}
|
|
1581
|
+
return false;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
function removeHistoryChangeset(idx) {
|
|
1585
|
+
var v = getActiveVariationForHistory();
|
|
1586
|
+
if (!v) return;
|
|
1587
|
+
var arr = parseVariationChangesets(v);
|
|
1588
|
+
if (idx < 0 || idx >= arr.length) return;
|
|
1589
|
+
var removed = arr[idx];
|
|
1590
|
+
arr.splice(idx, 1);
|
|
1591
|
+
persistActiveVariationChangesets(arr);
|
|
1592
|
+
var didReload = revertChangesetEntryOnDom(removed);
|
|
1593
|
+
try {
|
|
1594
|
+
delete varHtmlCache[activeVarId];
|
|
1595
|
+
} catch(_) {}
|
|
1596
|
+
// Re-applying remaining rows on top of current DOM duplicates insert/reorder nodes; reload when any
|
|
1597
|
+
// structural row remains or was removed (revert may already have started a reload for insert/body).
|
|
1598
|
+
var removedType = normalizeChangesetType(removed);
|
|
1599
|
+
var needsStructuralReload =
|
|
1600
|
+
!didReload &&
|
|
1601
|
+
(removedType === 'insert' ||
|
|
1602
|
+
removedType === 'reorder' ||
|
|
1603
|
+
changesetListHasStructural(arr));
|
|
1604
|
+
if (didReload) {
|
|
1605
|
+
/* revertChangesetEntryOnDom already kicked off iframe reload */
|
|
1606
|
+
} else if (needsStructuralReload) {
|
|
1607
|
+
softReloadEditorIframe();
|
|
1608
|
+
} else {
|
|
1609
|
+
try {
|
|
1610
|
+
appliedStructuralChangesetKeys = {};
|
|
1611
|
+
applyActiveVariationHtml();
|
|
1612
|
+
registerPendingGranularChangesets(
|
|
1613
|
+
arr,
|
|
1614
|
+
document.getElementById('iframeId').contentDocument,
|
|
1615
|
+
);
|
|
1616
|
+
saveCurrentVariationHtml();
|
|
1617
|
+
} catch(_) {}
|
|
1618
|
+
}
|
|
1619
|
+
if (currentMainTab === 'history') renderHistoryTab();
|
|
1620
|
+
recomputeEditorDirty();
|
|
1621
|
+
scheduleDomTreeRefresh();
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
function clearAllHistoryChangesets() {
|
|
1625
|
+
var v = getActiveVariationForHistory();
|
|
1626
|
+
if (!v) return;
|
|
1627
|
+
if (!parseVariationChangesets(v).length) return;
|
|
1628
|
+
persistActiveVariationChangesets([]);
|
|
1629
|
+
appliedChangesetSnapshots = {};
|
|
1630
|
+
appliedStructuralChangesetKeys = {};
|
|
1631
|
+
try {
|
|
1632
|
+
delete varHtmlCache[activeVarId];
|
|
1633
|
+
} catch(_) {}
|
|
1634
|
+
if (currentMainTab === 'history') renderHistoryTab();
|
|
1635
|
+
recomputeEditorDirty();
|
|
1636
|
+
scheduleDomTreeRefresh();
|
|
1637
|
+
softReloadEditorIframe();
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// \u2500\u2500 Persisted active variation (survives iframe / full page reload) \u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1641
|
+
/** All Visual Editor iframe keys in localStorage use this prefix \u2014 cleared on close. */
|
|
1642
|
+
var VVE_LOCAL_STORAGE_PREFIX = 'vve:';
|
|
1643
|
+
|
|
1644
|
+
function clearVisualEditorLocalStorage() {
|
|
1645
|
+
try {
|
|
1646
|
+
for (var i = localStorage.length - 1; i >= 0; i--) {
|
|
1647
|
+
var k = localStorage.key(i);
|
|
1648
|
+
if (k && k.indexOf(VVE_LOCAL_STORAGE_PREFIX) === 0) {
|
|
1649
|
+
localStorage.removeItem(k);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
} catch(_) {}
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
function activeVariationStorageKeyFromPayload(data) {
|
|
1656
|
+
return (
|
|
1657
|
+
VVE_LOCAL_STORAGE_PREFIX +
|
|
1658
|
+
'activeVar:' +
|
|
1659
|
+
String((data && data.experimentId) || '') +
|
|
1660
|
+
':' +
|
|
1661
|
+
String((data && data.pageUrl) || '')
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
function readPersistedActiveVariationId(data) {
|
|
1666
|
+
try {
|
|
1667
|
+
var sk = activeVariationStorageKeyFromPayload(data);
|
|
1668
|
+
if (sk === VVE_LOCAL_STORAGE_PREFIX + 'activeVar::') return null;
|
|
1669
|
+
return localStorage.getItem(sk);
|
|
1670
|
+
} catch(_) {
|
|
1671
|
+
return null;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
function writePersistedActiveVariationId(varId) {
|
|
1676
|
+
try {
|
|
1677
|
+
if (!experimentData || !experimentData.experimentId) return;
|
|
1678
|
+
var sk =
|
|
1679
|
+
VVE_LOCAL_STORAGE_PREFIX +
|
|
1680
|
+
'activeVar:' +
|
|
1681
|
+
String(experimentData.experimentId || '') +
|
|
1682
|
+
':' +
|
|
1683
|
+
String(experimentData.pageUrl || '');
|
|
1684
|
+
if (sk === VVE_LOCAL_STORAGE_PREFIX + 'activeVar::') return;
|
|
1685
|
+
if (varId) localStorage.setItem(sk, String(varId));
|
|
1686
|
+
} catch(_) {}
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
/**
|
|
1690
|
+
* @param allowPrevMemory when true, keep in-session activeVarId if still valid (skip-reload path).
|
|
1691
|
+
*/
|
|
1692
|
+
function pickActiveVariationIdForLoad(data, variationsArr, prevMemoryId, allowPrevMemory) {
|
|
1693
|
+
var baseline = variationsArr.find(function(v) { return v.baseline; });
|
|
1694
|
+
var fallback = (baseline || variationsArr[0] || {})._id || null;
|
|
1695
|
+
if (!variationsArr.length) return null;
|
|
1696
|
+
if (allowPrevMemory && prevMemoryId && variationsArr.some(function(v) { return v._id === prevMemoryId; })) {
|
|
1697
|
+
return prevMemoryId;
|
|
1698
|
+
}
|
|
1699
|
+
var stored = readPersistedActiveVariationId(data);
|
|
1700
|
+
if (stored && variationsArr.some(function(v) { return v._id === stored; })) {
|
|
1701
|
+
return stored;
|
|
1702
|
+
}
|
|
1703
|
+
return fallback;
|
|
1005
1704
|
}
|
|
1006
1705
|
|
|
1007
1706
|
// \u2500\u2500 Experiment loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1008
1707
|
function handleLoadExperiment(data) {
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1708
|
+
clearPendingGranularChangesets();
|
|
1709
|
+
var prevKey = experimentData
|
|
1710
|
+
? String(experimentData.experimentId || '') + '|' + String(experimentData.pageUrl || '')
|
|
1711
|
+
: '';
|
|
1712
|
+
var nextKey = String((data && data.experimentId) || '') + '|' + String((data && data.pageUrl) || '');
|
|
1015
1713
|
var pageUrl = data.pageUrl || '';
|
|
1016
1714
|
if (!pageUrl) {
|
|
1017
|
-
document.getElementById('loading').classList.add('hidden');
|
|
1018
1715
|
showNoUrl(true);
|
|
1716
|
+
lastLoadedProxyUrl = '';
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
var proxyUrl = '/api/conversion-proxy?password=' + encodeURIComponent(data.editorPassword || '') +
|
|
1720
|
+
'&url=' + encodeURIComponent(pageUrl);
|
|
1721
|
+
|
|
1722
|
+
// Parent often re-posts load-experiment when React re-renders (new object identity) or
|
|
1723
|
+
// after mutations-changed. Reloading the iframe again wipes variant changesets mid-session.
|
|
1724
|
+
var hadNav = !!lastLoadedProxyUrl;
|
|
1725
|
+
var sameExperimentPage = !!experimentData && prevKey === nextKey;
|
|
1726
|
+
var skipUrlReload = !!(data && data.skipUrlReload);
|
|
1727
|
+
var skipReload = skipUrlReload || (sameExperimentPage && hadNav && lastLoadedProxyUrl === proxyUrl);
|
|
1728
|
+
|
|
1729
|
+
if (skipReload) {
|
|
1730
|
+
experimentData = data;
|
|
1731
|
+
variations = Array.isArray(data.variations) ? data.variations : [];
|
|
1732
|
+
var prevActive = activeVarId;
|
|
1733
|
+
activeVarId = pickActiveVariationIdForLoad(data, variations, prevActive, true);
|
|
1734
|
+
writePersistedActiveVariationId(activeVarId);
|
|
1735
|
+
renderVariationTabs();
|
|
1736
|
+
var urlBarSkip = document.getElementById('url-bar');
|
|
1737
|
+
urlBarSkip.textContent = pageUrl;
|
|
1738
|
+
urlBarSkip.title = pageUrl;
|
|
1739
|
+
try {
|
|
1740
|
+
applyActiveVariationHtml();
|
|
1741
|
+
syncIframeInteractions('load-experiment-skip-url');
|
|
1742
|
+
} catch(_) {}
|
|
1743
|
+
try {
|
|
1744
|
+
var ifrSkip = document.getElementById('iframeId');
|
|
1745
|
+
if (ifrSkip && ifrSkip.contentDocument) attachIframeLoadingUntilComplete(ifrSkip);
|
|
1746
|
+
} catch(_) {}
|
|
1747
|
+
if (currentMainTab === 'history') renderHistoryTab();
|
|
1748
|
+
captureBaselineFromVariations(variations);
|
|
1749
|
+
recomputeEditorDirty();
|
|
1019
1750
|
return;
|
|
1020
1751
|
}
|
|
1752
|
+
|
|
1753
|
+
if (!experimentData || prevKey !== nextKey) {
|
|
1754
|
+
varHtmlCache = {};
|
|
1755
|
+
sessionStructuralChainRowsByVarId = {};
|
|
1756
|
+
appliedChangesetSnapshots = {};
|
|
1757
|
+
appliedStructuralChangesetKeys = {};
|
|
1758
|
+
}
|
|
1759
|
+
experimentData = data;
|
|
1760
|
+
variations = Array.isArray(data.variations) ? data.variations : [];
|
|
1761
|
+
var sameExpPage = prevKey === nextKey;
|
|
1762
|
+
activeVarId = pickActiveVariationIdForLoad(data, variations, activeVarId, sameExpPage);
|
|
1763
|
+
writePersistedActiveVariationId(activeVarId);
|
|
1764
|
+
renderVariationTabs();
|
|
1765
|
+
|
|
1021
1766
|
var urlBar = document.getElementById('url-bar');
|
|
1022
1767
|
urlBar.textContent = pageUrl;
|
|
1023
1768
|
urlBar.title = pageUrl;
|
|
1024
|
-
|
|
1025
|
-
|
|
1769
|
+
if (currentMainTab === 'history') renderHistoryTab();
|
|
1770
|
+
captureBaselineFromVariations(variations);
|
|
1771
|
+
recomputeEditorDirty();
|
|
1026
1772
|
loadPage(proxyUrl);
|
|
1027
1773
|
}
|
|
1028
1774
|
|
|
1029
1775
|
function showNoUrl(show) {
|
|
1030
1776
|
document.getElementById('no-url').style.display = show ? 'flex' : 'none';
|
|
1777
|
+
if (show) {
|
|
1778
|
+
detachIframeLoadingListeners();
|
|
1779
|
+
setIframePageLoadingUi(false);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
function detachIframeLoadingListeners() {
|
|
1784
|
+
if (!iframeDocLoadingListeners) return;
|
|
1785
|
+
try {
|
|
1786
|
+
iframeDocLoadingListeners.doc.removeEventListener(
|
|
1787
|
+
'readystatechange',
|
|
1788
|
+
iframeDocLoadingListeners.onRS,
|
|
1789
|
+
);
|
|
1790
|
+
} catch(_) {}
|
|
1791
|
+
iframeDocLoadingListeners = null;
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
function setIframePageLoadingUi(visible) {
|
|
1795
|
+
var disp = visible ? 'flex' : 'none';
|
|
1796
|
+
var tb = document.getElementById('iframe-loading-toolbar');
|
|
1797
|
+
var sb = document.getElementById('iframe-loading-sidebar');
|
|
1798
|
+
if (tb) tb.style.display = disp;
|
|
1799
|
+
if (sb) sb.style.display = disp;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
function updateIframeLoadingFromDocument(iframe, doc) {
|
|
1803
|
+
if (!iframe || !doc) {
|
|
1804
|
+
setIframePageLoadingUi(false);
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
if (!iframe.src || iframe.src === 'about:blank') {
|
|
1808
|
+
setIframePageLoadingUi(false);
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
try {
|
|
1812
|
+
if (!iframeDocMatchesNavigatedSrc(iframe, doc)) return;
|
|
1813
|
+
} catch(_) {
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
if (doc.readyState === 'complete') {
|
|
1817
|
+
setIframePageLoadingUi(false);
|
|
1818
|
+
detachIframeLoadingListeners();
|
|
1819
|
+
} else {
|
|
1820
|
+
setIframePageLoadingUi(true);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
function attachIframeLoadingUntilComplete(iframe) {
|
|
1825
|
+
detachIframeLoadingListeners();
|
|
1826
|
+
if (!iframe) return;
|
|
1827
|
+
var doc = null;
|
|
1828
|
+
try {
|
|
1829
|
+
doc = iframe.contentDocument;
|
|
1830
|
+
} catch(_) {}
|
|
1831
|
+
if (!doc) {
|
|
1832
|
+
setIframePageLoadingUi(true);
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
try {
|
|
1836
|
+
if (!iframeDocMatchesNavigatedSrc(iframe, doc)) return;
|
|
1837
|
+
} catch(_) {
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
function onRS() {
|
|
1841
|
+
updateIframeLoadingFromDocument(iframe, iframe.contentDocument);
|
|
1842
|
+
}
|
|
1843
|
+
updateIframeLoadingFromDocument(iframe, doc);
|
|
1844
|
+
if (doc.readyState !== 'complete') {
|
|
1845
|
+
iframeDocLoadingListeners = { doc: doc, onRS: onRS };
|
|
1846
|
+
doc.addEventListener('readystatechange', onRS);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
/** Tags we ignore when deciding body has \u201Creal\u201D content (matches DOM tree skippable). */
|
|
1851
|
+
function isDomTreeSkippableTagName(tagName) {
|
|
1852
|
+
var t = (tagName || '').toUpperCase();
|
|
1853
|
+
return t === 'SCRIPT' || t === 'STYLE' || t === 'NOSCRIPT' || t === 'LINK' || t === 'META' || t === 'TITLE';
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
/** True when body has at least one direct element child that is not script/style/etc. */
|
|
1857
|
+
function bodyHasFirstPaintChild(body) {
|
|
1858
|
+
if (!body) return false;
|
|
1859
|
+
var ch = body.children;
|
|
1860
|
+
for (var i = 0; i < ch.length; i++) {
|
|
1861
|
+
if (!isDomTreeSkippableTagName(ch[i].tagName)) return true;
|
|
1862
|
+
}
|
|
1863
|
+
return false;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
/** True when at least one granular changeset selector already matches (nested content painted). */
|
|
1867
|
+
function granularAnySelectorMatches(doc, cs) {
|
|
1868
|
+
if (!doc || !cs || !cs.length) return false;
|
|
1869
|
+
var g = filterGranularChangesetEntries(cs);
|
|
1870
|
+
for (var i = 0; i < g.length; i++) {
|
|
1871
|
+
if (querySelectorResolved(doc, g[i].selector)) return true;
|
|
1872
|
+
}
|
|
1873
|
+
return false;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
/** Bust bfcache / same-URL no-op reloads so the iframe actually re-parses (loading \u2192 interactive). */
|
|
1877
|
+
function appendIframeReloadBust(url) {
|
|
1878
|
+
if (!url || url === 'about:blank') return url;
|
|
1879
|
+
var stamp = Date.now();
|
|
1880
|
+
if (url.indexOf('__ve_reload=') !== -1) {
|
|
1881
|
+
return url.replace(/([&?])__ve_reload=[0-9]+/, '$1__ve_reload=' + stamp);
|
|
1882
|
+
}
|
|
1883
|
+
var sep = url.indexOf('?') !== -1 ? '&' : '?';
|
|
1884
|
+
return url + sep + '__ve_reload=' + stamp;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// True when the iframe contentDocument belongs to the current iframe.src navigation.
|
|
1888
|
+
// After src is updated, the old document can still read interactive/complete briefly;
|
|
1889
|
+
// applying variation changesets there is then wiped when the real navigation commits.
|
|
1890
|
+
function iframeDocMatchesNavigatedSrc(iframe, doc) {
|
|
1891
|
+
if (!iframe || !doc) return false;
|
|
1892
|
+
var src = iframe.getAttribute('src') || iframe.src || '';
|
|
1893
|
+
if (!src || src === 'about:blank') return false;
|
|
1894
|
+
var loc = '';
|
|
1895
|
+
try {
|
|
1896
|
+
loc = String(doc.URL || '');
|
|
1897
|
+
} catch(_) {
|
|
1898
|
+
return false;
|
|
1899
|
+
}
|
|
1900
|
+
if (!loc || loc === 'about:blank') return false;
|
|
1901
|
+
try {
|
|
1902
|
+
var base = window.location.href;
|
|
1903
|
+
var su = new URL(src, base);
|
|
1904
|
+
if (su.searchParams && su.searchParams.has('__ve_reload')) {
|
|
1905
|
+
su.searchParams.delete('__ve_reload');
|
|
1906
|
+
}
|
|
1907
|
+
var du = new URL(loc, base);
|
|
1908
|
+
if (du.searchParams && du.searchParams.has('__ve_reload')) {
|
|
1909
|
+
du.searchParams.delete('__ve_reload');
|
|
1910
|
+
}
|
|
1911
|
+
// Same-origin proxy that keeps document address aligned with iframe src
|
|
1912
|
+
if (su.origin === du.origin && su.pathname + su.search === du.pathname + du.search) {
|
|
1913
|
+
return true;
|
|
1914
|
+
}
|
|
1915
|
+
// conversion-proxy: iframe src stays on our app, but doc.URL is usually the target site after redirects
|
|
1916
|
+
var p = su.pathname || '';
|
|
1917
|
+
var isRootProxyPath = p === '/api/conversion-proxy' || p.indexOf('/api/conversion-proxy/') === 0;
|
|
1918
|
+
var isNestedMalformedProxy = !isRootProxyPath && p.indexOf('api/conversion-proxy') !== -1;
|
|
1919
|
+
if (isNestedMalformedProxy) return false;
|
|
1920
|
+
if (isRootProxyPath || String(su.href).indexOf('conversion-proxy') !== -1) {
|
|
1921
|
+
return doc === iframe.contentDocument;
|
|
1922
|
+
}
|
|
1923
|
+
return su.pathname + su.search === du.pathname + du.search;
|
|
1924
|
+
} catch(_) {
|
|
1925
|
+
return false;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
function nextIframeContentNavGen() {
|
|
1930
|
+
iframeContentNavGen += 1;
|
|
1931
|
+
return iframeContentNavGen;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
function stopIframeContentApplyWatcher() {
|
|
1935
|
+
if (iframeContentApplyTimer) {
|
|
1936
|
+
clearInterval(iframeContentApplyTimer);
|
|
1937
|
+
iframeContentApplyTimer = null;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
function clearPendingGranularChangesets() {
|
|
1942
|
+
pendingGranularChangesets = null;
|
|
1943
|
+
pendingGranularVarId = null;
|
|
1944
|
+
granularReapplyAttempts = 0;
|
|
1945
|
+
if (granularReapplyTimer) {
|
|
1946
|
+
clearTimeout(granularReapplyTimer);
|
|
1947
|
+
granularReapplyTimer = null;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
function resetIframeBindings() {
|
|
1952
|
+
detachIframeLoadingListeners();
|
|
1953
|
+
stopIframeContentApplyWatcher();
|
|
1954
|
+
appliedChangesetSnapshots = {};
|
|
1955
|
+
appliedStructuralChangesetKeys = {};
|
|
1956
|
+
clickAttachDoc = null;
|
|
1957
|
+
dragAttachDoc = null;
|
|
1958
|
+
changeObserverDoc = null;
|
|
1959
|
+
clearPendingGranularChangesets();
|
|
1960
|
+
if (changeObserver) {
|
|
1961
|
+
try { changeObserver.disconnect(); } catch(_) {}
|
|
1962
|
+
changeObserver = null;
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
function isIframeDomReady(iframe, doc) {
|
|
1967
|
+
if (!iframe || !doc || !doc.body) return false;
|
|
1968
|
+
var src = iframe.getAttribute('src') || '';
|
|
1969
|
+
var docUrl = '';
|
|
1970
|
+
try { docUrl = String(doc.URL || ''); } catch(_) {}
|
|
1971
|
+
if (src && src !== 'about:blank' && docUrl === 'about:blank') return false;
|
|
1972
|
+
// Allow interactive / loading \u2014 body may already have nodes; MutationObserver
|
|
1973
|
+
// will refresh the Elements tree as the rest of the document streams in.
|
|
1974
|
+
return true;
|
|
1031
1975
|
}
|
|
1032
1976
|
|
|
1033
1977
|
function loadPage(proxyUrl) {
|
|
1034
1978
|
showNoUrl(false);
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
document.getElementById('load-status').textContent = 'Loading page\u2026';
|
|
1979
|
+
lastLoadedProxyUrl = proxyUrl;
|
|
1980
|
+
var navGen = nextIframeContentNavGen();
|
|
1038
1981
|
var iframe = document.getElementById('iframeId');
|
|
1982
|
+
resetIframeBindings();
|
|
1039
1983
|
iframe.style.display = 'block';
|
|
1984
|
+
setIframePageLoadingUi(true);
|
|
1040
1985
|
iframe.src = proxyUrl;
|
|
1986
|
+
startIframeContentApplyWatcher(navGen);
|
|
1987
|
+
scheduleDomTreeRefresh();
|
|
1041
1988
|
}
|
|
1042
1989
|
|
|
1043
1990
|
// \u2500\u2500 Variation management \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1991
|
+
var VAR_COLORS = ['#0069A8','#CA3500','#00786F','#ec4899','#14b8a6','#f59e0b','#8b5cf6','#ef4444'];
|
|
1992
|
+
|
|
1044
1993
|
function renderVariationTabs() {
|
|
1045
1994
|
var container = document.getElementById('variation-tabs');
|
|
1046
1995
|
container.innerHTML = '';
|
|
1047
|
-
variations.forEach(function(v) {
|
|
1996
|
+
variations.forEach(function(v, i) {
|
|
1048
1997
|
var label = v.name || (v.baseline ? 'Control' : 'Variation');
|
|
1049
1998
|
var btn = document.createElement('button');
|
|
1050
1999
|
btn.className = 'var-tab' + (v._id === activeVarId ? ' active' : '');
|
|
1051
|
-
btn.textContent = label;
|
|
1052
2000
|
btn.title = label;
|
|
1053
2001
|
btn.onclick = function() { switchVariation(v._id); };
|
|
2002
|
+
var dot = document.createElement('span');
|
|
2003
|
+
dot.className = 'var-dot';
|
|
2004
|
+
dot.style.background = VAR_COLORS[i % VAR_COLORS.length];
|
|
2005
|
+
btn.appendChild(dot);
|
|
2006
|
+
btn.appendChild(document.createTextNode(label));
|
|
1054
2007
|
container.appendChild(btn);
|
|
1055
2008
|
});
|
|
2009
|
+
var addRow = document.createElement('button');
|
|
2010
|
+
addRow.className = 'var-add-row';
|
|
2011
|
+
var plus = document.createElement('span');
|
|
2012
|
+
plus.textContent = '+';
|
|
2013
|
+
plus.style.cssText = 'font-size:15px;line-height:1;font-weight:300';
|
|
2014
|
+
addRow.appendChild(plus);
|
|
2015
|
+
addRow.appendChild(document.createTextNode('Add variation'));
|
|
2016
|
+
container.appendChild(addRow);
|
|
1056
2017
|
var active = variations.find(function(v) { return v._id === activeVarId; });
|
|
1057
2018
|
document.getElementById('active-var-label').textContent = active ? active.name || '' : '';
|
|
1058
2019
|
}
|
|
@@ -1060,25 +2021,44 @@ function renderVariationTabs() {
|
|
|
1060
2021
|
function switchVariation(varId) {
|
|
1061
2022
|
if (varId === activeVarId) return;
|
|
1062
2023
|
saveCurrentVariationHtml();
|
|
2024
|
+
clearPendingGranularChangesets();
|
|
1063
2025
|
activeVarId = varId;
|
|
2026
|
+
writePersistedActiveVariationId(varId);
|
|
1064
2027
|
renderVariationTabs();
|
|
1065
2028
|
deselectElement();
|
|
1066
2029
|
try {
|
|
1067
2030
|
var iframe = document.getElementById('iframeId');
|
|
1068
2031
|
var saved = varHtmlCache[varId];
|
|
1069
2032
|
if (saved) {
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
2033
|
+
beginSuppressIframeMutationDirty();
|
|
2034
|
+
try {
|
|
2035
|
+
iframe.contentDocument.body.innerHTML = saved;
|
|
2036
|
+
} finally {
|
|
2037
|
+
endSuppressIframeMutationDirty();
|
|
2038
|
+
}
|
|
2039
|
+
detachIframeLoadingListeners();
|
|
2040
|
+
setIframePageLoadingUi(false);
|
|
2041
|
+
syncIframeInteractions('switch-variation-cache');
|
|
2042
|
+
try {
|
|
2043
|
+
var vNow = variations.find(function(x) { return x._id === activeVarId; });
|
|
2044
|
+
var csNow = parseVariationChangesets(vNow);
|
|
2045
|
+
registerPendingGranularChangesets(csNow, iframe.contentDocument);
|
|
2046
|
+
} catch(_) {}
|
|
1073
2047
|
} else {
|
|
1074
|
-
var
|
|
1075
|
-
|
|
1076
|
-
|
|
2048
|
+
var navGen = nextIframeContentNavGen();
|
|
2049
|
+
resetIframeBindings();
|
|
2050
|
+
setIframePageLoadingUi(true);
|
|
1077
2051
|
var src = iframe.src;
|
|
1078
2052
|
iframe.src = '';
|
|
1079
|
-
iframe.src = src;
|
|
2053
|
+
iframe.src = appendIframeReloadBust(src);
|
|
2054
|
+
// Do not sync here: the document is still the previous navigation until the
|
|
2055
|
+
// iframe load event; an eager sync attached observers / DOM tree to the wrong document.
|
|
2056
|
+
startIframeContentApplyWatcher(navGen);
|
|
2057
|
+
scheduleDomTreeRefresh();
|
|
1080
2058
|
}
|
|
1081
2059
|
} catch(_) {}
|
|
2060
|
+
if (currentMainTab === 'history') renderHistoryTab();
|
|
2061
|
+
recomputeEditorDirty();
|
|
1082
2062
|
}
|
|
1083
2063
|
|
|
1084
2064
|
function saveCurrentVariationHtml() {
|
|
@@ -1093,36 +2073,272 @@ function camelize(str) {
|
|
|
1093
2073
|
return (str || '').replace(/-([a-z])/g, function(_, c) { return c.toUpperCase(); });
|
|
1094
2074
|
}
|
|
1095
2075
|
|
|
2076
|
+
function parseVariationChangesets(variation) {
|
|
2077
|
+
if (!variation || variation.changesets == null) return [];
|
|
2078
|
+
var raw = variation.changesets;
|
|
2079
|
+
if (Array.isArray(raw)) return raw;
|
|
2080
|
+
if (typeof raw !== 'string') return [];
|
|
2081
|
+
var s = raw.trim();
|
|
2082
|
+
if (!s) return [];
|
|
2083
|
+
try {
|
|
2084
|
+
var cs = JSON.parse(s);
|
|
2085
|
+
if (Array.isArray(cs)) return cs;
|
|
2086
|
+
if (typeof cs === 'string') {
|
|
2087
|
+
try {
|
|
2088
|
+
var inner = JSON.parse(cs);
|
|
2089
|
+
return Array.isArray(inner) ? inner : [];
|
|
2090
|
+
} catch(_) {
|
|
2091
|
+
return [];
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
return [];
|
|
2095
|
+
} catch(_) {
|
|
2096
|
+
return [];
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
/** Lowercase entry.type so persisted / API rows match switches (e.g. Insert vs insert). */
|
|
2101
|
+
function normalizeChangesetType(entry) {
|
|
2102
|
+
return String(entry && entry.type != null ? entry.type : '').toLowerCase();
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
function filterGranularChangesetEntries(cs) {
|
|
2106
|
+
if (!cs || !cs.length) return [];
|
|
2107
|
+
var out = [];
|
|
2108
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2109
|
+
var e = cs[i];
|
|
2110
|
+
if (!e || !e.selector || e.selector === '__vvveb_body__') continue;
|
|
2111
|
+
out.push(e);
|
|
2112
|
+
}
|
|
2113
|
+
return out;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
/** Dedup key for merging chain-set rows (overlay wins over base). */
|
|
2117
|
+
function chainSetDedupKey(entry) {
|
|
2118
|
+
if (!entry || !entry.selector) return '';
|
|
2119
|
+
var t = normalizeChangesetType(entry);
|
|
2120
|
+
if (t === 'style') return entry.selector + '|s|' + String(entry.property || '');
|
|
2121
|
+
if (t === 'content') return entry.selector + '|c|' + (entry.html != null ? 'h' : 't');
|
|
2122
|
+
if (t === 'attribute') return entry.selector + '|a|' + String(entry.attribute || '');
|
|
2123
|
+
if (t === 'insert') return entry.selector + '|i|' + String(entry.action || '') + '|' + String(entry.html || '').slice(0, 120);
|
|
2124
|
+
if (t === 'remove') return entry.selector + '|r|';
|
|
2125
|
+
if (t === 'reorder') {
|
|
2126
|
+
return entry.selector + '|ro|' + String(entry.targetSelector || '') + '|' + String(entry.action || '');
|
|
2127
|
+
}
|
|
2128
|
+
try {
|
|
2129
|
+
return entry.selector + '|' + t + '|' + JSON.stringify(entry);
|
|
2130
|
+
} catch(_) {
|
|
2131
|
+
return entry.selector + '|' + t;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
function mergeGranularChainSets(baseList, overlayList) {
|
|
2136
|
+
var map = {};
|
|
2137
|
+
var order = [];
|
|
2138
|
+
function ingest(arr) {
|
|
2139
|
+
if (!arr || !arr.length) return;
|
|
2140
|
+
for (var i = 0; i < arr.length; i++) {
|
|
2141
|
+
var e = arr[i];
|
|
2142
|
+
if (!e || !e.selector) continue;
|
|
2143
|
+
var k = chainSetDedupKey(e);
|
|
2144
|
+
if (!map[k]) order.push(k);
|
|
2145
|
+
map[k] = e;
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
ingest(baseList);
|
|
2149
|
+
ingest(overlayList);
|
|
2150
|
+
var out = [];
|
|
2151
|
+
for (var j = 0; j < order.length; j++) {
|
|
2152
|
+
out.push(map[order[j]]);
|
|
2153
|
+
}
|
|
2154
|
+
return out;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
function appendSessionStructuralChainRow(varId, row) {
|
|
2158
|
+
if (!varId || !row) return;
|
|
2159
|
+
if (!sessionStructuralChainRowsByVarId[varId]) sessionStructuralChainRowsByVarId[varId] = [];
|
|
2160
|
+
sessionStructuralChainRowsByVarId[varId].push(row);
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
/** One States-tab row -> Conversion.io chain-set shape (matches applyChangesetEntry). */
|
|
2164
|
+
function stateChangeToChainSet(c) {
|
|
2165
|
+
if (!c || !c.selector) return null;
|
|
2166
|
+
if (c.cssProp) {
|
|
2167
|
+
return { selector: c.selector, type: 'style', property: c.cssProp, value: c.value };
|
|
2168
|
+
}
|
|
2169
|
+
switch (c.inputId) {
|
|
2170
|
+
case 'pp-text':
|
|
2171
|
+
return { selector: c.selector, type: 'content', value: c.value };
|
|
2172
|
+
case 'pp-html':
|
|
2173
|
+
return { selector: c.selector, type: 'content', html: c.value };
|
|
2174
|
+
case 'pp-cls':
|
|
2175
|
+
return { selector: c.selector, type: 'attribute', attribute: 'class', value: c.value };
|
|
2176
|
+
case 'pp-id':
|
|
2177
|
+
return { selector: c.selector, type: 'attribute', attribute: 'id', value: c.value };
|
|
2178
|
+
case 'pp-href':
|
|
2179
|
+
return { selector: c.selector, type: 'attribute', attribute: 'href', value: c.value };
|
|
2180
|
+
case 'pp-target':
|
|
2181
|
+
return { selector: c.selector, type: 'attribute', attribute: 'target', value: c.value };
|
|
2182
|
+
case 'pp-src':
|
|
2183
|
+
return { selector: c.selector, type: 'attribute', attribute: 'src', value: c.value };
|
|
2184
|
+
case 'pp-alt':
|
|
2185
|
+
return { selector: c.selector, type: 'attribute', attribute: 'alt', value: c.value };
|
|
2186
|
+
case 'pp-ph':
|
|
2187
|
+
return { selector: c.selector, type: 'attribute', attribute: 'placeholder', value: c.value };
|
|
2188
|
+
case 'pp-css':
|
|
2189
|
+
return { selector: c.selector, type: 'attribute', attribute: 'style', value: c.value };
|
|
2190
|
+
case 'pp-mob-css':
|
|
2191
|
+
return { selector: c.selector, type: 'attribute', attribute: 'data-mobile-css', value: c.value };
|
|
2192
|
+
case 'pp-tab-css':
|
|
2193
|
+
return { selector: c.selector, type: 'attribute', attribute: 'data-tablet-css', value: c.value };
|
|
2194
|
+
default:
|
|
2195
|
+
return null;
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
function countUnresolvedGranularSelectors(iframeDoc, entries) {
|
|
2200
|
+
if (!iframeDoc || !entries || !entries.length) return 0;
|
|
2201
|
+
var n = 0;
|
|
2202
|
+
for (var i = 0; i < entries.length; i++) {
|
|
2203
|
+
if (!querySelectorResolved(iframeDoc, entries[i].selector)) n++;
|
|
2204
|
+
}
|
|
2205
|
+
return n;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
function applyGranularChangesetEntries(iframeDoc, entries) {
|
|
2209
|
+
if (!iframeDoc || !entries || !entries.length) return;
|
|
2210
|
+
beginSuppressIframeMutationDirty();
|
|
2211
|
+
try {
|
|
2212
|
+
for (var i = 0; i < entries.length; i++) {
|
|
2213
|
+
applyChangesetEntry(entries[i], iframeDoc);
|
|
2214
|
+
}
|
|
2215
|
+
} finally {
|
|
2216
|
+
endSuppressIframeMutationDirty();
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
function registerPendingGranularChangesets(cs, iframeDoc) {
|
|
2221
|
+
clearPendingGranularChangesets();
|
|
2222
|
+
var granular = filterGranularChangesetEntries(cs);
|
|
2223
|
+
if (!granular.length || !iframeDoc) return;
|
|
2224
|
+
if (countUnresolvedGranularSelectors(iframeDoc, granular) === 0) return;
|
|
2225
|
+
pendingGranularChangesets = granular;
|
|
2226
|
+
pendingGranularVarId = activeVarId;
|
|
2227
|
+
granularReapplyAttempts = 0;
|
|
2228
|
+
scheduleGranularChangesetReapply();
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
function scheduleGranularChangesetReapply() {
|
|
2232
|
+
if (!pendingGranularChangesets || !pendingGranularChangesets.length) return;
|
|
2233
|
+
if (granularReapplyTimer) clearTimeout(granularReapplyTimer);
|
|
2234
|
+
granularReapplyTimer = setTimeout(function() {
|
|
2235
|
+
granularReapplyTimer = null;
|
|
2236
|
+
flushPendingGranularChangesets();
|
|
2237
|
+
}, 140);
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
function flushPendingGranularChangesets() {
|
|
2241
|
+
if (!pendingGranularChangesets || !pendingGranularChangesets.length) return;
|
|
2242
|
+
if (pendingGranularVarId !== activeVarId) {
|
|
2243
|
+
clearPendingGranularChangesets();
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
var iframe = document.getElementById('iframeId');
|
|
2247
|
+
var doc = iframe && iframe.contentDocument;
|
|
2248
|
+
if (!doc || !doc.body) {
|
|
2249
|
+
granularReapplyAttempts += 1;
|
|
2250
|
+
if (granularReapplyAttempts >= GRANULAR_REAPPLY_MAX) {
|
|
2251
|
+
clearPendingGranularChangesets();
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
scheduleGranularChangesetReapply();
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
applyGranularChangesetEntries(doc, pendingGranularChangesets);
|
|
2258
|
+
var left = countUnresolvedGranularSelectors(doc, pendingGranularChangesets);
|
|
2259
|
+
if (left === 0) {
|
|
2260
|
+
clearPendingGranularChangesets();
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
granularReapplyAttempts += 1;
|
|
2264
|
+
if (granularReapplyAttempts >= GRANULAR_REAPPLY_MAX) {
|
|
2265
|
+
clearPendingGranularChangesets();
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
2268
|
+
scheduleGranularChangesetReapply();
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
/** Stable key for structural changesets (insert/reorder) to avoid double-apply across early + full paint. */
|
|
2272
|
+
function structuralChangesetDedupKey(entry) {
|
|
2273
|
+
var nt = normalizeChangesetType(entry);
|
|
2274
|
+
if (!entry || (nt !== 'insert' && nt !== 'reorder')) return '';
|
|
2275
|
+
var vid = activeVarId || '';
|
|
2276
|
+
try {
|
|
2277
|
+
return (
|
|
2278
|
+
vid +
|
|
2279
|
+
'\0' +
|
|
2280
|
+
nt +
|
|
2281
|
+
'\0' +
|
|
2282
|
+
entry.selector +
|
|
2283
|
+
'\0' +
|
|
2284
|
+
String(entry.action || '') +
|
|
2285
|
+
'\0' +
|
|
2286
|
+
String(entry.html != null ? entry.html : '').slice(0, 240) +
|
|
2287
|
+
'\0' +
|
|
2288
|
+
String(entry.targetSelector || '')
|
|
2289
|
+
);
|
|
2290
|
+
} catch(_) {
|
|
2291
|
+
return vid + '\0' + nt + '\0' + entry.selector;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
|
|
1096
2295
|
/**
|
|
1097
|
-
* Apply a single changeset entry
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1100
|
-
* and the V2 full-body snapshot:
|
|
1101
|
-
* { selector: '__vvveb_body__', html: '<full body html>' }
|
|
2296
|
+
* Apply a single changeset entry inside the editor iframe.
|
|
2297
|
+
* Backend format: { selector, type: 'content'|'style'|'attribute'|'insert'|'remove', ... }
|
|
2298
|
+
* V2 snapshot: { selector: '__vvveb_body__', html: '<full body html>' }
|
|
1102
2299
|
*/
|
|
1103
2300
|
function applyChangesetEntry(entry, iframeDoc) {
|
|
1104
2301
|
if (!entry || !entry.selector) return;
|
|
1105
2302
|
|
|
1106
2303
|
// \u2500\u2500 V2 full-body snapshot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1107
2304
|
if (entry.selector === '__vvveb_body__' && entry.html != null) {
|
|
2305
|
+
var bkey = '__vvveb_body__|snapshot';
|
|
2306
|
+
if (!appliedChangesetSnapshots[bkey]) {
|
|
2307
|
+
appliedChangesetSnapshots[bkey] = { kind: 'bodyHTML', v: iframeDoc.body.innerHTML };
|
|
2308
|
+
}
|
|
1108
2309
|
iframeDoc.body.innerHTML = entry.html;
|
|
1109
2310
|
varHtmlCache[activeVarId] = entry.html;
|
|
1110
2311
|
return;
|
|
1111
2312
|
}
|
|
1112
2313
|
|
|
2314
|
+
var structuralDedupeKey = structuralChangesetDedupKey(entry);
|
|
2315
|
+
if (structuralDedupeKey && appliedStructuralChangesetKeys[structuralDedupeKey]) return;
|
|
2316
|
+
|
|
1113
2317
|
// \u2500\u2500 Standard granular changeset \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1114
|
-
var el =
|
|
1115
|
-
try { el = iframeDoc.querySelector(entry.selector); } catch(_) {}
|
|
2318
|
+
var el = querySelectorResolved(iframeDoc, entry.selector);
|
|
1116
2319
|
if (!el) return;
|
|
1117
2320
|
|
|
1118
|
-
|
|
2321
|
+
captureChangesetSnapshotBeforeApply(entry, el, iframeDoc);
|
|
2322
|
+
|
|
2323
|
+
switch (normalizeChangesetType(entry)) {
|
|
1119
2324
|
case 'content':
|
|
1120
2325
|
if (entry.html != null) el.innerHTML = entry.html;
|
|
1121
2326
|
else if (entry.value != null) el.textContent = entry.value;
|
|
1122
2327
|
break;
|
|
1123
2328
|
case 'style':
|
|
1124
|
-
if (entry.property
|
|
1125
|
-
|
|
2329
|
+
if (entry.property) {
|
|
2330
|
+
var propKebab = entry.property;
|
|
2331
|
+
var cam = camelize(propKebab);
|
|
2332
|
+
if (entry.value == null || entry.value === '') {
|
|
2333
|
+
try { el.style.removeProperty(propKebab); } catch(_) {}
|
|
2334
|
+
try { if (cam in el.style) el.style[cam] = ''; } catch(__) {}
|
|
2335
|
+
} else {
|
|
2336
|
+
try {
|
|
2337
|
+
el.style.setProperty(propKebab, entry.value, 'important');
|
|
2338
|
+
} catch(_) {
|
|
2339
|
+
el.style[cam] = entry.value;
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
1126
2342
|
}
|
|
1127
2343
|
break;
|
|
1128
2344
|
case 'attribute':
|
|
@@ -1133,56 +2349,562 @@ function applyChangesetEntry(entry, iframeDoc) {
|
|
|
1133
2349
|
case 'insert': {
|
|
1134
2350
|
var pos = entry.action === 'before' ? 'beforebegin' : 'afterend';
|
|
1135
2351
|
if (entry.html) el.insertAdjacentHTML(pos, entry.html);
|
|
2352
|
+
if (structuralDedupeKey) appliedStructuralChangesetKeys[structuralDedupeKey] = true;
|
|
1136
2353
|
break;
|
|
1137
2354
|
}
|
|
1138
2355
|
case 'remove':
|
|
1139
2356
|
el.style.display = 'none';
|
|
1140
2357
|
break;
|
|
2358
|
+
case 'reorder': {
|
|
2359
|
+
var target = entry.targetSelector ? querySelectorResolved(iframeDoc, entry.targetSelector) : null;
|
|
2360
|
+
if (!target || !el.parentNode || !target.parentNode) break;
|
|
2361
|
+
if (entry.action === 'before') {
|
|
2362
|
+
target.parentNode.insertBefore(el, target);
|
|
2363
|
+
} else {
|
|
2364
|
+
target.parentNode.insertBefore(el, target.nextSibling);
|
|
2365
|
+
}
|
|
2366
|
+
if (structuralDedupeKey) appliedStructuralChangesetKeys[structuralDedupeKey] = true;
|
|
2367
|
+
break;
|
|
2368
|
+
}
|
|
1141
2369
|
default: break;
|
|
1142
2370
|
}
|
|
1143
2371
|
}
|
|
1144
2372
|
|
|
1145
2373
|
function applyActiveVariationHtml() {
|
|
2374
|
+
clearPendingGranularChangesets();
|
|
1146
2375
|
if (!activeVarId) return;
|
|
2376
|
+
var iframe = document.getElementById('iframeId');
|
|
2377
|
+
var iframeDoc = iframe && iframe.contentDocument;
|
|
2378
|
+
if (!iframeDoc || !iframeDoc.body) return;
|
|
1147
2379
|
|
|
1148
|
-
|
|
1149
|
-
var
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
2380
|
+
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2381
|
+
var cs = parseVariationChangesets(variation);
|
|
2382
|
+
|
|
2383
|
+
beginSuppressIframeMutationDirty();
|
|
2384
|
+
try {
|
|
2385
|
+
// If we have an in-session HTML snapshot, use it (user edited in this session)
|
|
2386
|
+
var saved = varHtmlCache[activeVarId];
|
|
2387
|
+
if (saved) {
|
|
2388
|
+
try { iframeDoc.body.innerHTML = saved; } catch(_) {}
|
|
2389
|
+
return;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
if (!cs.length) return;
|
|
2393
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2394
|
+
applyChangesetEntry(cs[i], iframeDoc);
|
|
2395
|
+
}
|
|
2396
|
+
// Selectors often miss on first paint (client-rendered sections). Retry via timer + mutations.
|
|
2397
|
+
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2398
|
+
} finally {
|
|
2399
|
+
endSuppressIframeMutationDirty();
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
function changesetsHaveBodySnapshot(cs) {
|
|
2404
|
+
if (!cs || !cs.length) return false;
|
|
2405
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2406
|
+
if (cs[i] && cs[i].selector === '__vvveb_body__' && cs[i].html != null) return true;
|
|
1153
2407
|
}
|
|
2408
|
+
return false;
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
/** Rows to persist for this variation on Finalize (same chain-set model as EditorShell \u2014 never __vvveb_body__). */
|
|
2412
|
+
function buildPersistedChainSetsForVariation(v) {
|
|
2413
|
+
if (!v || !v._id) return [];
|
|
2414
|
+
var parsed = parseVariationChangesets(v);
|
|
2415
|
+
var base = filterGranularChangesetEntries(parsed);
|
|
2416
|
+
var sessionExtra = sessionStructuralChainRowsByVarId[v._id] || [];
|
|
2417
|
+
if (v._id !== activeVarId) {
|
|
2418
|
+
return mergeGranularChainSets(base, sessionExtra);
|
|
2419
|
+
}
|
|
2420
|
+
var overlay = [];
|
|
2421
|
+
for (var si = 0; si < stateChanges.length; si++) {
|
|
2422
|
+
var row = stateChangeToChainSet(stateChanges[si]);
|
|
2423
|
+
if (row) overlay.push(row);
|
|
2424
|
+
}
|
|
2425
|
+
return mergeGranularChainSets(mergeGranularChainSets(base, sessionExtra), overlay);
|
|
2426
|
+
}
|
|
1154
2427
|
|
|
1155
|
-
|
|
2428
|
+
/**
|
|
2429
|
+
* While document.readyState === 'loading', apply only granular changesets (no __vvveb_body__
|
|
2430
|
+
* replacement) so the first painted nodes can receive edits before iframe/window load completes.
|
|
2431
|
+
*/
|
|
2432
|
+
function applyVariationGranularOnly(iframeDoc) {
|
|
2433
|
+
if (!activeVarId || !iframeDoc || !iframeDoc.body) return;
|
|
2434
|
+
if (varHtmlCache[activeVarId]) return;
|
|
1156
2435
|
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
1157
|
-
|
|
2436
|
+
var cs = parseVariationChangesets(variation);
|
|
2437
|
+
if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
|
|
2438
|
+
beginSuppressIframeMutationDirty();
|
|
1158
2439
|
try {
|
|
1159
|
-
var
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
cs
|
|
1163
|
-
}
|
|
2440
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2441
|
+
applyChangesetEntry(cs[i], iframeDoc);
|
|
2442
|
+
}
|
|
2443
|
+
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2444
|
+
} finally {
|
|
2445
|
+
endSuppressIframeMutationDirty();
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
/** Re-try granular entries without resetting pending registration (use between poll ticks). */
|
|
2450
|
+
function reapplyActiveVariationGranular(iframeDoc) {
|
|
2451
|
+
if (!activeVarId || !iframeDoc || !iframeDoc.body) return;
|
|
2452
|
+
if (varHtmlCache[activeVarId]) return;
|
|
2453
|
+
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2454
|
+
var cs = parseVariationChangesets(variation);
|
|
2455
|
+
if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
|
|
2456
|
+
beginSuppressIframeMutationDirty();
|
|
2457
|
+
try {
|
|
2458
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2459
|
+
applyChangesetEntry(cs[i], iframeDoc);
|
|
2460
|
+
}
|
|
2461
|
+
} finally {
|
|
2462
|
+
endSuppressIframeMutationDirty();
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
/** Poll iframe document during navigation; apply granular edits as soon as DOM can match selectors. */
|
|
2467
|
+
function startIframeContentApplyWatcher(navGen) {
|
|
2468
|
+
stopIframeContentApplyWatcher();
|
|
2469
|
+
iframeEarlyGranularPrimedForGen = null;
|
|
2470
|
+
iframeEarlySyncPrimedForGen = null;
|
|
2471
|
+
var iframe = document.getElementById('iframeId');
|
|
2472
|
+
iframeContentApplyTimer = setInterval(function() {
|
|
2473
|
+
if (navGen !== iframeContentNavGen) {
|
|
2474
|
+
stopIframeContentApplyWatcher();
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2477
|
+
try {
|
|
2478
|
+
var doc = iframe.contentDocument;
|
|
2479
|
+
if (!doc || !doc.body) return;
|
|
2480
|
+
var docUrl = '';
|
|
2481
|
+
try { docUrl = String(doc.URL || ''); } catch(_) {}
|
|
2482
|
+
if (docUrl === 'about:blank') return;
|
|
2483
|
+
if (!iframeDocMatchesNavigatedSrc(iframe, doc)) return;
|
|
2484
|
+
|
|
2485
|
+
if (doc.readyState === 'interactive' || doc.readyState === 'complete') {
|
|
2486
|
+
applyActiveVariationHtml();
|
|
2487
|
+
syncIframeInteractions('iframe-readystate');
|
|
2488
|
+
stopIframeContentApplyWatcher();
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
if (doc.readyState === 'loading') {
|
|
2493
|
+
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2494
|
+
var cs0 = parseVariationChangesets(variation);
|
|
2495
|
+
if (!cs0.length || changesetsHaveBodySnapshot(cs0)) return;
|
|
2496
|
+
var granular = filterGranularChangesetEntries(cs0);
|
|
2497
|
+
if (!granular.length) return;
|
|
2498
|
+
|
|
2499
|
+
var canTry =
|
|
2500
|
+
bodyHasFirstPaintChild(doc.body) ||
|
|
2501
|
+
granularAnySelectorMatches(doc, cs0) ||
|
|
2502
|
+
(doc.body.children && doc.body.children.length > 0);
|
|
2503
|
+
|
|
2504
|
+
if (canTry) {
|
|
2505
|
+
if (iframeEarlyGranularPrimedForGen !== navGen) {
|
|
2506
|
+
iframeEarlyGranularPrimedForGen = navGen;
|
|
2507
|
+
applyVariationGranularOnly(doc);
|
|
2508
|
+
} else {
|
|
2509
|
+
reapplyActiveVariationGranular(doc);
|
|
2510
|
+
scheduleGranularChangesetReapply();
|
|
2511
|
+
}
|
|
2512
|
+
if (iframeEarlySyncPrimedForGen !== navGen) {
|
|
2513
|
+
iframeEarlySyncPrimedForGen = navGen;
|
|
2514
|
+
syncIframeInteractions('iframe-early-paint');
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
} catch(_) {}
|
|
2519
|
+
}, 20);
|
|
1164
2520
|
}
|
|
1165
2521
|
|
|
1166
2522
|
// \u2500\u2500 Element selection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1167
2523
|
function selectElement(el) {
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
2524
|
+
beginSuppressIframeMutationDirty();
|
|
2525
|
+
try {
|
|
2526
|
+
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} }
|
|
2527
|
+
selectedEl = el;
|
|
2528
|
+
try { el.classList.add('vve-selected'); } catch(_) {}
|
|
2529
|
+
} finally {
|
|
2530
|
+
endSuppressIframeMutationDirty();
|
|
2531
|
+
}
|
|
1171
2532
|
document.getElementById('bc-path').textContent = buildSelector(el);
|
|
1172
2533
|
document.getElementById('bc-path').style.color = 'var(--accent-txt)';
|
|
1173
2534
|
document.getElementById('no-sel').style.display = 'none';
|
|
1174
2535
|
renderRightPanel(el);
|
|
2536
|
+
updateSelectionToolbar();
|
|
2537
|
+
if (currentLeftTab === 'elements') {
|
|
2538
|
+
var dr = document.getElementById('dom-tree-root');
|
|
2539
|
+
if (dr && dr.querySelector('.dt-row')) syncDomTreeSelection();
|
|
2540
|
+
else scheduleDomTreeRefresh();
|
|
2541
|
+
}
|
|
1175
2542
|
}
|
|
1176
2543
|
|
|
1177
2544
|
function deselectElement() {
|
|
1178
|
-
|
|
2545
|
+
setDragHandleActive(false);
|
|
2546
|
+
beginSuppressIframeMutationDirty();
|
|
2547
|
+
try {
|
|
2548
|
+
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} selectedEl = null; }
|
|
2549
|
+
} finally {
|
|
2550
|
+
endSuppressIframeMutationDirty();
|
|
2551
|
+
}
|
|
1179
2552
|
document.getElementById('no-sel').style.display = '';
|
|
1180
2553
|
document.getElementById('el-info').style.display = 'none';
|
|
1181
2554
|
document.getElementById('rp-accordion').style.display = 'none';
|
|
1182
2555
|
document.getElementById('bc-path').textContent = 'No element selected';
|
|
1183
2556
|
document.getElementById('bc-path').style.color = 'var(--text-3)';
|
|
1184
|
-
// Switch back to Design tab so no-sel message is visible
|
|
1185
2557
|
switchMainTab('design');
|
|
2558
|
+
updateSelectionToolbar();
|
|
2559
|
+
syncDomTreeSelection();
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
// \u2500\u2500 Iframe selection chrome, floater toolbar, DOM tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2563
|
+
function injectIframeSelectionStyles(doc) {
|
|
2564
|
+
if (!doc || !doc.head) return;
|
|
2565
|
+
var sid = '__vve_sel_style';
|
|
2566
|
+
if (doc.getElementById(sid)) return;
|
|
2567
|
+
var st = doc.createElement('style');
|
|
2568
|
+
st.id = sid;
|
|
2569
|
+
st.textContent =
|
|
2570
|
+
'.vve-selected{outline:2px solid #6366f1!important;outline-offset:2px!important;' +
|
|
2571
|
+
'box-shadow:0 0 0 2px rgba(99,102,241,.28),inset 0 0 0 1px rgba(99,102,241,.18)!important;}' +
|
|
2572
|
+
'html.vve-drag-armed .vve-selected{cursor:grab!important;}' +
|
|
2573
|
+
'.vve-dragging{opacity:0.92!important;outline:2px dashed #f59e0b!important;' +
|
|
2574
|
+
'outline-offset:2px!important;cursor:grabbing!important;box-shadow:none!important;}';
|
|
2575
|
+
beginSuppressIframeMutationDirty();
|
|
2576
|
+
try {
|
|
2577
|
+
doc.head.appendChild(st);
|
|
2578
|
+
} finally {
|
|
2579
|
+
endSuppressIframeMutationDirty();
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
function setDragHandleActive(on) {
|
|
2584
|
+
dragHandleActive = !!on;
|
|
2585
|
+
var b = document.getElementById('sf-drag');
|
|
2586
|
+
if (b) b.classList.toggle('active', dragHandleActive);
|
|
2587
|
+
beginSuppressIframeMutationDirty();
|
|
2588
|
+
try {
|
|
2589
|
+
var iframe = document.getElementById('iframeId');
|
|
2590
|
+
var d = iframe && iframe.contentDocument && iframe.contentDocument.documentElement;
|
|
2591
|
+
if (d) d.classList.toggle('vve-drag-armed', dragHandleActive);
|
|
2592
|
+
} catch(_) {
|
|
2593
|
+
} finally {
|
|
2594
|
+
endSuppressIframeMutationDirty();
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
function positionSelectionToolbar() {
|
|
2599
|
+
var bar = document.getElementById('selection-floater');
|
|
2600
|
+
var iframe = document.getElementById('iframeId');
|
|
2601
|
+
var panel = document.getElementById('iframe-panel');
|
|
2602
|
+
if (!bar || !selectedEl || !iframe || !iframe.contentWindow || !panel) return;
|
|
2603
|
+
var elR = selectedEl.getBoundingClientRect();
|
|
2604
|
+
var iframeR = iframe.getBoundingClientRect();
|
|
2605
|
+
var panelR = panel.getBoundingClientRect();
|
|
2606
|
+
var left = iframeR.left - panelR.left + elR.left + elR.width / 2 - bar.offsetWidth / 2;
|
|
2607
|
+
var top = iframeR.top - panelR.top + elR.top - bar.offsetHeight - 8;
|
|
2608
|
+
if (top < 6) top = iframeR.top - panelR.top + elR.bottom + 8;
|
|
2609
|
+
var maxL = Math.max(6, panel.clientWidth - bar.offsetWidth - 8);
|
|
2610
|
+
left = Math.max(6, Math.min(left, maxL));
|
|
2611
|
+
bar.style.left = Math.round(left) + 'px';
|
|
2612
|
+
bar.style.top = Math.round(top) + 'px';
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
function updateSelectionToolbar() {
|
|
2616
|
+
var bar = document.getElementById('selection-floater');
|
|
2617
|
+
if (!bar) return;
|
|
2618
|
+
if (!selectedEl || currentMode !== 'editor') {
|
|
2619
|
+
bar.style.display = 'none';
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
bar.style.display = 'flex';
|
|
2623
|
+
requestAnimationFrame(function() { positionSelectionToolbar(); });
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
function onFloaterScroll() {
|
|
2627
|
+
if (selectedEl && currentMode === 'editor') positionSelectionToolbar();
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
function bindSelectionToolbarScroll() {
|
|
2631
|
+
var iframe = document.getElementById('iframeId');
|
|
2632
|
+
var w = iframe && iframe.contentWindow;
|
|
2633
|
+
if (!w) return;
|
|
2634
|
+
if (selectionScrollWin && selectionScrollWin !== w) {
|
|
2635
|
+
try { selectionScrollWin.removeEventListener('scroll', onFloaterScroll, true); } catch(_) {}
|
|
2636
|
+
}
|
|
2637
|
+
selectionScrollWin = w;
|
|
2638
|
+
w.addEventListener('scroll', onFloaterScroll, true);
|
|
2639
|
+
if (!selectionResizeBound) {
|
|
2640
|
+
selectionResizeBound = true;
|
|
2641
|
+
window.addEventListener('resize', onFloaterScroll);
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
/** Scroll the preview document so a node is visible (used when picking from the DOM tree). */
|
|
2646
|
+
function scrollIframeElementIntoView(el) {
|
|
2647
|
+
if (!el || el.nodeType !== 1) return;
|
|
2648
|
+
try {
|
|
2649
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
|
|
2650
|
+
} catch(_) {
|
|
2651
|
+
try {
|
|
2652
|
+
el.scrollIntoView(true);
|
|
2653
|
+
} catch(__) {}
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
function selectElementFromTree(el) {
|
|
2658
|
+
selectElement(el);
|
|
2659
|
+
scrollIframeElementIntoView(el);
|
|
2660
|
+
if (currentMainTab !== 'design') switchMainTab('design');
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
function duplicateSelectedEl() {
|
|
2664
|
+
if (!selectedEl || !selectedEl.parentNode) return;
|
|
2665
|
+
var anchorSel = buildSelector(selectedEl);
|
|
2666
|
+
var clone = selectedEl.cloneNode(true);
|
|
2667
|
+
clone.classList.remove('vve-selected');
|
|
2668
|
+
var all = clone.querySelectorAll ? clone.querySelectorAll('.vve-selected') : [];
|
|
2669
|
+
for (var i = 0; i < all.length; i++) all[i].classList.remove('vve-selected');
|
|
2670
|
+
clone.style.visibility = '';
|
|
2671
|
+
if (clone.removeAttribute) clone.removeAttribute('data-vve-hidden');
|
|
2672
|
+
stripDataVveInstanceSubtree(clone);
|
|
2673
|
+
try {
|
|
2674
|
+
if (clone.id) clone.removeAttribute('id');
|
|
2675
|
+
} catch(_) {}
|
|
2676
|
+
try {
|
|
2677
|
+
clone.setAttribute('data-vve-instance', generateVveInstanceId());
|
|
2678
|
+
} catch(_) {}
|
|
2679
|
+
if (activeVarId) {
|
|
2680
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
2681
|
+
selector: anchorSel,
|
|
2682
|
+
type: 'insert',
|
|
2683
|
+
action: 'after',
|
|
2684
|
+
html: clone.outerHTML,
|
|
2685
|
+
});
|
|
2686
|
+
}
|
|
2687
|
+
selectedEl.parentNode.insertBefore(clone, selectedEl.nextSibling);
|
|
2688
|
+
saveCurrentVariationHtml();
|
|
2689
|
+
recomputeEditorDirty();
|
|
2690
|
+
scheduleDomTreeRefresh();
|
|
2691
|
+
selectElement(clone);
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
function toggleHideSelectedEl() {
|
|
2695
|
+
if (!selectedEl) return;
|
|
2696
|
+
var hidSel = buildSelector(selectedEl);
|
|
2697
|
+
if (selectedEl.getAttribute('data-vve-hidden') === '1') {
|
|
2698
|
+
selectedEl.style.visibility = '';
|
|
2699
|
+
selectedEl.removeAttribute('data-vve-hidden');
|
|
2700
|
+
if (activeVarId) {
|
|
2701
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
2702
|
+
selector: hidSel,
|
|
2703
|
+
type: 'style',
|
|
2704
|
+
property: 'visibility',
|
|
2705
|
+
value: '',
|
|
2706
|
+
});
|
|
2707
|
+
}
|
|
2708
|
+
} else {
|
|
2709
|
+
selectedEl.style.visibility = 'hidden';
|
|
2710
|
+
selectedEl.setAttribute('data-vve-hidden', '1');
|
|
2711
|
+
if (activeVarId) {
|
|
2712
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
2713
|
+
selector: hidSel,
|
|
2714
|
+
type: 'style',
|
|
2715
|
+
property: 'visibility',
|
|
2716
|
+
value: 'hidden',
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
saveCurrentVariationHtml();
|
|
2721
|
+
recomputeEditorDirty();
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
function deleteSelectedEl() {
|
|
2725
|
+
if (!selectedEl || !selectedEl.parentNode) return;
|
|
2726
|
+
var delSel = buildSelector(selectedEl);
|
|
2727
|
+
selectedEl.remove();
|
|
2728
|
+
if (activeVarId) appendSessionStructuralChainRow(activeVarId, { selector: delSel, type: 'remove' });
|
|
2729
|
+
saveCurrentVariationHtml();
|
|
2730
|
+
recomputeEditorDirty();
|
|
2731
|
+
deselectElement();
|
|
2732
|
+
scheduleDomTreeRefresh();
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2735
|
+
function syncDomTreeSelection() {
|
|
2736
|
+
var root = document.getElementById('dom-tree-root');
|
|
2737
|
+
if (!root) return;
|
|
2738
|
+
var rows = root.querySelectorAll('.dt-row');
|
|
2739
|
+
for (var i = 0; i < rows.length; i++) {
|
|
2740
|
+
rows[i].classList.toggle('dt-selected', !!(selectedEl && rows[i]._dtEl === selectedEl));
|
|
2741
|
+
}
|
|
2742
|
+
if (!selectedEl) return;
|
|
2743
|
+
var found = null;
|
|
2744
|
+
for (var j = 0; j < rows.length; j++) {
|
|
2745
|
+
if (rows[j]._dtEl === selectedEl) { found = rows[j]; break; }
|
|
2746
|
+
}
|
|
2747
|
+
if (found) found.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
function scheduleDomTreeRefresh() {
|
|
2751
|
+
if (currentLeftTab !== 'elements') return;
|
|
2752
|
+
if (domTreeRefreshTimer) clearTimeout(domTreeRefreshTimer);
|
|
2753
|
+
domTreeRefreshTimer = setTimeout(function() {
|
|
2754
|
+
domTreeRefreshTimer = null;
|
|
2755
|
+
var inp = document.getElementById('comp-search');
|
|
2756
|
+
renderDomTree(inp ? inp.value : '');
|
|
2757
|
+
}, 150);
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
function setDomTreeStatus(mode) {
|
|
2761
|
+
var root = document.getElementById('dom-tree-root');
|
|
2762
|
+
if (!root) return;
|
|
2763
|
+
if (mode === 'empty') {
|
|
2764
|
+
root.innerHTML = '<div class="dt-muted">Load a page to see the DOM tree.</div>';
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
function domTreePathSegment(el) {
|
|
2769
|
+
var tag = el.tagName.toLowerCase();
|
|
2770
|
+
var idx = 1;
|
|
2771
|
+
var s = el.previousElementSibling;
|
|
2772
|
+
while (s) {
|
|
2773
|
+
if (s.tagName === el.tagName) idx++;
|
|
2774
|
+
s = s.previousElementSibling;
|
|
2775
|
+
}
|
|
2776
|
+
return tag + '[' + idx + ']';
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
function renderDomTree(filterRaw) {
|
|
2780
|
+
var filterText = (filterRaw || '').toLowerCase().trim();
|
|
2781
|
+
var root = document.getElementById('dom-tree-root');
|
|
2782
|
+
if (!root) return;
|
|
2783
|
+
var iframe = document.getElementById('iframeId');
|
|
2784
|
+
var doc = iframe && iframe.contentDocument;
|
|
2785
|
+
if (!isIframeDomReady(iframe, doc)) {
|
|
2786
|
+
if (iframe && iframe.src && iframe.src !== 'about:blank') {
|
|
2787
|
+
if (iframeSyncRetryTimer) clearTimeout(iframeSyncRetryTimer);
|
|
2788
|
+
iframeSyncRetryTimer = setTimeout(function() { syncIframeInteractions('dom-tree-wait'); }, 180);
|
|
2789
|
+
} else {
|
|
2790
|
+
setDomTreeStatus('empty');
|
|
2791
|
+
}
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
function labelFor(el) {
|
|
2796
|
+
var tag = el.tagName.toLowerCase();
|
|
2797
|
+
if (el.id) return tag + '#' + el.id.slice(0, 40);
|
|
2798
|
+
var cn = el.className && typeof el.className === 'string' ? el.className.trim() : '';
|
|
2799
|
+
if (cn) {
|
|
2800
|
+
var parts = cn.split(/s+/).filter(function(x) { return x.indexOf('vve-') !== 0; }).slice(0, 2).join('.');
|
|
2801
|
+
if (parts) return tag + '.' + parts.slice(0, 56);
|
|
2802
|
+
}
|
|
2803
|
+
return tag;
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
function skippable(el) {
|
|
2807
|
+
return isDomTreeSkippableTagName(el.tagName);
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
function branchMatches(el) {
|
|
2811
|
+
if (!filterText) return true;
|
|
2812
|
+
if (labelFor(el).toLowerCase().indexOf(filterText) >= 0) return true;
|
|
2813
|
+
var ch = el.children;
|
|
2814
|
+
for (var i = 0; i < ch.length; i++) {
|
|
2815
|
+
if (ch[i].nodeType !== 1 || skippable(ch[i])) continue;
|
|
2816
|
+
if (branchMatches(ch[i])) return true;
|
|
2817
|
+
}
|
|
2818
|
+
return false;
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
function nodeIcon(tag) {
|
|
2822
|
+
tag = (tag || '').toLowerCase();
|
|
2823
|
+
if (/^h[1-6]$/.test(tag)) return 'bi bi-type-h1';
|
|
2824
|
+
if (tag === 'a') return 'bi bi-link-45deg';
|
|
2825
|
+
if (tag === 'img') return 'bi bi-image';
|
|
2826
|
+
if (tag === 'section' || tag === 'main' || tag === 'article' || tag === 'header' || tag === 'footer' || tag === 'nav') return 'bi bi-layout-three-columns';
|
|
2827
|
+
if (tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea') return 'bi bi-ui-radios';
|
|
2828
|
+
if (tag === 'ul' || tag === 'ol') return 'bi bi-list-ul';
|
|
2829
|
+
if (tag === 'li') return 'bi bi-dot';
|
|
2830
|
+
if (tag === 'svg') return 'bi bi-bezier2';
|
|
2831
|
+
if (tag === 'p' || tag === 'span') return 'bi bi-text-left';
|
|
2832
|
+
return 'bi bi-square';
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
root.innerHTML = '';
|
|
2836
|
+
|
|
2837
|
+
function walk(el, depth, pathHere) {
|
|
2838
|
+
if (depth > 28) return;
|
|
2839
|
+
if (!el || el.nodeType !== 1) return;
|
|
2840
|
+
if (skippable(el)) return;
|
|
2841
|
+
if (!branchMatches(el)) return;
|
|
2842
|
+
|
|
2843
|
+
var hasKids = false;
|
|
2844
|
+
var i;
|
|
2845
|
+
for (i = 0; i < el.children.length; i++) {
|
|
2846
|
+
var c0 = el.children[i];
|
|
2847
|
+
if (c0.nodeType === 1 && !skippable(c0) && branchMatches(c0)) {
|
|
2848
|
+
hasKids = true;
|
|
2849
|
+
break;
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
var collapsed = !!domTreeCollapsed[pathHere];
|
|
2854
|
+
|
|
2855
|
+
var row = document.createElement('div');
|
|
2856
|
+
row.className = 'dt-row';
|
|
2857
|
+
if (el === selectedEl) row.classList.add('dt-selected');
|
|
2858
|
+
row._dtEl = el;
|
|
2859
|
+
row.style.paddingLeft = (4 + depth * 12) + 'px';
|
|
2860
|
+
|
|
2861
|
+
var chev = document.createElement('button');
|
|
2862
|
+
chev.type = 'button';
|
|
2863
|
+
chev.className = 'dt-chev' + (!hasKids ? ' dt-spacer' : '');
|
|
2864
|
+
chev.innerHTML = hasKids ? '<i class="bi bi-chevron-right"></i>' : '';
|
|
2865
|
+
if (hasKids) {
|
|
2866
|
+
chev.style.transform = collapsed ? 'rotate(0deg)' : 'rotate(90deg)';
|
|
2867
|
+
chev.onclick = function(e) {
|
|
2868
|
+
e.stopPropagation();
|
|
2869
|
+
domTreeCollapsed[pathHere] = !domTreeCollapsed[pathHere];
|
|
2870
|
+
renderDomTree(document.getElementById('comp-search').value);
|
|
2871
|
+
};
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
var ico = document.createElement('div');
|
|
2875
|
+
ico.className = 'dt-ico';
|
|
2876
|
+
ico.innerHTML = '<i class="' + nodeIcon(el.tagName) + '"></i>';
|
|
2877
|
+
|
|
2878
|
+
var lbl = document.createElement('div');
|
|
2879
|
+
lbl.className = 'dt-lbl';
|
|
2880
|
+
lbl.textContent = labelFor(el);
|
|
2881
|
+
lbl.title = buildSelector(el);
|
|
2882
|
+
|
|
2883
|
+
row.appendChild(chev);
|
|
2884
|
+
row.appendChild(ico);
|
|
2885
|
+
row.appendChild(lbl);
|
|
2886
|
+
row.onclick = function(e) {
|
|
2887
|
+
if (e.target.closest && e.target.closest('.dt-chev')) return;
|
|
2888
|
+
selectElementFromTree(el);
|
|
2889
|
+
};
|
|
2890
|
+
root.appendChild(row);
|
|
2891
|
+
|
|
2892
|
+
if (!hasKids || collapsed) return;
|
|
2893
|
+
for (i = 0; i < el.children.length; i++) {
|
|
2894
|
+
var c = el.children[i];
|
|
2895
|
+
if (c.nodeType !== 1 || skippable(c)) continue;
|
|
2896
|
+
if (!branchMatches(c)) continue;
|
|
2897
|
+
walk(c, depth + 1, pathHere + '/' + domTreePathSegment(c));
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
walk(doc.body, 0, 'body');
|
|
2902
|
+
|
|
2903
|
+
if (!root.querySelector('.dt-row')) {
|
|
2904
|
+
root.innerHTML = filterText
|
|
2905
|
+
? '<div class="dt-muted">No elements match your search.</div>'
|
|
2906
|
+
: '<div class="dt-muted">No visible elements yet.</div>';
|
|
2907
|
+
}
|
|
1186
2908
|
}
|
|
1187
2909
|
|
|
1188
2910
|
// \u2500\u2500 Utility helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -1297,14 +3019,12 @@ function renderImageSection(el) {
|
|
|
1297
3019
|
var prev = document.querySelector('.img-preview');
|
|
1298
3020
|
if (prev) prev.src = srcInp.value;
|
|
1299
3021
|
logChange(sel, 'pp-src', srcInp.value, el, orig);
|
|
1300
|
-
markDirty();
|
|
1301
3022
|
});
|
|
1302
3023
|
var altInp = document.getElementById('pp-img-alt');
|
|
1303
3024
|
if (altInp) altInp.addEventListener('input', function() {
|
|
1304
3025
|
var orig = getOriginalValue('pp-alt', el);
|
|
1305
3026
|
el.setAttribute('alt', altInp.value);
|
|
1306
3027
|
logChange(sel, 'pp-alt', altInp.value, el, orig);
|
|
1307
|
-
markDirty();
|
|
1308
3028
|
});
|
|
1309
3029
|
|
|
1310
3030
|
// Wire srcset entry inputs
|
|
@@ -1313,7 +3033,8 @@ function renderImageSection(el) {
|
|
|
1313
3033
|
var dInp = document.getElementById('pp-se-desc-'+i);
|
|
1314
3034
|
function flushSrcset() {
|
|
1315
3035
|
el.setAttribute('srcset', buildSrcset(_srcsetEntries));
|
|
1316
|
-
|
|
3036
|
+
saveCurrentVariationHtml();
|
|
3037
|
+
recomputeEditorDirty();
|
|
1317
3038
|
}
|
|
1318
3039
|
if (uInp) uInp.addEventListener('input', function(){ _srcsetEntries[i].url = uInp.value; flushSrcset(); });
|
|
1319
3040
|
if (dInp) dInp.addEventListener('input', function(){ _srcsetEntries[i].descriptor = dInp.value; flushSrcset(); });
|
|
@@ -1331,7 +3052,8 @@ function renderImageSection(el) {
|
|
|
1331
3052
|
}
|
|
1332
3053
|
function flushSizes() {
|
|
1333
3054
|
el.setAttribute('sizes', buildSizes(_sizesEntries));
|
|
1334
|
-
|
|
3055
|
+
saveCurrentVariationHtml();
|
|
3056
|
+
recomputeEditorDirty();
|
|
1335
3057
|
}
|
|
1336
3058
|
if (opInp) opInp.addEventListener('change', function(){ buildCondition(); flushSizes(); });
|
|
1337
3059
|
if (valInp) valInp.addEventListener('input', function(){ buildCondition(); flushSizes(); });
|
|
@@ -1345,7 +3067,12 @@ function addSrcsetEntry() {
|
|
|
1345
3067
|
}
|
|
1346
3068
|
function removeSrcsetEntry(i) {
|
|
1347
3069
|
_srcsetEntries.splice(i, 1);
|
|
1348
|
-
if (_imageEl) {
|
|
3070
|
+
if (_imageEl) {
|
|
3071
|
+
_imageEl.setAttribute('srcset', buildSrcset(_srcsetEntries));
|
|
3072
|
+
renderImageSection(_imageEl);
|
|
3073
|
+
saveCurrentVariationHtml();
|
|
3074
|
+
recomputeEditorDirty();
|
|
3075
|
+
}
|
|
1349
3076
|
}
|
|
1350
3077
|
function addSizesEntry() {
|
|
1351
3078
|
_sizesEntries.push({condition:'max-width: 760px', value:'760px'});
|
|
@@ -1353,7 +3080,12 @@ function addSizesEntry() {
|
|
|
1353
3080
|
}
|
|
1354
3081
|
function removeSizesEntry(i) {
|
|
1355
3082
|
_sizesEntries.splice(i, 1);
|
|
1356
|
-
if (_imageEl) {
|
|
3083
|
+
if (_imageEl) {
|
|
3084
|
+
_imageEl.setAttribute('sizes', buildSizes(_sizesEntries));
|
|
3085
|
+
renderImageSection(_imageEl);
|
|
3086
|
+
saveCurrentVariationHtml();
|
|
3087
|
+
recomputeEditorDirty();
|
|
3088
|
+
}
|
|
1357
3089
|
}
|
|
1358
3090
|
|
|
1359
3091
|
// \u2500\u2500 Right panel rendering (accordion) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -1558,20 +3290,51 @@ function renderRightPanel(el) {
|
|
|
1558
3290
|
var orig = getOriginalValue(b[0], el);
|
|
1559
3291
|
b[1](inp.value);
|
|
1560
3292
|
logChange(sel, b[0], inp.value, el, orig);
|
|
1561
|
-
markDirty();
|
|
1562
3293
|
});
|
|
1563
3294
|
});
|
|
1564
3295
|
}
|
|
1565
3296
|
|
|
1566
3297
|
// \u2500\u2500 Selector helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3298
|
+
function generateVveInstanceId() {
|
|
3299
|
+
return 'v' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10);
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
/** Editor-assigned clone marker so duplicated subtrees do not share the same CSS path as the original. */
|
|
3303
|
+
function stripDataVveInstanceSubtree(root) {
|
|
3304
|
+
if (!root || root.nodeType !== 1) return;
|
|
3305
|
+
try {
|
|
3306
|
+
root.removeAttribute('data-vve-instance');
|
|
3307
|
+
} catch(_) {}
|
|
3308
|
+
var sub = root.querySelectorAll ? root.querySelectorAll('[data-vve-instance]') : [];
|
|
3309
|
+
for (var i = 0; i < sub.length; i++) {
|
|
3310
|
+
try {
|
|
3311
|
+
sub[i].removeAttribute('data-vve-instance');
|
|
3312
|
+
} catch(_) {}
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
|
|
1567
3316
|
function buildSelector(el) {
|
|
1568
3317
|
if (!el) return '';
|
|
3318
|
+
var doc = el.ownerDocument || document;
|
|
3319
|
+
var inst = el.getAttribute && el.getAttribute('data-vve-instance');
|
|
3320
|
+
if (inst && String(inst).trim()) {
|
|
3321
|
+
var safe = String(inst).split('"').join('\\"');
|
|
3322
|
+
var attrSel = '[data-vve-instance="' + safe + '"]';
|
|
3323
|
+
try {
|
|
3324
|
+
if (doc.querySelectorAll(attrSel).length === 1) return attrSel;
|
|
3325
|
+
} catch(_) {}
|
|
3326
|
+
}
|
|
1569
3327
|
if (el.id) return '#' + el.id;
|
|
1570
3328
|
var parts = [], node = el, depth = 0;
|
|
1571
3329
|
while (node && node.nodeType === 1 && depth < 5) {
|
|
1572
3330
|
if (node.id) { parts.unshift('#' + node.id); break; }
|
|
1573
3331
|
var p = node.tagName.toLowerCase();
|
|
1574
|
-
if (node.classList && node.classList.length)
|
|
3332
|
+
if (node.classList && node.classList.length) {
|
|
3333
|
+
var clsArr = Array.from(node.classList).filter(function(c) {
|
|
3334
|
+
return c.indexOf('vve-') !== 0;
|
|
3335
|
+
});
|
|
3336
|
+
if (clsArr.length) p += '.' + clsArr.slice(0, 2).join('.');
|
|
3337
|
+
}
|
|
1575
3338
|
var idx = 1, sib = node.previousElementSibling;
|
|
1576
3339
|
while (sib) { if (sib.tagName === node.tagName) idx++; sib = sib.previousElementSibling; }
|
|
1577
3340
|
if (idx > 1) p += ':nth-of-type(' + idx + ')';
|
|
@@ -1582,12 +3345,202 @@ function buildSelector(el) {
|
|
|
1582
3345
|
return parts.join(' > ');
|
|
1583
3346
|
}
|
|
1584
3347
|
|
|
3348
|
+
/**
|
|
3349
|
+
* Strip editor-only .vve-* class tokens from a selector string (fixes DB rows saved while an element was selected).
|
|
3350
|
+
*/
|
|
3351
|
+
function sanitizeSelectorForMatch(sel) {
|
|
3352
|
+
if (!sel || typeof sel !== 'string') return '';
|
|
3353
|
+
var s0 = sel.replace(/.vve-[a-zA-Z0-9_-]+/gi, '');
|
|
3354
|
+
var parts = s0.split(/s*>s*/).map(function(seg) {
|
|
3355
|
+
var t = seg.replace(/.+/g, '.').replace(/.$/, '');
|
|
3356
|
+
return t.trim();
|
|
3357
|
+
});
|
|
3358
|
+
return parts.filter(Boolean).join(' > ');
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
/** Drop the rightmost :nth-of-type(n) (hydration / layout often shifts sibling indices). */
|
|
3362
|
+
function stripRightmostNthOfType(sel) {
|
|
3363
|
+
if (!sel || typeof sel !== 'string') return null;
|
|
3364
|
+
var idx = sel.lastIndexOf(':nth-of-type(');
|
|
3365
|
+
if (idx === -1) return null;
|
|
3366
|
+
var j = idx + ':nth-of-type('.length;
|
|
3367
|
+
while (j < sel.length && sel.charCodeAt(j) >= 48 && sel.charCodeAt(j) <= 57) j++;
|
|
3368
|
+
if (j >= sel.length || sel.charAt(j) !== ')') return null;
|
|
3369
|
+
return (sel.slice(0, idx) + sel.slice(j + 1))
|
|
3370
|
+
.replace(/s{2,}/g, ' ')
|
|
3371
|
+
.replace(/s*>s*>/g, ' >')
|
|
3372
|
+
.trim();
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
function querySelectorResolved(iframeDoc, selector) {
|
|
3376
|
+
if (!iframeDoc || !selector) return null;
|
|
3377
|
+
var seen = {};
|
|
3378
|
+
function tryOne(s) {
|
|
3379
|
+
if (!s || seen[s]) return null;
|
|
3380
|
+
seen[s] = true;
|
|
3381
|
+
try {
|
|
3382
|
+
return iframeDoc.querySelector(s) || null;
|
|
3383
|
+
} catch(_) {
|
|
3384
|
+
return null;
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
function walkRelax(base) {
|
|
3388
|
+
if (!base) return null;
|
|
3389
|
+
var el = tryOne(base);
|
|
3390
|
+
if (el) return el;
|
|
3391
|
+
var cur = base;
|
|
3392
|
+
for (var g = 0; g < 28; g++) {
|
|
3393
|
+
var nxt = stripRightmostNthOfType(cur);
|
|
3394
|
+
if (!nxt || nxt === cur) break;
|
|
3395
|
+
cur = nxt;
|
|
3396
|
+
el = tryOne(cur);
|
|
3397
|
+
if (el) return el;
|
|
3398
|
+
}
|
|
3399
|
+
return null;
|
|
3400
|
+
}
|
|
3401
|
+
var alt = sanitizeSelectorForMatch(selector);
|
|
3402
|
+
// Prefer sanitized + nth relax FIRST: raw selectors often still contain .vve-* from
|
|
3403
|
+
// save-time selection; those only match after clicking (we re-add vve-selected).
|
|
3404
|
+
var el = walkRelax(alt || selector);
|
|
3405
|
+
if (el) return el;
|
|
3406
|
+
if (alt !== selector) {
|
|
3407
|
+
el = walkRelax(selector);
|
|
3408
|
+
if (el) return el;
|
|
3409
|
+
}
|
|
3410
|
+
return null;
|
|
3411
|
+
}
|
|
3412
|
+
|
|
1585
3413
|
// \u2500\u2500 Iframe interaction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3414
|
+
function repositionDragSibling(dragEl, clientY) {
|
|
3415
|
+
var p = dragEl.parentElement;
|
|
3416
|
+
if (!p) return;
|
|
3417
|
+
var list = Array.prototype.filter.call(p.children, function(n) { return n.nodeType === 1; });
|
|
3418
|
+
var targetBefore = null;
|
|
3419
|
+
for (var i = 0; i < list.length; i++) {
|
|
3420
|
+
var node = list[i];
|
|
3421
|
+
if (node === dragEl) continue;
|
|
3422
|
+
var r = node.getBoundingClientRect();
|
|
3423
|
+
if (clientY < r.top + r.height / 2) {
|
|
3424
|
+
targetBefore = node;
|
|
3425
|
+
break;
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
if (targetBefore) {
|
|
3429
|
+
if (dragEl.nextSibling === targetBefore) return;
|
|
3430
|
+
p.insertBefore(dragEl, targetBefore);
|
|
3431
|
+
} else {
|
|
3432
|
+
var lastEl = list[list.length - 1];
|
|
3433
|
+
if (lastEl === dragEl) return;
|
|
3434
|
+
p.appendChild(dragEl);
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
function recordReorderAfterDrag(movedEl) {
|
|
3439
|
+
if (!activeVarId || !movedEl || !movedEl.parentElement) return;
|
|
3440
|
+
var prev = movedEl.previousElementSibling;
|
|
3441
|
+
var next = movedEl.nextElementSibling;
|
|
3442
|
+
if (prev) {
|
|
3443
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
3444
|
+
selector: buildSelector(movedEl),
|
|
3445
|
+
type: 'reorder',
|
|
3446
|
+
targetSelector: buildSelector(prev),
|
|
3447
|
+
action: 'after',
|
|
3448
|
+
});
|
|
3449
|
+
} else if (next) {
|
|
3450
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
3451
|
+
selector: buildSelector(movedEl),
|
|
3452
|
+
type: 'reorder',
|
|
3453
|
+
targetSelector: buildSelector(next),
|
|
3454
|
+
action: 'before',
|
|
3455
|
+
});
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
function attachDragReposition() {
|
|
3460
|
+
try {
|
|
3461
|
+
var iframe = document.getElementById('iframeId');
|
|
3462
|
+
var doc = iframe.contentDocument;
|
|
3463
|
+
if (!doc || !doc.body) return;
|
|
3464
|
+
if (dragAttachDoc === doc) return;
|
|
3465
|
+
dragAttachDoc = doc;
|
|
3466
|
+
doc.addEventListener('mousedown', function(e) {
|
|
3467
|
+
if (!dragHandleActive) return;
|
|
3468
|
+
if (currentMode !== 'editor' || !selectedEl) return;
|
|
3469
|
+
var t = e.target;
|
|
3470
|
+
if (!selectedEl.contains(t) || t === doc.body || t === doc.documentElement) return;
|
|
3471
|
+
var iframeEl = document.getElementById('iframeId');
|
|
3472
|
+
var win = iframeEl.contentWindow;
|
|
3473
|
+
function iframeLocalXY(ev) {
|
|
3474
|
+
if (ev.view === win) return { x: ev.clientX, y: ev.clientY };
|
|
3475
|
+
var ir = iframeEl.getBoundingClientRect();
|
|
3476
|
+
return { x: ev.clientX - ir.left, y: ev.clientY - ir.top };
|
|
3477
|
+
}
|
|
3478
|
+
var start = iframeLocalXY(e);
|
|
3479
|
+
var moved = false;
|
|
3480
|
+
function onMove(e2) {
|
|
3481
|
+
if (!selectedEl) return;
|
|
3482
|
+
var p = iframeLocalXY(e2);
|
|
3483
|
+
var dx = p.x - start.x, dy = p.y - start.y;
|
|
3484
|
+
if (!moved && (dx * dx + dy * dy < 25)) return;
|
|
3485
|
+
if (!moved) {
|
|
3486
|
+
moved = true;
|
|
3487
|
+
try {
|
|
3488
|
+
e.preventDefault();
|
|
3489
|
+
selectedEl.classList.add('vve-dragging');
|
|
3490
|
+
selectedEl.style.pointerEvents = 'none';
|
|
3491
|
+
} catch(_) {}
|
|
3492
|
+
}
|
|
3493
|
+
try { e2.preventDefault(); } catch(_) {}
|
|
3494
|
+
repositionDragSibling(selectedEl, p.y);
|
|
3495
|
+
}
|
|
3496
|
+
function onUp() {
|
|
3497
|
+
doc.removeEventListener('mousemove', onMove);
|
|
3498
|
+
doc.removeEventListener('mouseup', onUp);
|
|
3499
|
+
if (win) {
|
|
3500
|
+
win.removeEventListener('mousemove', onMove, true);
|
|
3501
|
+
win.removeEventListener('mouseup', onUp, true);
|
|
3502
|
+
}
|
|
3503
|
+
window.removeEventListener('mousemove', onMove, true);
|
|
3504
|
+
window.removeEventListener('mouseup', onUp, true);
|
|
3505
|
+
if (moved && selectedEl) {
|
|
3506
|
+
try {
|
|
3507
|
+
selectedEl.classList.remove('vve-dragging');
|
|
3508
|
+
selectedEl.style.pointerEvents = '';
|
|
3509
|
+
} catch(_) {}
|
|
3510
|
+
suppressClickUntil = Date.now() + 200;
|
|
3511
|
+
setDragHandleActive(false);
|
|
3512
|
+
if (activeVarId) recordReorderAfterDrag(selectedEl);
|
|
3513
|
+
saveCurrentVariationHtml();
|
|
3514
|
+
recomputeEditorDirty();
|
|
3515
|
+
updateSelectionToolbar();
|
|
3516
|
+
scheduleDomTreeRefresh();
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
doc.addEventListener('mousemove', onMove);
|
|
3520
|
+
doc.addEventListener('mouseup', onUp);
|
|
3521
|
+
if (win) {
|
|
3522
|
+
win.addEventListener('mousemove', onMove, true);
|
|
3523
|
+
win.addEventListener('mouseup', onUp, true);
|
|
3524
|
+
}
|
|
3525
|
+
window.addEventListener('mousemove', onMove, true);
|
|
3526
|
+
window.addEventListener('mouseup', onUp, true);
|
|
3527
|
+
}, true);
|
|
3528
|
+
} catch(_) {}
|
|
3529
|
+
}
|
|
3530
|
+
|
|
1586
3531
|
function attachClickHandler() {
|
|
1587
3532
|
try {
|
|
1588
3533
|
var iframe = document.getElementById('iframeId');
|
|
1589
3534
|
var doc = iframe.contentDocument;
|
|
3535
|
+
if (!doc || !doc.body) return;
|
|
3536
|
+
if (clickAttachDoc === doc) return;
|
|
3537
|
+
clickAttachDoc = doc;
|
|
1590
3538
|
doc.addEventListener('click', function(e) {
|
|
3539
|
+
if (Date.now() < suppressClickUntil) {
|
|
3540
|
+
e.preventDefault();
|
|
3541
|
+
e.stopPropagation();
|
|
3542
|
+
return;
|
|
3543
|
+
}
|
|
1591
3544
|
if (currentMode !== 'editor') return;
|
|
1592
3545
|
e.preventDefault();
|
|
1593
3546
|
e.stopPropagation();
|
|
@@ -1603,29 +3556,134 @@ function attachClickHandler() {
|
|
|
1603
3556
|
function attachChangeObserver() {
|
|
1604
3557
|
try {
|
|
1605
3558
|
var iframe = document.getElementById('iframeId');
|
|
1606
|
-
var
|
|
1607
|
-
|
|
3559
|
+
var doc = iframe && iframe.contentDocument;
|
|
3560
|
+
if (!doc || !doc.body) return;
|
|
3561
|
+
if (changeObserverDoc === doc) return;
|
|
3562
|
+
if (changeObserver) {
|
|
3563
|
+
try { changeObserver.disconnect(); } catch(_) {}
|
|
3564
|
+
changeObserver = null;
|
|
3565
|
+
changeObserverDoc = null;
|
|
3566
|
+
}
|
|
3567
|
+
changeObserver = new MutationObserver(function() {
|
|
3568
|
+
// Dirty state is derived from changesets baseline + stateChanges (not raw DOM mutations).
|
|
3569
|
+
scheduleDomTreeRefresh();
|
|
3570
|
+
scheduleGranularChangesetReapply();
|
|
3571
|
+
});
|
|
3572
|
+
changeObserver.observe(doc.body, {
|
|
1608
3573
|
childList: true, subtree: true, attributes: true, characterData: true
|
|
1609
3574
|
});
|
|
3575
|
+
changeObserverDoc = doc;
|
|
3576
|
+
} catch(_) {}
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
function syncIframeInteractions(reason) {
|
|
3580
|
+
try {
|
|
3581
|
+
var iframe = document.getElementById('iframeId');
|
|
3582
|
+
var doc = iframe && iframe.contentDocument;
|
|
3583
|
+
if (!isIframeDomReady(iframe, doc)) {
|
|
3584
|
+
iframeSyncAttempts += 1;
|
|
3585
|
+
if (iframeSyncAttempts > 120) return;
|
|
3586
|
+
if (iframeSyncRetryTimer) clearTimeout(iframeSyncRetryTimer);
|
|
3587
|
+
iframeSyncRetryTimer = setTimeout(function() { syncIframeInteractions('retry:' + reason); }, 120);
|
|
3588
|
+
return;
|
|
3589
|
+
}
|
|
3590
|
+
iframeSyncAttempts = 0;
|
|
3591
|
+
if (iframeSyncRetryTimer) {
|
|
3592
|
+
clearTimeout(iframeSyncRetryTimer);
|
|
3593
|
+
iframeSyncRetryTimer = null;
|
|
3594
|
+
}
|
|
3595
|
+
showNoUrl(false);
|
|
3596
|
+
injectIframeSelectionStyles(doc);
|
|
3597
|
+
attachClickHandler();
|
|
3598
|
+
attachDragReposition();
|
|
3599
|
+
attachChangeObserver();
|
|
3600
|
+
bindSelectionToolbarScroll();
|
|
3601
|
+
var inp = document.getElementById('comp-search');
|
|
3602
|
+
renderDomTree(inp ? inp.value : '');
|
|
3603
|
+
updateSelectionToolbar();
|
|
3604
|
+
recomputeEditorDirty();
|
|
1610
3605
|
} catch(_) {}
|
|
1611
3606
|
}
|
|
1612
3607
|
|
|
1613
|
-
// \u2500\u2500 HTML insertion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3608
|
+
// \u2500\u2500 HTML insertion (Components / Sections) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3609
|
+
/** Full snippets for Vvveb keys whose html field is placeholder text, not markup. */
|
|
3610
|
+
var VVVEB_INSERT_HTML_OVERRIDES = {
|
|
3611
|
+
'html/gridrow': '<div class="row g-3"><div class="col-sm-4"><p>Column 1</p></div><div class="col-sm-4"><p>Column 2</p></div><div class="col-sm-4"><p>Column 3</p></div></div>',
|
|
3612
|
+
'html/gridcolumn': '<div class="col-sm-6"><p>New column</p></div>',
|
|
3613
|
+
'html/container': '<div class="container py-3"><p>Container</p></div>',
|
|
3614
|
+
'html/btn-link': '<a class="btn btn-primary" href="#">Primary button</a>',
|
|
3615
|
+
'html/btn': '<button type="button" class="btn btn-primary">Primary</button>',
|
|
3616
|
+
'html/pageitem': '<li class="page-item"><a class="page-link" href="#">1</a></li>',
|
|
3617
|
+
'html/breadcrumbitem': '<li class="breadcrumb-item"><a href="#">Item</a></li>',
|
|
3618
|
+
'html/listitem': '<li class="list-group-item">List item</li>',
|
|
3619
|
+
'html/tablebody': '<tbody><tr><td>Cell</td></tr></tbody>',
|
|
3620
|
+
};
|
|
3621
|
+
|
|
3622
|
+
function buildHtmlFromVvvebComponent(comp, typeKey) {
|
|
3623
|
+
if (!comp) return '';
|
|
3624
|
+
if (typeKey && VVVEB_INSERT_HTML_OVERRIDES[typeKey]) return VVVEB_INSERT_HTML_OVERRIDES[typeKey];
|
|
3625
|
+
var raw = (comp.html != null ? String(comp.html) : '').trim();
|
|
3626
|
+
if (raw.indexOf('<') === 0) return comp.html;
|
|
3627
|
+
var tmpl = document.createElement('template');
|
|
3628
|
+
tmpl.innerHTML = raw;
|
|
3629
|
+
if (tmpl.content.children.length > 0) return comp.html;
|
|
3630
|
+
var classes = (comp.classes || []).filter(Boolean);
|
|
3631
|
+
var cls = classes[0] || '';
|
|
3632
|
+
var tag = 'div';
|
|
3633
|
+
if (classes.indexOf('btn') >= 0 || /(^|s)btn(s|$)/.test(cls)) tag = 'a';
|
|
3634
|
+
var extra = tag === 'a' ? ' href="#" role="button"' : '';
|
|
3635
|
+
var inner = raw || comp.name || 'Element';
|
|
3636
|
+
return '<' + tag + (cls ? ' class="' + cls + '"' : '') + extra + '>' + inner + '</' + tag + '>';
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
function insertVvvebComponent(typeKey) {
|
|
3640
|
+
var comp = typeof Vvveb !== 'undefined' && Vvveb.Components && Vvveb.Components.get
|
|
3641
|
+
? Vvveb.Components.get(typeKey)
|
|
3642
|
+
: null;
|
|
3643
|
+
var html = buildHtmlFromVvvebComponent(comp, typeKey);
|
|
3644
|
+
insertHtml(html);
|
|
3645
|
+
}
|
|
3646
|
+
|
|
1614
3647
|
function insertHtml(html) {
|
|
1615
3648
|
if (!html) return;
|
|
1616
3649
|
try {
|
|
1617
3650
|
var iframe = document.getElementById('iframeId');
|
|
1618
|
-
var doc = iframe.contentDocument;
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
3651
|
+
var doc = iframe && iframe.contentDocument;
|
|
3652
|
+
if (!doc || !doc.body) {
|
|
3653
|
+
console.warn('[V2] insertHtml: iframe document not ready');
|
|
3654
|
+
return;
|
|
3655
|
+
}
|
|
3656
|
+
var htmlStr = String(html).trim();
|
|
3657
|
+
var anchorSel =
|
|
3658
|
+
selectedEl && selectedEl !== doc.body && selectedEl.parentNode ? buildSelector(selectedEl) : 'body';
|
|
3659
|
+
var t = doc.createElement('template');
|
|
3660
|
+
t.innerHTML = htmlStr;
|
|
3661
|
+
var frag = doc.createDocumentFragment();
|
|
3662
|
+
var firstEl = null;
|
|
3663
|
+
while (t.content.firstChild) {
|
|
3664
|
+
var n = t.content.firstChild;
|
|
3665
|
+
t.content.removeChild(n);
|
|
3666
|
+
frag.appendChild(n);
|
|
3667
|
+
if (n.nodeType === 1 && !firstEl) firstEl = n;
|
|
3668
|
+
}
|
|
3669
|
+
if (!frag.childNodes.length) return;
|
|
1622
3670
|
if (selectedEl && selectedEl !== doc.body && selectedEl.parentNode) {
|
|
1623
|
-
selectedEl.parentNode.insertBefore(
|
|
3671
|
+
selectedEl.parentNode.insertBefore(frag, selectedEl.nextSibling);
|
|
1624
3672
|
} else {
|
|
1625
|
-
doc.body.appendChild(
|
|
3673
|
+
doc.body.appendChild(frag);
|
|
1626
3674
|
}
|
|
1627
|
-
selectElement(
|
|
1628
|
-
|
|
3675
|
+
if (firstEl) selectElement(firstEl);
|
|
3676
|
+
if (activeVarId) {
|
|
3677
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
3678
|
+
selector: anchorSel,
|
|
3679
|
+
type: 'insert',
|
|
3680
|
+
action: 'after',
|
|
3681
|
+
html: htmlStr,
|
|
3682
|
+
});
|
|
3683
|
+
}
|
|
3684
|
+
saveCurrentVariationHtml();
|
|
3685
|
+
recomputeEditorDirty();
|
|
3686
|
+
scheduleDomTreeRefresh();
|
|
1629
3687
|
} catch(err) { console.warn('[V2] insertHtml:', err); }
|
|
1630
3688
|
}
|
|
1631
3689
|
|
|
@@ -1650,14 +3708,24 @@ function renderSidebar(filter) {
|
|
|
1650
3708
|
}
|
|
1651
3709
|
if (!q && typeof Vvveb !== 'undefined' && Vvveb.Components && Vvveb.Components.list) {
|
|
1652
3710
|
var vvItems = [], clist = Vvveb.Components.list;
|
|
1653
|
-
for (var ck in clist) {
|
|
3711
|
+
for (var ck in clist) {
|
|
3712
|
+
if (!Object.prototype.hasOwnProperty.call(clist, ck)) continue;
|
|
3713
|
+
var cdef = clist[ck];
|
|
3714
|
+
vvItems.push({
|
|
3715
|
+
key: ck,
|
|
3716
|
+
name: (cdef.name || ck).replace(/^html\\//, '').slice(0, 22),
|
|
3717
|
+
});
|
|
3718
|
+
}
|
|
3719
|
+
vvItems.sort(function(a, b) { return a.name.localeCompare(b.name); });
|
|
1654
3720
|
if (vvItems.length > 0) {
|
|
1655
3721
|
var h2 = document.createElement('div'); h2.className = 'cg-hdr'; h2.textContent = 'Bootstrap 5'; compTab.appendChild(h2);
|
|
1656
3722
|
var g2 = document.createElement('div'); g2.className = 'cg-grid';
|
|
1657
|
-
vvItems.slice(0,
|
|
1658
|
-
var item = document.createElement('div'); item.className = 'cg-item'; item.title =
|
|
1659
|
-
item.innerHTML = '<div class="cg-icon"><i class="bi bi-puzzle"></i></div><div class="cg-name">' +
|
|
1660
|
-
|
|
3723
|
+
vvItems.slice(0, 48).forEach(function(entry) {
|
|
3724
|
+
var item = document.createElement('div'); item.className = 'cg-item'; item.title = entry.key;
|
|
3725
|
+
item.innerHTML = '<div class="cg-icon"><i class="bi bi-puzzle"></i></div><div class="cg-name">' + esc(entry.name) + '</div>';
|
|
3726
|
+
(function(typeKey) {
|
|
3727
|
+
item.onclick = function() { insertVvvebComponent(typeKey); };
|
|
3728
|
+
})(entry.key);
|
|
1661
3729
|
g2.appendChild(item);
|
|
1662
3730
|
});
|
|
1663
3731
|
compTab.appendChild(g2);
|
|
@@ -1694,7 +3762,10 @@ function renderSidebar(filter) {
|
|
|
1694
3762
|
if (!secTab.children.length) secTab.innerHTML = '<div style="padding:20px;text-align:center;color:#444;font-size:12px">No sections match</div>';
|
|
1695
3763
|
}
|
|
1696
3764
|
|
|
1697
|
-
document.getElementById('comp-search').addEventListener('input', function() {
|
|
3765
|
+
document.getElementById('comp-search').addEventListener('input', function() {
|
|
3766
|
+
if (currentLeftTab === 'elements') renderDomTree(this.value);
|
|
3767
|
+
else renderSidebar(this.value);
|
|
3768
|
+
});
|
|
1698
3769
|
|
|
1699
3770
|
// \u2500\u2500 Save / Close \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1700
3771
|
document.getElementById('btn-save').addEventListener('click', handleSave);
|
|
@@ -1703,30 +3774,147 @@ document.getElementById('btn-close').addEventListener('click', handleClose);
|
|
|
1703
3774
|
function handleSave() {
|
|
1704
3775
|
saveCurrentVariationHtml();
|
|
1705
3776
|
var updatedVariations = variations.map(function(v) {
|
|
1706
|
-
var
|
|
1707
|
-
|
|
1708
|
-
|
|
3777
|
+
var prevParsed = parseVariationChangesets(v);
|
|
3778
|
+
var granularPrev = filterGranularChangesetEntries(prevParsed);
|
|
3779
|
+
var bodyOnlyLegacy = changesetsHaveBodySnapshot(prevParsed) && granularPrev.length === 0;
|
|
3780
|
+
|
|
3781
|
+
var rows = buildPersistedChainSetsForVariation(v);
|
|
3782
|
+
if (bodyOnlyLegacy && rows.length === 0) {
|
|
3783
|
+
return Object.assign({}, v);
|
|
3784
|
+
}
|
|
3785
|
+
var json = '[]';
|
|
3786
|
+
try {
|
|
3787
|
+
json = JSON.stringify(rows || []);
|
|
3788
|
+
} catch(_) {
|
|
3789
|
+
json = '[]';
|
|
3790
|
+
}
|
|
3791
|
+
return Object.assign({}, v, { changesets: json });
|
|
1709
3792
|
});
|
|
3793
|
+
variations = updatedVariations;
|
|
3794
|
+
varHtmlCache = {};
|
|
3795
|
+
sessionStructuralChainRowsByVarId = {};
|
|
3796
|
+
stateChanges = [];
|
|
3797
|
+
if (currentMainTab === 'states') renderStatesTab();
|
|
3798
|
+
captureBaselineFromVariations(variations);
|
|
3799
|
+
if (experimentData && Array.isArray(experimentData.variations)) {
|
|
3800
|
+
experimentData.variations = updatedVariations;
|
|
3801
|
+
}
|
|
1710
3802
|
send('save-experiment', { experimentId: experimentData ? experimentData.experimentId : null, variations: updatedVariations });
|
|
1711
|
-
|
|
3803
|
+
setEditorDirty(false);
|
|
1712
3804
|
}
|
|
1713
3805
|
|
|
1714
3806
|
function handleClose() {
|
|
1715
|
-
|
|
3807
|
+
clearVisualEditorLocalStorage();
|
|
3808
|
+
// Unsaved-changes UX lives in the parent (PlatformVisualEditorV2); avoid double confirm here.
|
|
1716
3809
|
send('close-editor', {});
|
|
1717
3810
|
}
|
|
1718
3811
|
|
|
1719
3812
|
// \u2500\u2500 Keyboard shortcuts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1720
3813
|
document.addEventListener('keydown', function(e) {
|
|
1721
3814
|
var meta = e.metaKey || e.ctrlKey;
|
|
1722
|
-
if (meta && !e.shiftKey && e.key === 'z') {
|
|
1723
|
-
|
|
3815
|
+
if (meta && !e.shiftKey && e.key === 'z') {
|
|
3816
|
+
e.preventDefault();
|
|
3817
|
+
if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
|
|
3818
|
+
Vvveb.Undo.undo();
|
|
3819
|
+
saveCurrentVariationHtml();
|
|
3820
|
+
recomputeEditorDirty();
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
if (meta && e.shiftKey && e.key === 'z') {
|
|
3824
|
+
e.preventDefault();
|
|
3825
|
+
if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
|
|
3826
|
+
Vvveb.Undo.redo();
|
|
3827
|
+
saveCurrentVariationHtml();
|
|
3828
|
+
recomputeEditorDirty();
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
1724
3831
|
if (meta && e.key === 's') { e.preventDefault(); handleSave(); }
|
|
1725
|
-
if (e.key === 'Escape'
|
|
3832
|
+
if (e.key === 'Escape') {
|
|
3833
|
+
var openTips = document.querySelectorAll('.ve-pl-tip.is-tip-open');
|
|
3834
|
+
if (openTips.length) {
|
|
3835
|
+
e.preventDefault();
|
|
3836
|
+
for (var ti = 0; ti < openTips.length; ti++) {
|
|
3837
|
+
var h = openTips[ti];
|
|
3838
|
+
h.classList.remove('is-tip-open');
|
|
3839
|
+
h.classList.remove('is-tip-flip');
|
|
3840
|
+
var tp = h.querySelector('.ve-pl-tooltip');
|
|
3841
|
+
if (tp) tp.style.display = '';
|
|
3842
|
+
}
|
|
3843
|
+
return;
|
|
3844
|
+
}
|
|
3845
|
+
setDragHandleActive(false);
|
|
3846
|
+
if (selectedEl) deselectElement();
|
|
3847
|
+
}
|
|
1726
3848
|
});
|
|
1727
3849
|
document.getElementById('btn-undo').addEventListener('click', function() { if (typeof Vvveb !== 'undefined' && Vvveb.Undo) Vvveb.Undo.undo(); });
|
|
1728
3850
|
document.getElementById('btn-redo').addEventListener('click', function() { if (typeof Vvveb !== 'undefined' && Vvveb.Undo) Vvveb.Undo.redo(); });
|
|
1729
3851
|
|
|
3852
|
+
function layoutLoadingTooltip(host) {
|
|
3853
|
+
var tip = host.querySelector('.ve-pl-tooltip');
|
|
3854
|
+
if (!tip || !host.classList.contains('is-tip-open')) return;
|
|
3855
|
+
var cr = host.getBoundingClientRect();
|
|
3856
|
+
var gap = 10;
|
|
3857
|
+
var pad = 12;
|
|
3858
|
+
host.classList.remove('is-tip-flip');
|
|
3859
|
+
tip.style.display = 'block';
|
|
3860
|
+
tip.style.visibility = 'hidden';
|
|
3861
|
+
var tw = tip.offsetWidth;
|
|
3862
|
+
var th = tip.offsetHeight;
|
|
3863
|
+
tip.style.visibility = '';
|
|
3864
|
+
var cx = cr.left + cr.width / 2;
|
|
3865
|
+
cx = Math.max(pad + tw / 2, Math.min(cx, window.innerWidth - pad - tw / 2));
|
|
3866
|
+
tip.style.left = Math.round(cx) + 'px';
|
|
3867
|
+
tip.style.transform = 'translateX(-50%)';
|
|
3868
|
+
var preferBelow = host.classList.contains('ve-pl-tip-below');
|
|
3869
|
+
var top;
|
|
3870
|
+
if (preferBelow) {
|
|
3871
|
+
top = cr.bottom + gap;
|
|
3872
|
+
if (top + th > window.innerHeight - pad) {
|
|
3873
|
+
top = cr.top - gap - th;
|
|
3874
|
+
host.classList.add('is-tip-flip');
|
|
3875
|
+
}
|
|
3876
|
+
} else {
|
|
3877
|
+
top = cr.top - gap - th;
|
|
3878
|
+
if (top < pad) {
|
|
3879
|
+
top = cr.bottom + gap;
|
|
3880
|
+
host.classList.add('is-tip-flip');
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
top = Math.max(pad, Math.min(top, window.innerHeight - pad - th));
|
|
3884
|
+
tip.style.top = Math.round(top) + 'px';
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
function bindLoadingTooltipPositioning() {
|
|
3888
|
+
function openTip(host) {
|
|
3889
|
+
host.classList.add('is-tip-open');
|
|
3890
|
+
layoutLoadingTooltip(host);
|
|
3891
|
+
requestAnimationFrame(function() { layoutLoadingTooltip(host); });
|
|
3892
|
+
}
|
|
3893
|
+
function closeTip(host) {
|
|
3894
|
+
host.classList.remove('is-tip-open');
|
|
3895
|
+
host.classList.remove('is-tip-flip');
|
|
3896
|
+
var tip = host.querySelector('.ve-pl-tooltip');
|
|
3897
|
+
if (tip) tip.style.display = '';
|
|
3898
|
+
}
|
|
3899
|
+
var nodes = document.querySelectorAll('.ve-pl-tip');
|
|
3900
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
3901
|
+
(function(host) {
|
|
3902
|
+
host.addEventListener('mouseenter', function() { openTip(host); });
|
|
3903
|
+
host.addEventListener('mouseleave', function() { closeTip(host); });
|
|
3904
|
+
host.addEventListener('focusin', function() { openTip(host); });
|
|
3905
|
+
host.addEventListener('focusout', function(e) {
|
|
3906
|
+
if (!host.contains(e.relatedTarget)) closeTip(host);
|
|
3907
|
+
});
|
|
3908
|
+
})(nodes[i]);
|
|
3909
|
+
}
|
|
3910
|
+
function reflowOpenTips() {
|
|
3911
|
+
var o = document.querySelectorAll('.ve-pl-tip.is-tip-open');
|
|
3912
|
+
for (var j = 0; j < o.length; j++) layoutLoadingTooltip(o[j]);
|
|
3913
|
+
}
|
|
3914
|
+
window.addEventListener('scroll', reflowOpenTips, true);
|
|
3915
|
+
window.addEventListener('resize', reflowOpenTips);
|
|
3916
|
+
}
|
|
3917
|
+
|
|
1730
3918
|
// \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1731
3919
|
function registerCROSections() {
|
|
1732
3920
|
if (typeof Vvveb === 'undefined' || !Vvveb.Sections) return;
|
|
@@ -1736,22 +3924,57 @@ function registerCROSections() {
|
|
|
1736
3924
|
window.addEventListener('load', function() {
|
|
1737
3925
|
registerCROSections();
|
|
1738
3926
|
renderSidebar();
|
|
3927
|
+
renderDomTree(document.getElementById('comp-search').value);
|
|
1739
3928
|
vvvebReady = true;
|
|
3929
|
+
bindLoadingTooltipPositioning();
|
|
1740
3930
|
|
|
1741
|
-
//
|
|
1742
|
-
document.getElementById('loading').classList.add('hidden');
|
|
3931
|
+
// Show no-url state until experiment arrives
|
|
1743
3932
|
showNoUrl(true);
|
|
1744
3933
|
|
|
1745
3934
|
// After each iframe load: apply variation, wire click+mutation handlers
|
|
1746
3935
|
var iframe = document.getElementById('iframeId');
|
|
1747
3936
|
iframe.addEventListener('load', function() {
|
|
1748
3937
|
if (!iframe.src || iframe.src === 'about:blank' || iframe.src === window.location.href) return;
|
|
1749
|
-
|
|
1750
|
-
|
|
3938
|
+
var doc = iframe.contentDocument;
|
|
3939
|
+
if (!doc || !doc.body) return;
|
|
3940
|
+
var docUrl = '';
|
|
3941
|
+
try { docUrl = String(doc.URL || ''); } catch(_) {}
|
|
3942
|
+
// Stale events: src may already be the proxy URL while the document is still
|
|
3943
|
+
// about:blank (e.g. src cleared then reset to force reload).
|
|
3944
|
+
if (docUrl === 'about:blank') return;
|
|
3945
|
+
if (!iframeDocMatchesNavigatedSrc(iframe, doc)) return;
|
|
3946
|
+
attachIframeLoadingUntilComplete(iframe);
|
|
3947
|
+
stopIframeContentApplyWatcher();
|
|
1751
3948
|
deselectElement();
|
|
1752
3949
|
applyActiveVariationHtml();
|
|
1753
|
-
|
|
1754
|
-
|
|
3950
|
+
syncIframeInteractions('iframe-load');
|
|
3951
|
+
});
|
|
3952
|
+
|
|
3953
|
+
document.getElementById('sf-drag').addEventListener('click', function(e) {
|
|
3954
|
+
e.preventDefault();
|
|
3955
|
+
e.stopPropagation();
|
|
3956
|
+
if (!selectedEl) return;
|
|
3957
|
+
setDragHandleActive(!dragHandleActive);
|
|
3958
|
+
});
|
|
3959
|
+
document.getElementById('sf-dup').addEventListener('click', function(e) {
|
|
3960
|
+
e.preventDefault();
|
|
3961
|
+
e.stopPropagation();
|
|
3962
|
+
duplicateSelectedEl();
|
|
3963
|
+
});
|
|
3964
|
+
document.getElementById('sf-hide').addEventListener('click', function(e) {
|
|
3965
|
+
e.preventDefault();
|
|
3966
|
+
e.stopPropagation();
|
|
3967
|
+
toggleHideSelectedEl();
|
|
3968
|
+
});
|
|
3969
|
+
document.getElementById('sf-del').addEventListener('click', function(e) {
|
|
3970
|
+
e.preventDefault();
|
|
3971
|
+
e.stopPropagation();
|
|
3972
|
+
deleteSelectedEl();
|
|
3973
|
+
});
|
|
3974
|
+
document.getElementById('sf-close').addEventListener('click', function(e) {
|
|
3975
|
+
e.preventDefault();
|
|
3976
|
+
e.stopPropagation();
|
|
3977
|
+
deselectElement();
|
|
1755
3978
|
});
|
|
1756
3979
|
|
|
1757
3980
|
send('editor-ready', {});
|