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