@accelerated-agency/visual-editor 0.2.3 → 0.2.4
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 +3 -10
- package/dist/vite.cjs +1770 -165
- package/dist/vite.js +1770 -165
- package/package.json +1 -1
package/dist/vite.cjs
CHANGED
|
@@ -93,12 +93,12 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
93
93
|
--bg-hover: #f1f5f9;
|
|
94
94
|
--border: #e2e8f0;
|
|
95
95
|
--border-sub: #f1f5f9;
|
|
96
|
-
--text: #
|
|
96
|
+
--text: #404040;
|
|
97
97
|
--text-2: #475569;
|
|
98
98
|
--text-3: #94a3b8;
|
|
99
99
|
--accent: #6366f1;
|
|
100
100
|
--accent-bg: #eef2ff;
|
|
101
|
-
--accent-txt: #
|
|
101
|
+
--accent-txt: #404040;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/* \u2500\u2500 Layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
@@ -107,15 +107,113 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
107
107
|
|
|
108
108
|
/* \u2500\u2500 Toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
109
109
|
#toolbar{
|
|
110
|
-
height:
|
|
111
|
-
display:flex;align-items:center;padding:0
|
|
112
|
-
|
|
113
|
-
}
|
|
110
|
+
height:52px;background:#FFFFFF;border-bottom:1px solid #E5E5E5;
|
|
111
|
+
display:flex;align-items:center;padding:0 16px;gap:0;flex-shrink:0;z-index:100;
|
|
112
|
+
overflow:visible
|
|
113
|
+
}
|
|
114
|
+
/* Toolbar layout sections */
|
|
115
|
+
.tb-left{display:flex;align-items:center;gap:0;flex-shrink:0}
|
|
116
|
+
.tb-center{display:flex;align-items:center;gap:4px;flex:1;justify-content:center}
|
|
117
|
+
.tb-right{display:flex;align-items:center;gap:6px;flex-shrink:0;margin-left:auto}
|
|
118
|
+
/* Logo */
|
|
119
|
+
.tb-logo{font-size:24px;color:#404040;flex-shrink:0;margin-right:10px;line-height:1}
|
|
120
|
+
/* Breadcrumb */
|
|
121
|
+
.tb-bc-item{font-size:14px;font-weight:500;color:#737373;white-space:nowrap}
|
|
122
|
+
.tb-bc-sep{font-size:14px;color:#737373;padding:0 6px;user-select:none}
|
|
123
|
+
.tb-bc-page{display:flex;align-items:center;gap:3px;cursor:pointer;color:#e4e4e7;font-size:13px;font-weight:500;background:none;border:none;padding:3px 6px;border-radius:5px;font-family:inherit;line-height:1}
|
|
124
|
+
.tb-bc-page:hover{background:rgba(255,255,255,.06)}
|
|
125
|
+
.tb-bc-page i{font-size:9px;color:#71717a}
|
|
126
|
+
/* DRAFT status badge */
|
|
127
|
+
.tb-draft-badge{display:flex;align-items:center;gap:5px;background:#E5E5E5;padding:5px 4px;border-radius:4px}
|
|
128
|
+
.tb-draft-dot{width:6px;height:6px;border-radius:50%;background:#404040;flex-shrink:0}
|
|
129
|
+
.tb-draft-lbl{font-size:12px;font-weight:700;color:##404040;letter-spacing:.04em}
|
|
130
|
+
/* Viewport selector */
|
|
131
|
+
.tb-viewport{display:flex;align-items:center;gap:4px;padding:6px 10px;border-radius:6px;border:1px solid #e5e7eb;background:#FFFFFF;height:30px;flex-shrink:0}
|
|
132
|
+
.tb-viewport #dev-label{font-size:14px;font-weight:500;color:##404040;min-width:auto;background:none;border:none;padding:0;overflow:visible;white-space:nowrap;text-overflow:clip;border-radius:0;max-width:none}
|
|
133
|
+
.tb-viewport i{font-size:9px;color:##404040}
|
|
134
|
+
/* Device toggle buttons */
|
|
135
|
+
.tb-dev-btns{display:flex;align-items:center;gap:1px;padding:8px 4px;background:#F0F0F0;border-radius:7px; height: 30px;width: 116px;}
|
|
136
|
+
.tb-dev-3btns{display:flex;align-items:center;gap:2px;padding:8px 4px;background:#F0F0F0;border-radius:7px; height: 30px;width: 100px;}
|
|
137
|
+
/* Dark icon buttons */
|
|
138
|
+
.tb-dk-btn{width:28px;height:28px;background:transparent;border:none;border-radius:5px;cursor:pointer;color:#71717a;display:flex;align-items:center;justify-content:center;font-size:13px;transition:all .12s;flex-shrink:0}
|
|
139
|
+
.tb-dk-btn:hover{color:#e4e4e7;background:rgba(255,255,255,.07)}
|
|
140
|
+
.tb-dk-btn.active{background:#FFFFFF}
|
|
141
|
+
/* Dark separator */
|
|
142
|
+
.tb-dk-sep{width:1px;height:16px;background:#3f3f46;flex-shrink:0;margin:0 2px}
|
|
143
|
+
/* Save status */
|
|
144
|
+
#dirty-dot{width:6px;height:6px;border-radius:50%;background:#22c55e;flex-shrink:0;transition:background .2s}
|
|
145
|
+
#dirty-dot.on{background:#f59e0b}
|
|
146
|
+
.tb-save-area{display:flex;align-items:center;gap:5px}
|
|
147
|
+
.tb-save-txt{font-size:14px;color:#00C951;white-space:nowrap}
|
|
148
|
+
.tb-save-txt::before{content:'Saved'}
|
|
149
|
+
#dirty-dot.on~.tb-save-txt::before{content:'Unsaved'}
|
|
150
|
+
.tb-save-time{font-size:12px;color:#52525b;white-space:nowrap}
|
|
151
|
+
#dirty-dot.on~.tb-save-time{display:none}
|
|
152
|
+
/* Simulate + Finalize buttons */
|
|
153
|
+
.tb-sim-btn{background:transparent;border:1px solid #e5e7eb;border-radius:6px;color:#404040;cursor:pointer;font-size:14px;font-weight:500;padding:6px 10px;display:flex;align-items:center;gap:5px;transition:all .15s;white-space:nowrap;font-family:inherit}
|
|
154
|
+
.tb-sim-btn:hover{background:rgba(255,255,255,.06);border-color:#52525b}
|
|
155
|
+
.tb-sim-btn i{font-size:11px}
|
|
156
|
+
.tb-fin-btn{background:#09090b;border:1px solid #3f3f46;border-radius:6px;color:#fafafa;cursor:pointer;font-size:14px;font-weight:600;padding:5px 14px;transition:all .15s;white-space:nowrap;font-family:inherit}
|
|
157
|
+
.tb-fin-btn:hover{background:#000;border-color:#52525b}
|
|
158
|
+
|
|
159
|
+
/* \u2500\u2500 Page loading hint (toolbar + sidebar) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
160
|
+
.tb-page-loading{
|
|
161
|
+
display:none;flex-shrink:0;align-items:center;margin:0 10px 0 4px;
|
|
162
|
+
max-width:min(340px,36vw);position:relative;z-index:120
|
|
163
|
+
}
|
|
164
|
+
.ve-page-loading-msg{
|
|
165
|
+
display:flex;align-items:center;gap:5px;font-size:9px;font-weight:500;
|
|
166
|
+
color:#b45309;white-space:nowrap;user-select:none
|
|
167
|
+
}
|
|
168
|
+
.ve-pl-dot{color:#d97706;font-size:9px;line-height:1;flex-shrink:0;margin-top:1px}
|
|
169
|
+
.ve-pl-tip{
|
|
170
|
+
position:relative;display:inline-flex;align-items:center;cursor:help;
|
|
171
|
+
color:#b45309;padding:2px;border-radius:4px;outline:none
|
|
172
|
+
}
|
|
173
|
+
.ve-pl-tip:hover,.ve-pl-tip:focus{background:rgba(217,119,6,.12)}
|
|
174
|
+
.ve-pl-tip .bi{font-size:13px}
|
|
175
|
+
/* Fixed viewport positioning \u2014 avoids clipping by #main / #left-panel overflow */
|
|
176
|
+
.ve-pl-tooltip{
|
|
177
|
+
display:none;position:fixed;left:0;top:0;
|
|
178
|
+
width:min(280px,calc(100vw - 40px));padding:10px 12px 11px;border-radius:8px;
|
|
179
|
+
background:#27272a;color:#fafafa;font-size:11px;font-weight:400;line-height:1.45;
|
|
180
|
+
box-shadow:0 8px 24px rgba(0,0,0,.35),0 0 0 1px rgba(255,255,255,.06);
|
|
181
|
+
z-index:10050;pointer-events:none;text-align:left;white-space:normal;
|
|
182
|
+
transform:translateX(-50%);margin:0
|
|
183
|
+
}
|
|
184
|
+
.ve-pl-tooltip::after{
|
|
185
|
+
content:'';position:absolute;left:50%;margin-left:-6px;
|
|
186
|
+
border:6px solid transparent
|
|
187
|
+
}
|
|
188
|
+
/* Sidebar: tooltip below trigger \u2014 arrow points up */
|
|
189
|
+
.ve-pl-tip-below:not(.is-tip-flip) .ve-pl-tooltip::after{
|
|
190
|
+
top:auto;bottom:100%;border-bottom-color:#27272a;border-top-color:transparent
|
|
191
|
+
}
|
|
192
|
+
/* Toolbar: tooltip above trigger \u2014 arrow points down */
|
|
193
|
+
.ve-pl-tip:not(.ve-pl-tip-below):not(.is-tip-flip) .ve-pl-tooltip::after{
|
|
194
|
+
top:100%;border-top-color:#27272a;border-bottom-color:transparent
|
|
195
|
+
}
|
|
196
|
+
/* Flipped: sidebar showed above */
|
|
197
|
+
.ve-pl-tip-below.is-tip-flip .ve-pl-tooltip::after{
|
|
198
|
+
top:100%;bottom:auto;border-top-color:#27272a;border-bottom-color:transparent
|
|
199
|
+
}
|
|
200
|
+
/* Flipped: toolbar showed below */
|
|
201
|
+
.ve-pl-tip:not(.ve-pl-tip-below).is-tip-flip .ve-pl-tooltip::after{
|
|
202
|
+
top:auto;bottom:100%;border-bottom-color:#27272a;border-top-color:transparent
|
|
203
|
+
}
|
|
204
|
+
.ve-pl-tip.is-tip-open .ve-pl-tooltip{display:block}
|
|
205
|
+
.ve-pl-tip-hd{display:block;font-weight:700;margin-bottom:6px;color:#fff;font-size:12px}
|
|
206
|
+
.ve-pl-tip-body{color:#e4e4e7}
|
|
207
|
+
.lp-loading-banner{
|
|
208
|
+
display:none;flex-shrink:0;padding:8px 16px 10px;border-bottom:1px solid var(--border);
|
|
209
|
+
background:#fffbeb;position:relative;z-index:115;overflow:visible
|
|
210
|
+
}
|
|
211
|
+
.lp-loading-banner .ve-page-loading-msg{white-space:normal;flex-wrap:wrap;font-size:9px}
|
|
114
212
|
|
|
115
213
|
/* \u2500\u2500 Left panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
116
214
|
#left-panel{
|
|
117
|
-
width:232px;background
|
|
118
|
-
display:flex;flex-direction:column;flex-shrink:0;overflow:hidden
|
|
215
|
+
width:232px;background:#FFFFFF;border-right:1px solid var(--border);
|
|
216
|
+
display:flex;flex-direction:column;flex-shrink:0;min-height:0;overflow:hidden
|
|
119
217
|
}
|
|
120
218
|
|
|
121
219
|
/* \u2500\u2500 Iframe panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
@@ -124,6 +222,42 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
124
222
|
align-items:center;justify-content:flex-start;overflow:auto;position:relative
|
|
125
223
|
}
|
|
126
224
|
|
|
225
|
+
/* \u2500\u2500 Floating selection toolbar (above iframe selection) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
226
|
+
#selection-floater{
|
|
227
|
+
display:none;position:absolute;z-index:60;align-items:center;gap:1px;
|
|
228
|
+
background:#fff;border:1px solid var(--border);border-radius:8px;padding:3px 4px;
|
|
229
|
+
box-shadow:0 4px 20px rgba(0,0,0,.12),0 0 0 1px rgba(0,0,0,.04);
|
|
230
|
+
pointer-events:auto
|
|
231
|
+
}
|
|
232
|
+
#selection-floater .sf-btn{
|
|
233
|
+
width:30px;height:28px;border:none;background:transparent;border-radius:6px;
|
|
234
|
+
cursor:pointer;color:var(--text-2);display:flex;align-items:center;justify-content:center;
|
|
235
|
+
font-size:14px;transition:background .12s,color .12s
|
|
236
|
+
}
|
|
237
|
+
#selection-floater .sf-btn:hover{background:var(--bg-hover);color:var(--text)}
|
|
238
|
+
#selection-floater .sf-btn.active{background:var(--accent-bg);color:var(--accent-txt)}
|
|
239
|
+
#selection-floater .sf-btn:disabled{opacity:.35;cursor:not-allowed}
|
|
240
|
+
#selection-floater .sf-sep{width:1px;height:16px;background:var(--border);margin:0 2px;flex-shrink:0}
|
|
241
|
+
|
|
242
|
+
/* \u2500\u2500 DOM tree (Elements tab) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
243
|
+
.dt-tree{font-size:11px;padding:0px 0 0px 20px;user-select:none}
|
|
244
|
+
.dt-row{
|
|
245
|
+
display:flex;align-items:center;gap:2px;min-height:26px;padding:2px 8px 2px 4px;
|
|
246
|
+
cursor:pointer;color:var(--text-2);border-radius:4px;margin:0 4px
|
|
247
|
+
}
|
|
248
|
+
.dt-row:hover{background:var(--bg-hover);color:var(--text)}
|
|
249
|
+
.dt-row.dt-selected{background:#e0e7ff!important;color:var(--accent-txt)!important;outline:1px solid #a5b4fc}
|
|
250
|
+
.dt-chev{
|
|
251
|
+
width:16px;height:16px;flex-shrink:0;border:none;background:transparent;
|
|
252
|
+
cursor:pointer;color:var(--text-3);display:flex;align-items:center;justify-content:center;
|
|
253
|
+
padding:0;font-size:10px;border-radius:3px
|
|
254
|
+
}
|
|
255
|
+
.dt-chev:hover{color:var(--text)}
|
|
256
|
+
.dt-chev.dt-spacer{visibility:hidden;pointer-events:none}
|
|
257
|
+
.dt-ico{width:16px;flex-shrink:0;text-align:center;color:var(--text-3);font-size:12px}
|
|
258
|
+
.dt-lbl{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,monospace;font-size:10px}
|
|
259
|
+
.dt-muted{padding:16px 12px;text-align:center;color:var(--text-3);font-size:11px;line-height:1.4}
|
|
260
|
+
|
|
127
261
|
/* \u2500\u2500 Right panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
128
262
|
#right-panel{
|
|
129
263
|
width:252px;background:var(--bg);border-left:1px solid var(--border);
|
|
@@ -137,16 +271,6 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
137
271
|
color:var(--text-3);flex-shrink:0;gap:5px;overflow:hidden
|
|
138
272
|
}
|
|
139
273
|
|
|
140
|
-
/* \u2500\u2500 Loading overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
141
|
-
#loading{
|
|
142
|
-
position:absolute;inset:0;background:rgba(248,250,252,.95);display:flex;
|
|
143
|
-
flex-direction:column;align-items:center;justify-content:center;gap:12px;z-index:50;
|
|
144
|
-
backdrop-filter:blur(2px)
|
|
145
|
-
}
|
|
146
|
-
#loading.hidden{display:none}
|
|
147
|
-
.spinner{width:32px;height:32px;border:2.5px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .7s linear infinite}
|
|
148
|
-
@keyframes spin{to{transform:rotate(360deg)}}
|
|
149
|
-
|
|
150
274
|
/* \u2500\u2500 No-URL overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
151
275
|
#no-url{
|
|
152
276
|
display:none;position:absolute;inset:0;flex-direction:column;
|
|
@@ -195,9 +319,7 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
195
319
|
.tg-btn.active{background:#fff;color:var(--accent-txt);box-shadow:0 1px 3px rgba(0,0,0,.1)}
|
|
196
320
|
.tg-btn.icon-only{padding:4px 8px}
|
|
197
321
|
|
|
198
|
-
/* \u2500\u2500 Dirty dot \
|
|
199
|
-
#dirty-dot{width:6px;height:6px;border-radius:50%;background:transparent;flex-shrink:0;transition:background .2s}
|
|
200
|
-
#dirty-dot.on{background:#f59e0b}
|
|
322
|
+
/* \u2500\u2500 Dirty dot \u2014 styles defined in toolbar block above \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
201
323
|
|
|
202
324
|
/* \u2500\u2500 URL bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
203
325
|
#url-bar{
|
|
@@ -206,22 +328,44 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
206
328
|
}
|
|
207
329
|
|
|
208
330
|
/* \u2500\u2500 Left panel sections \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
209
|
-
.lp-sec{border-bottom:1px solid var(--border);flex-shrink:0}
|
|
331
|
+
.lp-sec{border-bottom:1px solid var(--border);flex-shrink:0;padding: 24px;}
|
|
332
|
+
.lp-sec-no-border{border-bottom:none!important}
|
|
210
333
|
.lp-sec-hd{
|
|
211
|
-
padding:
|
|
212
|
-
|
|
334
|
+
padding:10px 12px 8px;font-size:14px;font-weight:600;
|
|
335
|
+
color:#404040;display:flex;align-items:center;justify-content:space-between;gap:5px
|
|
213
336
|
}
|
|
214
|
-
.lp-sec-hd
|
|
337
|
+
.lp-sec-hd-left{display:flex;align-items:center;gap:5px}
|
|
338
|
+
#active-var-label{display:none;color:var(--accent-txt);font-size:10px;font-weight:500}
|
|
339
|
+
.lp-info-icon{font-size:11px;color:var(--text-3);cursor:default;opacity:.7}
|
|
340
|
+
.lp-add-btn{
|
|
341
|
+
display:none;
|
|
342
|
+
background:none;border:none;color:var(--text);font-size:14px;font-weight:500;
|
|
343
|
+
cursor:pointer;padding:2px 5px;border-radius:4px;transition:all .12s;flex-shrink:0
|
|
344
|
+
}
|
|
345
|
+
.lp-add-btn:hover{background:var(--bg-hover);color:var(--accent-txt)}
|
|
215
346
|
|
|
216
347
|
/* \u2500\u2500 Variation tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
217
|
-
#variation-tabs{padding:
|
|
348
|
+
#variation-tabs{padding:2px 0 6px;display:flex;flex-direction:column; gap:8px;}
|
|
218
349
|
.var-tab{
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
350
|
+
border-radius: 4px;
|
|
351
|
+
border: 1px solid #e5e7eb;
|
|
352
|
+
background:transparent;color:var(--text);
|
|
353
|
+
cursor:pointer;font-size:12px;font-weight:500;padding:6px 12px;transition:background .12s,color .12s;
|
|
354
|
+
width:100%;text-align:left;display:flex;align-items:center;gap:8px;
|
|
355
|
+
}
|
|
356
|
+
.var-tab:hover{background:var(--bg-hover);color:var(--text)}
|
|
357
|
+
.var-tab.active{background:var(--accent-bg);color:var(--accent-txt);font-weight: 700;}
|
|
358
|
+
.var-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
|
359
|
+
.var-add-row{
|
|
360
|
+
display:none!important;
|
|
361
|
+
border-radius: 4px;
|
|
362
|
+
border: 1px solid #e5e7eb;
|
|
363
|
+
background:transparent;color:var(--text);
|
|
364
|
+
cursor:pointer;font-size:12px;padding:6px 12px;
|
|
365
|
+
width:100%;text-align:left;display:flex;align-items:center;gap:8px;
|
|
366
|
+
border-radius:0;transition:color .12s
|
|
367
|
+
}
|
|
368
|
+
.var-add-row:hover{color:var(--accent-txt)}
|
|
225
369
|
|
|
226
370
|
/* \u2500\u2500 Search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
227
371
|
#comp-search{
|
|
@@ -234,11 +378,12 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
234
378
|
/* \u2500\u2500 Left tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
235
379
|
.lp-tabs{display:flex;border-bottom:1px solid var(--border);flex-shrink:0;background:#fff}
|
|
236
380
|
.lp-tab{
|
|
237
|
-
flex:1;padding:8px
|
|
238
|
-
cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;font-weight:600
|
|
381
|
+
flex:1;padding:8px 2px;text-align:center;font-size:10px;color:var(--text-3);
|
|
382
|
+
cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;font-weight:600;line-height:1.15
|
|
239
383
|
}
|
|
240
384
|
.lp-tab:hover{color:var(--text-2)}
|
|
241
385
|
.lp-tab.active{color:var(--accent-txt);border-bottom-color:var(--accent)}
|
|
386
|
+
.future-hidden{display:none!important}
|
|
242
387
|
|
|
243
388
|
/* \u2500\u2500 Left panel body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
244
389
|
.lp-body{flex:1;overflow-y:auto}
|
|
@@ -341,8 +486,9 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
341
486
|
.adv-key{font-size:10px;color:var(--text-3);margin-bottom:3px;font-weight:700;text-transform:uppercase;letter-spacing:.05em}
|
|
342
487
|
.adv-val{font-size:11px;color:var(--text-2);word-break:break-all;background:var(--bg-sub);padding:5px 7px;border-radius:5px;font-family:monospace;border:1px solid var(--border)}
|
|
343
488
|
|
|
344
|
-
/* \u2500\u2500 Selected element ring \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
345
|
-
|
|
489
|
+
/* \u2500\u2500 Selected element ring + drag affordance \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
490
|
+
/* Selection chrome is injected into the iframe (see injectIframeSelectionStyles); rules here are fallback only */
|
|
491
|
+
.vve-dragging{opacity:0.92!important;outline:2px dashed #f59e0b!important;outline-offset:2px!important;cursor:grabbing!important}
|
|
346
492
|
|
|
347
493
|
/* iframe is always interactive \u2014 mode behaviour is controlled by JS handlers */
|
|
348
494
|
#iframeId{pointer-events:all}
|
|
@@ -411,11 +557,13 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
411
557
|
transition:all .15s
|
|
412
558
|
}
|
|
413
559
|
#states-clear:hover{border-color:#fca5a5;color:#ef4444;background:#fef2f2}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
560
|
+
#history-clear{
|
|
561
|
+
display:block;width:calc(100% - 24px);margin:10px 12px;
|
|
562
|
+
background:none;border:1px solid var(--border);border-radius:6px;
|
|
563
|
+
padding:6px;font-size:11px;color:var(--text-2);cursor:pointer;font-family:inherit;
|
|
564
|
+
transition:all .15s
|
|
565
|
+
}
|
|
566
|
+
#history-clear:hover{border-color:#fca5a5;color:#ef4444;background:#fef2f2}
|
|
419
567
|
</style>
|
|
420
568
|
</head>
|
|
421
569
|
<body class="mode-editor">
|
|
@@ -423,47 +571,68 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
423
571
|
|
|
424
572
|
<!-- \u2500\u2500 Toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->
|
|
425
573
|
<div id="toolbar">
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
<div class="tb-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
<
|
|
433
|
-
<
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
<i class="bi bi-cursor"></i> Navigate
|
|
437
|
-
</button>
|
|
574
|
+
|
|
575
|
+
<!-- Left: Logo + Breadcrumb -->
|
|
576
|
+
<div class="tb-left">
|
|
577
|
+
<span class="tb-logo">\u2733</span>
|
|
578
|
+
<span class="tb-bc-item" id="tb-exp-name">Visual Editor</span>
|
|
579
|
+
<span class="tb-bc-sep">/</span>
|
|
580
|
+
<span class="tb-draft-badge">
|
|
581
|
+
<span class="tb-draft-dot"></span>
|
|
582
|
+
<span class="tb-draft-lbl">DRAFT</span>
|
|
583
|
+
</span>
|
|
438
584
|
</div>
|
|
439
585
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
<i class="bi bi-tablet"></i>
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
586
|
+
<!-- Center: Viewport + Device icons + More + Undo/Redo -->
|
|
587
|
+
<div class="tb-center">
|
|
588
|
+
<div class="tb-viewport">
|
|
589
|
+
<span id="dev-label">1440px</span>
|
|
590
|
+
<i class="bi bi-chevron-down"></i>
|
|
591
|
+
</div>
|
|
592
|
+
<div class="tb-dev-btns">
|
|
593
|
+
<button class="tb-dk-btn active" id="dev-desktop" onclick="setDevice('desktop')" title="Desktop \u2014 full width"><i class="bi bi-display"></i></button>
|
|
594
|
+
<button class="tb-dk-btn" id="dev-tablet" onclick="setDevice('tablet')" title="Tablet \u2014 768px"><i class="bi bi-tablet"></i></button>
|
|
595
|
+
<button class="tb-dk-btn" id="dev-mobile" onclick="setDevice('mobile')" title="Mobile \u2014 390px"><i class="bi bi-phone"></i></button>
|
|
596
|
+
<button class="tb-dk-btn" title="More options"><i class="bi bi-three-dots"></i></button>
|
|
597
|
+
</div>
|
|
598
|
+
<button class="tb-dk-btn" id="btn-undo" title="Undo (\u2318Z)"><i class="bi bi-arrow-counterclockwise"></i></button>
|
|
599
|
+
<button class="tb-dk-btn" id="btn-redo" title="Redo (\u2318\u21E7Z)"><i class="bi bi-arrow-clockwise"></i></button>
|
|
453
600
|
</div>
|
|
454
|
-
<span id="dev-label" style="font-size:11px;color:var(--text-3);min-width:56px">Desktop</span>
|
|
455
601
|
|
|
456
|
-
<div class="tb-
|
|
457
|
-
|
|
458
|
-
|
|
602
|
+
<div id="iframe-loading-toolbar" class="tb-page-loading" aria-live="polite" aria-atomic="true">
|
|
603
|
+
<span class="ve-page-loading-msg">
|
|
604
|
+
<span class="ve-pl-dot" aria-hidden="true">\u25CF</span>
|
|
605
|
+
<span>Page Loading. Content may shift</span>
|
|
606
|
+
<span class="ve-pl-tip" tabindex="0" aria-label="More about page loading">
|
|
607
|
+
<i class="bi bi-info-circle" aria-hidden="true"></i>
|
|
608
|
+
<span class="ve-pl-tooltip" role="tooltip">
|
|
609
|
+
<span class="ve-pl-tip-hd">Heads up! The page is still loading.</span>
|
|
610
|
+
<span class="ve-pl-tip-body">You can already start, but we recommend waiting a moment so you don't have to redo your edits.</span>
|
|
611
|
+
</span>
|
|
612
|
+
</span>
|
|
613
|
+
</span>
|
|
614
|
+
</div>
|
|
459
615
|
|
|
460
|
-
<!--
|
|
461
|
-
<
|
|
462
|
-
|
|
463
|
-
|
|
616
|
+
<!-- Right: Save status + Mode icons + Simulate + Finalize -->
|
|
617
|
+
<div class="tb-right">
|
|
618
|
+
<div class="tb-save-area">
|
|
619
|
+
<span id="dirty-dot" title="Unsaved changes"></span>
|
|
620
|
+
<span class="tb-save-txt"></span>
|
|
621
|
+
<span id="tb-save-time" class="tb-save-time"></span>
|
|
622
|
+
</div>
|
|
623
|
+
<div class="tb-dev-3btns">
|
|
624
|
+
<button class="tb-dk-btn active" id="btn-mode-editor" onclick="setMode('editor')" title="Edit mode \u2014 click elements to edit"><i class="bi bi-pencil"></i></button>
|
|
625
|
+
<button class="tb-dk-btn" id="btn-mode-nav" onclick="setMode('navigate')" title="Navigate mode \u2014 interact with page normally"><i class="bi bi-magic"></i></button>
|
|
626
|
+
<button class="tb-dk-btn" title="Comments"><i class="bi bi-chat-dots"></i></button>
|
|
627
|
+
</div>
|
|
628
|
+
<!-- btn-close: hidden visually, kept for JS event listener -->
|
|
629
|
+
<button id="btn-close" style="display:none" title="Close editor"></button>
|
|
630
|
+
<button class="tb-sim-btn" id="btn-simulate"><i class="bi bi-lightning-charge-fill"></i> Simulate</button>
|
|
631
|
+
<button class="tb-fin-btn" id="btn-save">Finalize</button>
|
|
632
|
+
</div>
|
|
464
633
|
|
|
465
|
-
|
|
466
|
-
<
|
|
634
|
+
<!-- url-bar: hidden, kept for JS compatibility -->
|
|
635
|
+
<div id="url-bar" style="display:none" title="">No page loaded</div>
|
|
467
636
|
</div>
|
|
468
637
|
|
|
469
638
|
<!-- \u2500\u2500 Main \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->
|
|
@@ -475,27 +644,51 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
475
644
|
<!-- Variations -->
|
|
476
645
|
<div class="lp-sec">
|
|
477
646
|
<div class="lp-sec-hd">
|
|
478
|
-
Variations
|
|
479
|
-
<
|
|
647
|
+
<span class="lp-sec-hd-left">Variations <span id="active-var-label"></span></span>
|
|
648
|
+
<button class="lp-add-btn" title="Add variation">+ Add</button>
|
|
480
649
|
</div>
|
|
481
650
|
<div id="variation-tabs"></div>
|
|
482
651
|
</div>
|
|
483
652
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
653
|
+
<div id="iframe-loading-sidebar" class="lp-loading-banner" aria-live="polite" aria-atomic="true">
|
|
654
|
+
<span class="ve-page-loading-msg">
|
|
655
|
+
<span class="ve-pl-dot" aria-hidden="true">\u25CF</span>
|
|
656
|
+
<span>Page Loading. Content may shift</span>
|
|
657
|
+
<span class="ve-pl-tip ve-pl-tip-below" tabindex="0" aria-label="More about page loading">
|
|
658
|
+
<i class="bi bi-info-circle" aria-hidden="true"></i>
|
|
659
|
+
<span class="ve-pl-tooltip" role="tooltip">
|
|
660
|
+
<span class="ve-pl-tip-hd">Heads up! The page is still loading.</span>
|
|
661
|
+
<span class="ve-pl-tip-body">You can already start, but we recommend waiting a moment so you don't have to redo your edits.</span>
|
|
662
|
+
</span>
|
|
663
|
+
</span>
|
|
664
|
+
</span>
|
|
665
|
+
</div>
|
|
666
|
+
|
|
667
|
+
<!-- Elements -->
|
|
668
|
+
<div class="lp-sec lp-sec-no-border">
|
|
669
|
+
<div class="lp-sec-hd">
|
|
670
|
+
<span class="lp-sec-hd-left">Elements <i class="bi bi-info-circle lp-info-icon" title="Page elements"></i></span>
|
|
671
|
+
<button class="lp-add-btn" title="Add element">+ Add</button>
|
|
672
|
+
</div>
|
|
487
673
|
</div>
|
|
488
674
|
|
|
489
|
-
<!--
|
|
490
|
-
<div
|
|
491
|
-
<
|
|
492
|
-
|
|
675
|
+
<!-- Search (hidden, kept for JS) -->
|
|
676
|
+
<div style="display:none">
|
|
677
|
+
<input type="search" id="comp-search" placeholder="Search layers\u2026" autocomplete="off">
|
|
678
|
+
</div>
|
|
679
|
+
|
|
680
|
+
<!-- Tabs (hidden, kept for JS) -->
|
|
681
|
+
<div class="lp-tabs" style="display:none">
|
|
682
|
+
<div class="lp-tab active" onclick="switchLeftTab('elements')">Elements</div>
|
|
683
|
+
<div class="lp-tab future-hidden" onclick="switchLeftTab('components')">Components</div>
|
|
684
|
+
<div class="lp-tab future-hidden" onclick="switchLeftTab('sections')">Sections</div>
|
|
493
685
|
</div>
|
|
494
686
|
|
|
495
687
|
<!-- Tab content -->
|
|
496
688
|
<div class="lp-body">
|
|
497
|
-
<div id="tab-
|
|
498
|
-
<div id="tab-
|
|
689
|
+
<div id="tab-elements" class="tab-pane active"><div id="dom-tree-root" class="dt-tree"></div></div>
|
|
690
|
+
<div id="tab-components" class="tab-pane future-hidden"></div>
|
|
691
|
+
<div id="tab-sections" class="tab-pane future-hidden"></div>
|
|
499
692
|
</div>
|
|
500
693
|
|
|
501
694
|
</div><!-- #left-panel -->
|
|
@@ -503,10 +696,17 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
503
696
|
<!-- Center / iframe panel -->
|
|
504
697
|
<div id="iframe-panel">
|
|
505
698
|
|
|
506
|
-
<!--
|
|
507
|
-
<div id="
|
|
508
|
-
<
|
|
509
|
-
<
|
|
699
|
+
<!-- Floating toolbar for selected element (positioned over iframe) -->
|
|
700
|
+
<div id="selection-floater" aria-label="Selection actions">
|
|
701
|
+
<button type="button" class="sf-btn" id="sf-drag" title="Move up/down (drag on page after activating)"><i class="bi bi-arrows-move"></i></button>
|
|
702
|
+
<span class="sf-sep"></span>
|
|
703
|
+
<button type="button" class="sf-btn" id="sf-resize" disabled title="Resize (coming soon)"><i class="bi bi-arrows-angle-expand"></i></button>
|
|
704
|
+
<button type="button" class="sf-btn" id="sf-rotate" disabled title="Rotate (coming soon)"><i class="bi bi-arrow-repeat"></i></button>
|
|
705
|
+
<button type="button" class="sf-btn" id="sf-dup" title="Duplicate"><i class="bi bi-files"></i></button>
|
|
706
|
+
<button type="button" class="sf-btn" id="sf-hide" title="Hide"><i class="bi bi-eye-slash"></i></button>
|
|
707
|
+
<button type="button" class="sf-btn" id="sf-del" title="Delete"><i class="bi bi-trash"></i></button>
|
|
708
|
+
<span class="sf-sep"></span>
|
|
709
|
+
<button type="button" class="sf-btn" id="sf-close" title="Deselect"><i class="bi bi-x-lg"></i></button>
|
|
510
710
|
</div>
|
|
511
711
|
|
|
512
712
|
<!-- No-URL overlay (absolute, covers center) -->
|
|
@@ -621,12 +821,13 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
621
821
|
</div>
|
|
622
822
|
</div><!-- #tab-states -->
|
|
623
823
|
|
|
624
|
-
<!-- \u2500\u2500 History pane (
|
|
824
|
+
<!-- \u2500\u2500 History pane (saved DB changesets for active variation) \u2500\u2500 -->
|
|
625
825
|
<div id="tab-history" class="rp-pane">
|
|
626
|
-
<div
|
|
627
|
-
<
|
|
628
|
-
|
|
629
|
-
|
|
826
|
+
<div id="history-list">
|
|
827
|
+
<div class="states-empty">
|
|
828
|
+
<i class="bi bi-clock-history"></i>
|
|
829
|
+
No saved changesets for this variation
|
|
830
|
+
</div>
|
|
630
831
|
</div>
|
|
631
832
|
</div><!-- #tab-history -->
|
|
632
833
|
|
|
@@ -652,11 +853,16 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
652
853
|
<script>
|
|
653
854
|
/* Safety stub: if components.js didn't define these, create empty arrays so
|
|
654
855
|
components-bootstrap5.js doesn't throw ReferenceError on load. */
|
|
655
|
-
if (typeof bgcolorClasses
|
|
656
|
-
if (typeof colorClasses
|
|
657
|
-
if (typeof textColorClasses=== 'undefined') window.textColorClasses= [];
|
|
658
|
-
if (typeof borderClasses
|
|
659
|
-
if (typeof sizeClasses
|
|
856
|
+
if (typeof bgcolorClasses === 'undefined') window.bgcolorClasses = [];
|
|
857
|
+
if (typeof colorClasses === 'undefined') window.colorClasses = [];
|
|
858
|
+
if (typeof textColorClasses === 'undefined') window.textColorClasses = [];
|
|
859
|
+
if (typeof borderClasses === 'undefined') window.borderClasses = [];
|
|
860
|
+
if (typeof sizeClasses === 'undefined') window.sizeClasses = [];
|
|
861
|
+
if (typeof bgcolorSelectOptions === 'undefined') window.bgcolorSelectOptions = [];
|
|
862
|
+
if (typeof colorSelectOptions === 'undefined') window.colorSelectOptions = [];
|
|
863
|
+
if (typeof textColorSelectOptions=== 'undefined') window.textColorSelectOptions= [];
|
|
864
|
+
if (typeof borderSelectOptions === 'undefined') window.borderSelectOptions = [];
|
|
865
|
+
if (typeof sizeSelectOptions === 'undefined') window.sizeSelectOptions = [];
|
|
660
866
|
</script>
|
|
661
867
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/components-bootstrap5.js"></script>
|
|
662
868
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/components-widgets.js"></script>
|
|
@@ -708,15 +914,45 @@ var experimentData = null;
|
|
|
708
914
|
var variations = [];
|
|
709
915
|
var activeVarId = null;
|
|
710
916
|
var varHtmlCache = {};
|
|
917
|
+
/** Last iframe proxy URL we navigated to \u2014 used to skip redundant reloads when parent re-sends load-experiment */
|
|
918
|
+
var lastLoadedProxyUrl = '';
|
|
919
|
+
/** API changeset rows (excluding __vvveb_body__) reapplied until selectors match late-hydrated DOM */
|
|
920
|
+
var pendingGranularChangesets = null;
|
|
921
|
+
var pendingGranularVarId = null;
|
|
922
|
+
var granularReapplyTimer = null;
|
|
923
|
+
var granularReapplyAttempts = 0;
|
|
924
|
+
var GRANULAR_REAPPLY_MAX = 80;
|
|
925
|
+
/** Bumped on each iframe navigation so stale apply timers exit */
|
|
926
|
+
var iframeContentNavGen = 0;
|
|
927
|
+
var iframeContentApplyTimer = null;
|
|
928
|
+
var iframeEarlyGranularPrimedForGen = null;
|
|
929
|
+
var iframeEarlySyncPrimedForGen = null;
|
|
711
930
|
var isDirty = false;
|
|
712
931
|
var vvvebReady = false;
|
|
713
932
|
var currentMode = 'editor';
|
|
714
933
|
var currentDevice = 'desktop';
|
|
715
934
|
var selectedEl = null;
|
|
935
|
+
var suppressClickUntil = 0;
|
|
936
|
+
var dragAttachDoc = null;
|
|
716
937
|
var currentMainTab = 'design';
|
|
938
|
+
var currentLeftTab = 'elements';
|
|
939
|
+
var dragHandleActive = false;
|
|
940
|
+
var domTreeCollapsed = {};
|
|
941
|
+
var domTreeRefreshTimer = null;
|
|
942
|
+
var iframeSyncRetryTimer = null;
|
|
943
|
+
var iframeSyncAttempts = 0;
|
|
944
|
+
var selectionScrollWin = null;
|
|
945
|
+
var selectionResizeBound = false;
|
|
946
|
+
var clickAttachDoc = null;
|
|
947
|
+
var changeObserver = null;
|
|
948
|
+
var changeObserverDoc = null;
|
|
949
|
+
/** { doc, onRS } \u2014 iframe document readystate until complete */
|
|
950
|
+
var iframeDocLoadingListeners = null;
|
|
717
951
|
// Each entry: {selector, label, cssProp, value, targetEl}
|
|
718
952
|
// cssProp is null for non-CSS attributes (href, alt, classes\u2026)
|
|
719
953
|
var stateChanges = [];
|
|
954
|
+
/** Pre-apply DOM snapshots for granular + body changesets (used when removing History rows) */
|
|
955
|
+
var appliedChangesetSnapshots = {};
|
|
720
956
|
|
|
721
957
|
// \u2500\u2500 Dirty tracking \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
722
958
|
function markDirty() {
|
|
@@ -724,9 +960,19 @@ function markDirty() {
|
|
|
724
960
|
document.getElementById('dirty-dot').classList.add('on');
|
|
725
961
|
send('mutations-changed', {});
|
|
726
962
|
}
|
|
963
|
+
var savedAt = null;
|
|
964
|
+
function updateSaveTime() {
|
|
965
|
+
var el = document.getElementById('tb-save-time');
|
|
966
|
+
if (!el || !savedAt) return;
|
|
967
|
+
var s = Math.floor((Date.now() - savedAt) / 1000);
|
|
968
|
+
el.textContent = s < 60 ? s + 's ago' : Math.floor(s / 60) + 'm ago';
|
|
969
|
+
}
|
|
970
|
+
setInterval(updateSaveTime, 10000);
|
|
727
971
|
function markClean() {
|
|
728
972
|
isDirty = false;
|
|
973
|
+
savedAt = Date.now();
|
|
729
974
|
document.getElementById('dirty-dot').classList.remove('on');
|
|
975
|
+
updateSaveTime();
|
|
730
976
|
}
|
|
731
977
|
|
|
732
978
|
// \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
|
|
@@ -735,11 +981,15 @@ function setMode(mode) {
|
|
|
735
981
|
document.body.className = 'mode-' + mode;
|
|
736
982
|
document.getElementById('btn-mode-editor').classList.toggle('active', mode === 'editor');
|
|
737
983
|
document.getElementById('btn-mode-nav').classList.toggle('active', mode === 'navigate');
|
|
738
|
-
if (mode === 'navigate')
|
|
984
|
+
if (mode === 'navigate') {
|
|
985
|
+
setDragHandleActive(false);
|
|
986
|
+
deselectElement();
|
|
987
|
+
}
|
|
988
|
+
updateSelectionToolbar();
|
|
739
989
|
}
|
|
740
990
|
|
|
741
991
|
// \u2500\u2500 Device toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
742
|
-
var DEVICE_LABELS = { desktop: '
|
|
992
|
+
var DEVICE_LABELS = { desktop: '1440px', tablet: '768px', mobile: '390px' };
|
|
743
993
|
function setDevice(device) {
|
|
744
994
|
currentDevice = device;
|
|
745
995
|
var frame = document.getElementById('device-frame');
|
|
@@ -752,11 +1002,25 @@ function setDevice(device) {
|
|
|
752
1002
|
|
|
753
1003
|
// \u2500\u2500 Left panel tab switch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
754
1004
|
function switchLeftTab(tab) {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1005
|
+
currentLeftTab = tab;
|
|
1006
|
+
var tabs = document.querySelectorAll('.lp-tab');
|
|
1007
|
+
tabs[0].classList.toggle('active', tab === 'elements');
|
|
1008
|
+
tabs[1].classList.toggle('active', tab === 'components');
|
|
1009
|
+
tabs[2].classList.toggle('active', tab === 'sections');
|
|
1010
|
+
document.getElementById('tab-elements').classList.toggle('active', tab === 'elements');
|
|
758
1011
|
document.getElementById('tab-components').classList.toggle('active', tab === 'components');
|
|
759
1012
|
document.getElementById('tab-sections').classList.toggle('active', tab === 'sections');
|
|
1013
|
+
var inp = document.getElementById('comp-search');
|
|
1014
|
+
if (tab === 'elements') {
|
|
1015
|
+
inp.placeholder = 'Search layers\u2026';
|
|
1016
|
+
renderDomTree(inp.value);
|
|
1017
|
+
} else if (tab === 'sections') {
|
|
1018
|
+
inp.placeholder = 'Search sections\u2026';
|
|
1019
|
+
renderSidebar(inp.value);
|
|
1020
|
+
} else {
|
|
1021
|
+
inp.placeholder = 'Search components\u2026';
|
|
1022
|
+
renderSidebar(inp.value);
|
|
1023
|
+
}
|
|
760
1024
|
}
|
|
761
1025
|
|
|
762
1026
|
// \u2500\u2500 Accordion toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -776,6 +1040,7 @@ function switchMainTab(tab) {
|
|
|
776
1040
|
if (pane) pane.classList.toggle('active', t === tab);
|
|
777
1041
|
});
|
|
778
1042
|
if (tab === 'states') renderStatesTab();
|
|
1043
|
+
if (tab === 'history') renderHistoryTab();
|
|
779
1044
|
}
|
|
780
1045
|
|
|
781
1046
|
// \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
|
|
@@ -1004,55 +1269,547 @@ function clearAllStates() {
|
|
|
1004
1269
|
markDirty();
|
|
1005
1270
|
}
|
|
1006
1271
|
|
|
1272
|
+
// \u2500\u2500 History tab (saved changesets from DB for active variation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1273
|
+
function getActiveVariationForHistory() {
|
|
1274
|
+
return variations.find(function(v) { return v._id === activeVarId; });
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
function persistActiveVariationChangesets(arr) {
|
|
1278
|
+
var v = variations.find(function(x) { return x._id === activeVarId; });
|
|
1279
|
+
if (!v) return;
|
|
1280
|
+
var json = JSON.stringify(arr);
|
|
1281
|
+
v.changesets = json;
|
|
1282
|
+
if (experimentData && Array.isArray(experimentData.variations)) {
|
|
1283
|
+
for (var i = 0; i < experimentData.variations.length; i++) {
|
|
1284
|
+
if (experimentData.variations[i]._id === activeVarId) {
|
|
1285
|
+
experimentData.variations[i].changesets = json;
|
|
1286
|
+
break;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function entrySnapshotKey(entry) {
|
|
1293
|
+
if (!entry || !entry.selector) return '';
|
|
1294
|
+
return (
|
|
1295
|
+
entry.selector +
|
|
1296
|
+
'\0' +
|
|
1297
|
+
(entry.type || '') +
|
|
1298
|
+
'\0' +
|
|
1299
|
+
String(entry.property || '') +
|
|
1300
|
+
'\0' +
|
|
1301
|
+
String(entry.attribute || '') +
|
|
1302
|
+
'\0' +
|
|
1303
|
+
String(entry.action || '') +
|
|
1304
|
+
'\0' +
|
|
1305
|
+
String(entry.html != null ? 'h' + String(entry.html).length : '') +
|
|
1306
|
+
'\0' +
|
|
1307
|
+
String(entry.value != null ? 'v:' + entry.value : '')
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
function captureChangesetSnapshotBeforeApply(entry, el, iframeDoc) {
|
|
1312
|
+
if (!entry || !el || entry.selector === '__vvveb_body__') return;
|
|
1313
|
+
var k = entrySnapshotKey(entry);
|
|
1314
|
+
if (appliedChangesetSnapshots[k]) return;
|
|
1315
|
+
switch (entry.type) {
|
|
1316
|
+
case 'content':
|
|
1317
|
+
if (entry.html != null) {
|
|
1318
|
+
appliedChangesetSnapshots[k] = { kind: 'innerHTML', v: el.innerHTML };
|
|
1319
|
+
} else if (entry.value != null) {
|
|
1320
|
+
appliedChangesetSnapshots[k] = { kind: 'textContent', v: el.textContent };
|
|
1321
|
+
}
|
|
1322
|
+
break;
|
|
1323
|
+
case 'style':
|
|
1324
|
+
if (entry.property) {
|
|
1325
|
+
appliedChangesetSnapshots[k] = { kind: 'styleBlock', v: el.getAttribute('style') || '' };
|
|
1326
|
+
}
|
|
1327
|
+
break;
|
|
1328
|
+
case 'attribute':
|
|
1329
|
+
if (entry.attribute) {
|
|
1330
|
+
appliedChangesetSnapshots[k] = {
|
|
1331
|
+
kind: 'attribute',
|
|
1332
|
+
name: entry.attribute,
|
|
1333
|
+
v: el.getAttribute(entry.attribute),
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
break;
|
|
1337
|
+
case 'remove':
|
|
1338
|
+
appliedChangesetSnapshots[k] = { kind: 'display', v: el.style.display || '' };
|
|
1339
|
+
break;
|
|
1340
|
+
case 'insert':
|
|
1341
|
+
appliedChangesetSnapshots[k] = { kind: 'insert' };
|
|
1342
|
+
break;
|
|
1343
|
+
default:
|
|
1344
|
+
appliedChangesetSnapshots[k] = { kind: 'unknown' };
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
function softReloadEditorIframe() {
|
|
1349
|
+
var iframe = document.getElementById('iframeId');
|
|
1350
|
+
if (!iframe) return;
|
|
1351
|
+
var src = iframe.src || lastLoadedProxyUrl;
|
|
1352
|
+
if (!src || src === 'about:blank') return;
|
|
1353
|
+
var navGen = nextIframeContentNavGen();
|
|
1354
|
+
resetIframeBindings();
|
|
1355
|
+
setIframePageLoadingUi(true);
|
|
1356
|
+
iframe.src = '';
|
|
1357
|
+
iframe.src = appendIframeReloadBust(src);
|
|
1358
|
+
startIframeContentApplyWatcher(navGen);
|
|
1359
|
+
scheduleDomTreeRefresh();
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
/** @returns {boolean} true if a full iframe reload was started */
|
|
1363
|
+
function revertChangesetEntryOnDom(entry) {
|
|
1364
|
+
if (!entry) return false;
|
|
1365
|
+
if (entry.selector === '__vvveb_body__') {
|
|
1366
|
+
var iframeDoc0 = document.getElementById('iframeId').contentDocument;
|
|
1367
|
+
if (!iframeDoc0 || !iframeDoc0.body) return false;
|
|
1368
|
+
var bk = '__vvveb_body__|snapshot';
|
|
1369
|
+
var snapBody = appliedChangesetSnapshots[bk];
|
|
1370
|
+
if (snapBody && snapBody.kind === 'bodyHTML') {
|
|
1371
|
+
iframeDoc0.body.innerHTML = snapBody.v;
|
|
1372
|
+
}
|
|
1373
|
+
delete appliedChangesetSnapshots[bk];
|
|
1374
|
+
try {
|
|
1375
|
+
delete varHtmlCache[activeVarId];
|
|
1376
|
+
} catch(_) {}
|
|
1377
|
+
return false;
|
|
1378
|
+
}
|
|
1379
|
+
var iframeDoc = document.getElementById('iframeId').contentDocument;
|
|
1380
|
+
if (!iframeDoc) return false;
|
|
1381
|
+
var k = entrySnapshotKey(entry);
|
|
1382
|
+
var snap = appliedChangesetSnapshots[k];
|
|
1383
|
+
var el = null;
|
|
1384
|
+
try {
|
|
1385
|
+
el = iframeDoc.querySelector(entry.selector);
|
|
1386
|
+
} catch(_) {}
|
|
1387
|
+
if (!snap || !el) {
|
|
1388
|
+
softReloadEditorIframe();
|
|
1389
|
+
delete appliedChangesetSnapshots[k];
|
|
1390
|
+
return true;
|
|
1391
|
+
}
|
|
1392
|
+
if (snap.kind === 'innerHTML') el.innerHTML = snap.v;
|
|
1393
|
+
else if (snap.kind === 'textContent') el.textContent = snap.v;
|
|
1394
|
+
else if (snap.kind === 'styleBlock') {
|
|
1395
|
+
if (snap.v) el.setAttribute('style', snap.v);
|
|
1396
|
+
else el.removeAttribute('style');
|
|
1397
|
+
} else if (snap.kind === 'attribute' && snap.name) {
|
|
1398
|
+
if (snap.v == null || snap.v === '') el.removeAttribute(snap.name);
|
|
1399
|
+
else el.setAttribute(snap.name, snap.v);
|
|
1400
|
+
} else if (snap.kind === 'display') el.style.display = snap.v;
|
|
1401
|
+
else {
|
|
1402
|
+
softReloadEditorIframe();
|
|
1403
|
+
delete appliedChangesetSnapshots[k];
|
|
1404
|
+
return true;
|
|
1405
|
+
}
|
|
1406
|
+
delete appliedChangesetSnapshots[k];
|
|
1407
|
+
return false;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
function historyEntryTypeLabel(entry) {
|
|
1411
|
+
if (!entry) return 'Change';
|
|
1412
|
+
if (entry.selector === '__vvveb_body__') return 'Full page HTML';
|
|
1413
|
+
var t = (entry.type || '').toLowerCase();
|
|
1414
|
+
if (t === 'content') return entry.html != null ? 'Inner HTML' : 'Text / content';
|
|
1415
|
+
if (t === 'style') return 'Style: ' + (entry.property || '');
|
|
1416
|
+
if (t === 'attribute') return 'Attribute: ' + (entry.attribute || '');
|
|
1417
|
+
if (t === 'insert') return 'Insert HTML';
|
|
1418
|
+
if (t === 'remove') return 'Hide element';
|
|
1419
|
+
return t || 'Change';
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
function historyEntryValuePreview(entry) {
|
|
1423
|
+
if (!entry) return '';
|
|
1424
|
+
if (entry.selector === '__vvveb_body__') return '(body snapshot)';
|
|
1425
|
+
if (entry.html != null) return String(entry.html).slice(0, 120);
|
|
1426
|
+
if (entry.value != null) return String(entry.value).slice(0, 120);
|
|
1427
|
+
if (entry.type === 'style' || entry.type === 'attribute') return String(entry.value != null ? entry.value : '').slice(0, 120);
|
|
1428
|
+
return '';
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function renderHistoryTab() {
|
|
1432
|
+
var container = document.getElementById('history-list');
|
|
1433
|
+
if (!container) return;
|
|
1434
|
+
var v = getActiveVariationForHistory();
|
|
1435
|
+
var arr = v ? parseVariationChangesets(v) : [];
|
|
1436
|
+
if (!arr.length) {
|
|
1437
|
+
container.innerHTML =
|
|
1438
|
+
'<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>';
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
var groups = {};
|
|
1442
|
+
var order = [];
|
|
1443
|
+
for (var gi = 0; gi < arr.length; gi++) {
|
|
1444
|
+
var entry = arr[gi];
|
|
1445
|
+
var sel = entry.selector || '(unknown)';
|
|
1446
|
+
if (!groups[sel]) {
|
|
1447
|
+
groups[sel] = [];
|
|
1448
|
+
order.push(sel);
|
|
1449
|
+
}
|
|
1450
|
+
groups[sel].push({ entry: entry, idx: gi });
|
|
1451
|
+
}
|
|
1452
|
+
var html =
|
|
1453
|
+
'<button type="button" id="history-clear" onclick="clearAllHistoryChangesets()"><i class="bi bi-trash3"></i> Clear all saved changes</button>';
|
|
1454
|
+
order.forEach(function(sel) {
|
|
1455
|
+
html += '<div class="state-group"><div class="state-group-sel">' + esc(sel) + '</div>';
|
|
1456
|
+
groups[sel].forEach(function(item) {
|
|
1457
|
+
var lab = historyEntryTypeLabel(item.entry);
|
|
1458
|
+
var val = historyEntryValuePreview(item.entry);
|
|
1459
|
+
html +=
|
|
1460
|
+
'<div class="state-item">' +
|
|
1461
|
+
'<span class="state-item-label">' +
|
|
1462
|
+
esc(lab) +
|
|
1463
|
+
'</span>' +
|
|
1464
|
+
'<span class="state-item-val" title="' +
|
|
1465
|
+
esc(val) +
|
|
1466
|
+
'">' +
|
|
1467
|
+
esc(val) +
|
|
1468
|
+
'</span>' +
|
|
1469
|
+
'<button type="button" class="state-remove" title="Remove from saved changesets" onclick="removeHistoryChangeset(' +
|
|
1470
|
+
item.idx +
|
|
1471
|
+
')">✕</button>' +
|
|
1472
|
+
'</div>';
|
|
1473
|
+
});
|
|
1474
|
+
html += '</div>';
|
|
1475
|
+
});
|
|
1476
|
+
container.innerHTML = html;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
function removeHistoryChangeset(idx) {
|
|
1480
|
+
var v = getActiveVariationForHistory();
|
|
1481
|
+
if (!v) return;
|
|
1482
|
+
var arr = parseVariationChangesets(v);
|
|
1483
|
+
if (idx < 0 || idx >= arr.length) return;
|
|
1484
|
+
var removed = arr[idx];
|
|
1485
|
+
arr.splice(idx, 1);
|
|
1486
|
+
persistActiveVariationChangesets(arr);
|
|
1487
|
+
var didReload = revertChangesetEntryOnDom(removed);
|
|
1488
|
+
try {
|
|
1489
|
+
delete varHtmlCache[activeVarId];
|
|
1490
|
+
} catch(_) {}
|
|
1491
|
+
if (!didReload) {
|
|
1492
|
+
try {
|
|
1493
|
+
applyActiveVariationHtml();
|
|
1494
|
+
registerPendingGranularChangesets(
|
|
1495
|
+
arr,
|
|
1496
|
+
document.getElementById('iframeId').contentDocument,
|
|
1497
|
+
);
|
|
1498
|
+
} catch(_) {}
|
|
1499
|
+
saveCurrentVariationHtml();
|
|
1500
|
+
}
|
|
1501
|
+
if (currentMainTab === 'history') renderHistoryTab();
|
|
1502
|
+
markDirty();
|
|
1503
|
+
scheduleDomTreeRefresh();
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
function clearAllHistoryChangesets() {
|
|
1507
|
+
var v = getActiveVariationForHistory();
|
|
1508
|
+
if (!v) return;
|
|
1509
|
+
if (!parseVariationChangesets(v).length) return;
|
|
1510
|
+
persistActiveVariationChangesets([]);
|
|
1511
|
+
appliedChangesetSnapshots = {};
|
|
1512
|
+
try {
|
|
1513
|
+
delete varHtmlCache[activeVarId];
|
|
1514
|
+
} catch(_) {}
|
|
1515
|
+
if (currentMainTab === 'history') renderHistoryTab();
|
|
1516
|
+
markDirty();
|
|
1517
|
+
scheduleDomTreeRefresh();
|
|
1518
|
+
softReloadEditorIframe();
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1007
1521
|
// \u2500\u2500 Experiment loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1008
1522
|
function handleLoadExperiment(data) {
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1523
|
+
clearPendingGranularChangesets();
|
|
1524
|
+
var prevKey = experimentData
|
|
1525
|
+
? String(experimentData.experimentId || '') + '|' + String(experimentData.pageUrl || '')
|
|
1526
|
+
: '';
|
|
1527
|
+
var nextKey = String((data && data.experimentId) || '') + '|' + String((data && data.pageUrl) || '');
|
|
1015
1528
|
var pageUrl = data.pageUrl || '';
|
|
1016
1529
|
if (!pageUrl) {
|
|
1017
|
-
document.getElementById('loading').classList.add('hidden');
|
|
1018
1530
|
showNoUrl(true);
|
|
1531
|
+
lastLoadedProxyUrl = '';
|
|
1019
1532
|
return;
|
|
1020
1533
|
}
|
|
1534
|
+
var proxyUrl = '/api/conversion-proxy?password=' + encodeURIComponent(data.editorPassword || '') +
|
|
1535
|
+
'&url=' + encodeURIComponent(pageUrl);
|
|
1536
|
+
|
|
1537
|
+
// Parent often re-posts load-experiment when React re-renders (new object identity) or
|
|
1538
|
+
// after mutations-changed. Reloading the iframe again wipes variant changesets mid-session.
|
|
1539
|
+
var hadNav = !!lastLoadedProxyUrl;
|
|
1540
|
+
var sameExperimentPage = !!experimentData && prevKey === nextKey;
|
|
1541
|
+
var skipUrlReload = !!(data && data.skipUrlReload);
|
|
1542
|
+
var skipReload = skipUrlReload || (sameExperimentPage && hadNav && lastLoadedProxyUrl === proxyUrl);
|
|
1543
|
+
|
|
1544
|
+
if (skipReload) {
|
|
1545
|
+
experimentData = data;
|
|
1546
|
+
variations = Array.isArray(data.variations) ? data.variations : [];
|
|
1547
|
+
var prevActive = activeVarId;
|
|
1548
|
+
var baseline = variations.find(function(v) { return v.baseline; });
|
|
1549
|
+
var fallback = (baseline || variations[0] || {})._id || null;
|
|
1550
|
+
activeVarId =
|
|
1551
|
+
prevActive && variations.some(function(v) { return v._id === prevActive; }) ? prevActive : fallback;
|
|
1552
|
+
renderVariationTabs();
|
|
1553
|
+
var urlBarSkip = document.getElementById('url-bar');
|
|
1554
|
+
urlBarSkip.textContent = pageUrl;
|
|
1555
|
+
urlBarSkip.title = pageUrl;
|
|
1556
|
+
try {
|
|
1557
|
+
applyActiveVariationHtml();
|
|
1558
|
+
syncIframeInteractions('load-experiment-skip-url');
|
|
1559
|
+
} catch(_) {}
|
|
1560
|
+
try {
|
|
1561
|
+
var ifrSkip = document.getElementById('iframeId');
|
|
1562
|
+
if (ifrSkip && ifrSkip.contentDocument) attachIframeLoadingUntilComplete(ifrSkip);
|
|
1563
|
+
} catch(_) {}
|
|
1564
|
+
if (currentMainTab === 'history') renderHistoryTab();
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
if (!experimentData || prevKey !== nextKey) {
|
|
1569
|
+
varHtmlCache = {};
|
|
1570
|
+
appliedChangesetSnapshots = {};
|
|
1571
|
+
}
|
|
1572
|
+
experimentData = data;
|
|
1573
|
+
variations = Array.isArray(data.variations) ? data.variations : [];
|
|
1574
|
+
// New document load: start on baseline so the first paint matches the live page.
|
|
1575
|
+
var baseline = variations.find(function(v) { return v.baseline; });
|
|
1576
|
+
activeVarId = (baseline || variations[0] || {})._id || null;
|
|
1577
|
+
renderVariationTabs();
|
|
1578
|
+
|
|
1021
1579
|
var urlBar = document.getElementById('url-bar');
|
|
1022
1580
|
urlBar.textContent = pageUrl;
|
|
1023
1581
|
urlBar.title = pageUrl;
|
|
1024
|
-
|
|
1025
|
-
'&url=' + encodeURIComponent(pageUrl);
|
|
1582
|
+
if (currentMainTab === 'history') renderHistoryTab();
|
|
1026
1583
|
loadPage(proxyUrl);
|
|
1027
1584
|
}
|
|
1028
1585
|
|
|
1029
1586
|
function showNoUrl(show) {
|
|
1030
1587
|
document.getElementById('no-url').style.display = show ? 'flex' : 'none';
|
|
1588
|
+
if (show) {
|
|
1589
|
+
detachIframeLoadingListeners();
|
|
1590
|
+
setIframePageLoadingUi(false);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
function detachIframeLoadingListeners() {
|
|
1595
|
+
if (!iframeDocLoadingListeners) return;
|
|
1596
|
+
try {
|
|
1597
|
+
iframeDocLoadingListeners.doc.removeEventListener(
|
|
1598
|
+
'readystatechange',
|
|
1599
|
+
iframeDocLoadingListeners.onRS,
|
|
1600
|
+
);
|
|
1601
|
+
} catch(_) {}
|
|
1602
|
+
iframeDocLoadingListeners = null;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
function setIframePageLoadingUi(visible) {
|
|
1606
|
+
var disp = visible ? 'flex' : 'none';
|
|
1607
|
+
var tb = document.getElementById('iframe-loading-toolbar');
|
|
1608
|
+
var sb = document.getElementById('iframe-loading-sidebar');
|
|
1609
|
+
if (tb) tb.style.display = disp;
|
|
1610
|
+
if (sb) sb.style.display = disp;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
function updateIframeLoadingFromDocument(iframe, doc) {
|
|
1614
|
+
if (!iframe || !doc) {
|
|
1615
|
+
setIframePageLoadingUi(false);
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
if (!iframe.src || iframe.src === 'about:blank') {
|
|
1619
|
+
setIframePageLoadingUi(false);
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
try {
|
|
1623
|
+
if (!iframeDocMatchesNavigatedSrc(iframe, doc)) return;
|
|
1624
|
+
} catch(_) {
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
if (doc.readyState === 'complete') {
|
|
1628
|
+
setIframePageLoadingUi(false);
|
|
1629
|
+
detachIframeLoadingListeners();
|
|
1630
|
+
} else {
|
|
1631
|
+
setIframePageLoadingUi(true);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
function attachIframeLoadingUntilComplete(iframe) {
|
|
1636
|
+
detachIframeLoadingListeners();
|
|
1637
|
+
if (!iframe) return;
|
|
1638
|
+
var doc = null;
|
|
1639
|
+
try {
|
|
1640
|
+
doc = iframe.contentDocument;
|
|
1641
|
+
} catch(_) {}
|
|
1642
|
+
if (!doc) {
|
|
1643
|
+
setIframePageLoadingUi(true);
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
try {
|
|
1647
|
+
if (!iframeDocMatchesNavigatedSrc(iframe, doc)) return;
|
|
1648
|
+
} catch(_) {
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
function onRS() {
|
|
1652
|
+
updateIframeLoadingFromDocument(iframe, iframe.contentDocument);
|
|
1653
|
+
}
|
|
1654
|
+
updateIframeLoadingFromDocument(iframe, doc);
|
|
1655
|
+
if (doc.readyState !== 'complete') {
|
|
1656
|
+
iframeDocLoadingListeners = { doc: doc, onRS: onRS };
|
|
1657
|
+
doc.addEventListener('readystatechange', onRS);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
/** Tags we ignore when deciding body has \u201Creal\u201D content (matches DOM tree skippable). */
|
|
1662
|
+
function isDomTreeSkippableTagName(tagName) {
|
|
1663
|
+
var t = (tagName || '').toUpperCase();
|
|
1664
|
+
return t === 'SCRIPT' || t === 'STYLE' || t === 'NOSCRIPT' || t === 'LINK' || t === 'META' || t === 'TITLE';
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
/** True when body has at least one direct element child that is not script/style/etc. */
|
|
1668
|
+
function bodyHasFirstPaintChild(body) {
|
|
1669
|
+
if (!body) return false;
|
|
1670
|
+
var ch = body.children;
|
|
1671
|
+
for (var i = 0; i < ch.length; i++) {
|
|
1672
|
+
if (!isDomTreeSkippableTagName(ch[i].tagName)) return true;
|
|
1673
|
+
}
|
|
1674
|
+
return false;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
/** True when at least one granular changeset selector already matches (nested content painted). */
|
|
1678
|
+
function granularAnySelectorMatches(doc, cs) {
|
|
1679
|
+
if (!doc || !cs || !cs.length) return false;
|
|
1680
|
+
var g = filterGranularChangesetEntries(cs);
|
|
1681
|
+
for (var i = 0; i < g.length; i++) {
|
|
1682
|
+
try {
|
|
1683
|
+
if (doc.querySelector(g[i].selector)) return true;
|
|
1684
|
+
} catch(_) {}
|
|
1685
|
+
}
|
|
1686
|
+
return false;
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
/** Bust bfcache / same-URL no-op reloads so the iframe actually re-parses (loading \u2192 interactive). */
|
|
1690
|
+
function appendIframeReloadBust(url) {
|
|
1691
|
+
if (!url || url === 'about:blank') return url;
|
|
1692
|
+
var stamp = Date.now();
|
|
1693
|
+
if (url.indexOf('__ve_reload=') !== -1) {
|
|
1694
|
+
return url.replace(/([&?])__ve_reload=[0-9]+/, '$1__ve_reload=' + stamp);
|
|
1695
|
+
}
|
|
1696
|
+
var sep = url.indexOf('?') !== -1 ? '&' : '?';
|
|
1697
|
+
return url + sep + '__ve_reload=' + stamp;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// True when the iframe contentDocument belongs to the current iframe.src navigation.
|
|
1701
|
+
// After src is updated, the old document can still read interactive/complete briefly;
|
|
1702
|
+
// applying variation changesets there is then wiped when the real navigation commits.
|
|
1703
|
+
function iframeDocMatchesNavigatedSrc(iframe, doc) {
|
|
1704
|
+
if (!iframe || !doc) return false;
|
|
1705
|
+
var src = iframe.src || iframe.getAttribute('src') || '';
|
|
1706
|
+
if (!src || src === 'about:blank') return false;
|
|
1707
|
+
var loc = '';
|
|
1708
|
+
try {
|
|
1709
|
+
loc = String(doc.URL || '');
|
|
1710
|
+
} catch(_) {
|
|
1711
|
+
return false;
|
|
1712
|
+
}
|
|
1713
|
+
if (!loc || loc === 'about:blank') return false;
|
|
1714
|
+
var rmSrc = src.match(/__ve_reload=([0-9]+)/);
|
|
1715
|
+
if (rmSrc) return loc.indexOf('__ve_reload=' + rmSrc[1]) !== -1;
|
|
1716
|
+
try {
|
|
1717
|
+
var base = window.location.href;
|
|
1718
|
+
var su = new URL(src, base);
|
|
1719
|
+
var du = new URL(loc, base);
|
|
1720
|
+
return su.pathname + su.search === du.pathname + du.search;
|
|
1721
|
+
} catch(_) {
|
|
1722
|
+
return false;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
function nextIframeContentNavGen() {
|
|
1727
|
+
iframeContentNavGen += 1;
|
|
1728
|
+
return iframeContentNavGen;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
function stopIframeContentApplyWatcher() {
|
|
1732
|
+
if (iframeContentApplyTimer) {
|
|
1733
|
+
clearInterval(iframeContentApplyTimer);
|
|
1734
|
+
iframeContentApplyTimer = null;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
function clearPendingGranularChangesets() {
|
|
1739
|
+
pendingGranularChangesets = null;
|
|
1740
|
+
pendingGranularVarId = null;
|
|
1741
|
+
granularReapplyAttempts = 0;
|
|
1742
|
+
if (granularReapplyTimer) {
|
|
1743
|
+
clearTimeout(granularReapplyTimer);
|
|
1744
|
+
granularReapplyTimer = null;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
function resetIframeBindings() {
|
|
1749
|
+
detachIframeLoadingListeners();
|
|
1750
|
+
stopIframeContentApplyWatcher();
|
|
1751
|
+
appliedChangesetSnapshots = {};
|
|
1752
|
+
clickAttachDoc = null;
|
|
1753
|
+
dragAttachDoc = null;
|
|
1754
|
+
changeObserverDoc = null;
|
|
1755
|
+
clearPendingGranularChangesets();
|
|
1756
|
+
if (changeObserver) {
|
|
1757
|
+
try { changeObserver.disconnect(); } catch(_) {}
|
|
1758
|
+
changeObserver = null;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
function isIframeDomReady(iframe, doc) {
|
|
1763
|
+
if (!iframe || !doc || !doc.body) return false;
|
|
1764
|
+
var src = iframe.getAttribute('src') || '';
|
|
1765
|
+
var docUrl = '';
|
|
1766
|
+
try { docUrl = String(doc.URL || ''); } catch(_) {}
|
|
1767
|
+
if (src && src !== 'about:blank' && docUrl === 'about:blank') return false;
|
|
1768
|
+
// Allow interactive / loading \u2014 body may already have nodes; MutationObserver
|
|
1769
|
+
// will refresh the Elements tree as the rest of the document streams in.
|
|
1770
|
+
return true;
|
|
1031
1771
|
}
|
|
1032
1772
|
|
|
1033
1773
|
function loadPage(proxyUrl) {
|
|
1034
1774
|
showNoUrl(false);
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
document.getElementById('load-status').textContent = 'Loading page\u2026';
|
|
1775
|
+
lastLoadedProxyUrl = proxyUrl;
|
|
1776
|
+
var navGen = nextIframeContentNavGen();
|
|
1038
1777
|
var iframe = document.getElementById('iframeId');
|
|
1778
|
+
resetIframeBindings();
|
|
1039
1779
|
iframe.style.display = 'block';
|
|
1780
|
+
setIframePageLoadingUi(true);
|
|
1040
1781
|
iframe.src = proxyUrl;
|
|
1782
|
+
startIframeContentApplyWatcher(navGen);
|
|
1783
|
+
scheduleDomTreeRefresh();
|
|
1041
1784
|
}
|
|
1042
1785
|
|
|
1043
1786
|
// \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
|
|
1787
|
+
var VAR_COLORS = ['#0069A8','#CA3500','#00786F','#ec4899','#14b8a6','#f59e0b','#8b5cf6','#ef4444'];
|
|
1788
|
+
|
|
1044
1789
|
function renderVariationTabs() {
|
|
1045
1790
|
var container = document.getElementById('variation-tabs');
|
|
1046
1791
|
container.innerHTML = '';
|
|
1047
|
-
variations.forEach(function(v) {
|
|
1792
|
+
variations.forEach(function(v, i) {
|
|
1048
1793
|
var label = v.name || (v.baseline ? 'Control' : 'Variation');
|
|
1049
1794
|
var btn = document.createElement('button');
|
|
1050
1795
|
btn.className = 'var-tab' + (v._id === activeVarId ? ' active' : '');
|
|
1051
|
-
btn.textContent = label;
|
|
1052
1796
|
btn.title = label;
|
|
1053
1797
|
btn.onclick = function() { switchVariation(v._id); };
|
|
1798
|
+
var dot = document.createElement('span');
|
|
1799
|
+
dot.className = 'var-dot';
|
|
1800
|
+
dot.style.background = VAR_COLORS[i % VAR_COLORS.length];
|
|
1801
|
+
btn.appendChild(dot);
|
|
1802
|
+
btn.appendChild(document.createTextNode(label));
|
|
1054
1803
|
container.appendChild(btn);
|
|
1055
1804
|
});
|
|
1805
|
+
var addRow = document.createElement('button');
|
|
1806
|
+
addRow.className = 'var-add-row';
|
|
1807
|
+
var plus = document.createElement('span');
|
|
1808
|
+
plus.textContent = '+';
|
|
1809
|
+
plus.style.cssText = 'font-size:15px;line-height:1;font-weight:300';
|
|
1810
|
+
addRow.appendChild(plus);
|
|
1811
|
+
addRow.appendChild(document.createTextNode('Add variation'));
|
|
1812
|
+
container.appendChild(addRow);
|
|
1056
1813
|
var active = variations.find(function(v) { return v._id === activeVarId; });
|
|
1057
1814
|
document.getElementById('active-var-label').textContent = active ? active.name || '' : '';
|
|
1058
1815
|
}
|
|
@@ -1060,6 +1817,7 @@ function renderVariationTabs() {
|
|
|
1060
1817
|
function switchVariation(varId) {
|
|
1061
1818
|
if (varId === activeVarId) return;
|
|
1062
1819
|
saveCurrentVariationHtml();
|
|
1820
|
+
clearPendingGranularChangesets();
|
|
1063
1821
|
activeVarId = varId;
|
|
1064
1822
|
renderVariationTabs();
|
|
1065
1823
|
deselectElement();
|
|
@@ -1068,17 +1826,28 @@ function switchVariation(varId) {
|
|
|
1068
1826
|
var saved = varHtmlCache[varId];
|
|
1069
1827
|
if (saved) {
|
|
1070
1828
|
iframe.contentDocument.body.innerHTML = saved;
|
|
1071
|
-
|
|
1072
|
-
|
|
1829
|
+
detachIframeLoadingListeners();
|
|
1830
|
+
setIframePageLoadingUi(false);
|
|
1831
|
+
syncIframeInteractions('switch-variation-cache');
|
|
1832
|
+
try {
|
|
1833
|
+
var vNow = variations.find(function(x) { return x._id === activeVarId; });
|
|
1834
|
+
var csNow = parseVariationChangesets(vNow);
|
|
1835
|
+
registerPendingGranularChangesets(csNow, iframe.contentDocument);
|
|
1836
|
+
} catch(_) {}
|
|
1073
1837
|
} else {
|
|
1074
|
-
var
|
|
1075
|
-
|
|
1076
|
-
|
|
1838
|
+
var navGen = nextIframeContentNavGen();
|
|
1839
|
+
resetIframeBindings();
|
|
1840
|
+
setIframePageLoadingUi(true);
|
|
1077
1841
|
var src = iframe.src;
|
|
1078
1842
|
iframe.src = '';
|
|
1079
|
-
iframe.src = src;
|
|
1843
|
+
iframe.src = appendIframeReloadBust(src);
|
|
1844
|
+
// Do not sync here: the document is still the previous navigation until the
|
|
1845
|
+
// iframe load event; an eager sync attached observers / DOM tree to the wrong document.
|
|
1846
|
+
startIframeContentApplyWatcher(navGen);
|
|
1847
|
+
scheduleDomTreeRefresh();
|
|
1080
1848
|
}
|
|
1081
1849
|
} catch(_) {}
|
|
1850
|
+
if (currentMainTab === 'history') renderHistoryTab();
|
|
1082
1851
|
}
|
|
1083
1852
|
|
|
1084
1853
|
function saveCurrentVariationHtml() {
|
|
@@ -1093,18 +1862,123 @@ function camelize(str) {
|
|
|
1093
1862
|
return (str || '').replace(/-([a-z])/g, function(_, c) { return c.toUpperCase(); });
|
|
1094
1863
|
}
|
|
1095
1864
|
|
|
1865
|
+
function parseVariationChangesets(variation) {
|
|
1866
|
+
if (!variation || variation.changesets == null) return [];
|
|
1867
|
+
var raw = variation.changesets;
|
|
1868
|
+
if (Array.isArray(raw)) return raw;
|
|
1869
|
+
if (typeof raw !== 'string') return [];
|
|
1870
|
+
var s = raw.trim();
|
|
1871
|
+
if (!s) return [];
|
|
1872
|
+
try {
|
|
1873
|
+
var cs = JSON.parse(s);
|
|
1874
|
+
if (Array.isArray(cs)) return cs;
|
|
1875
|
+
if (typeof cs === 'string') {
|
|
1876
|
+
try {
|
|
1877
|
+
var inner = JSON.parse(cs);
|
|
1878
|
+
return Array.isArray(inner) ? inner : [];
|
|
1879
|
+
} catch(_) {
|
|
1880
|
+
return [];
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
return [];
|
|
1884
|
+
} catch(_) {
|
|
1885
|
+
return [];
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
function filterGranularChangesetEntries(cs) {
|
|
1890
|
+
if (!cs || !cs.length) return [];
|
|
1891
|
+
var out = [];
|
|
1892
|
+
for (var i = 0; i < cs.length; i++) {
|
|
1893
|
+
var e = cs[i];
|
|
1894
|
+
if (e && e.selector && e.selector !== '__vvveb_body__') out.push(e);
|
|
1895
|
+
}
|
|
1896
|
+
return out;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
function countUnresolvedGranularSelectors(iframeDoc, entries) {
|
|
1900
|
+
if (!iframeDoc || !entries || !entries.length) return 0;
|
|
1901
|
+
var n = 0;
|
|
1902
|
+
for (var i = 0; i < entries.length; i++) {
|
|
1903
|
+
var el = null;
|
|
1904
|
+
try { el = iframeDoc.querySelector(entries[i].selector); } catch(_) {}
|
|
1905
|
+
if (!el) n++;
|
|
1906
|
+
}
|
|
1907
|
+
return n;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
function applyGranularChangesetEntries(iframeDoc, entries) {
|
|
1911
|
+
if (!iframeDoc || !entries || !entries.length) return;
|
|
1912
|
+
for (var i = 0; i < entries.length; i++) {
|
|
1913
|
+
applyChangesetEntry(entries[i], iframeDoc);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
function registerPendingGranularChangesets(cs, iframeDoc) {
|
|
1918
|
+
clearPendingGranularChangesets();
|
|
1919
|
+
var granular = filterGranularChangesetEntries(cs);
|
|
1920
|
+
if (!granular.length || !iframeDoc) return;
|
|
1921
|
+
if (countUnresolvedGranularSelectors(iframeDoc, granular) === 0) return;
|
|
1922
|
+
pendingGranularChangesets = granular;
|
|
1923
|
+
pendingGranularVarId = activeVarId;
|
|
1924
|
+
granularReapplyAttempts = 0;
|
|
1925
|
+
scheduleGranularChangesetReapply();
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
function scheduleGranularChangesetReapply() {
|
|
1929
|
+
if (!pendingGranularChangesets || !pendingGranularChangesets.length) return;
|
|
1930
|
+
if (granularReapplyTimer) clearTimeout(granularReapplyTimer);
|
|
1931
|
+
granularReapplyTimer = setTimeout(function() {
|
|
1932
|
+
granularReapplyTimer = null;
|
|
1933
|
+
flushPendingGranularChangesets();
|
|
1934
|
+
}, 140);
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
function flushPendingGranularChangesets() {
|
|
1938
|
+
if (!pendingGranularChangesets || !pendingGranularChangesets.length) return;
|
|
1939
|
+
if (pendingGranularVarId !== activeVarId) {
|
|
1940
|
+
clearPendingGranularChangesets();
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
var iframe = document.getElementById('iframeId');
|
|
1944
|
+
var doc = iframe && iframe.contentDocument;
|
|
1945
|
+
if (!doc || !doc.body) {
|
|
1946
|
+
granularReapplyAttempts += 1;
|
|
1947
|
+
if (granularReapplyAttempts >= GRANULAR_REAPPLY_MAX) {
|
|
1948
|
+
clearPendingGranularChangesets();
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
scheduleGranularChangesetReapply();
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
applyGranularChangesetEntries(doc, pendingGranularChangesets);
|
|
1955
|
+
var left = countUnresolvedGranularSelectors(doc, pendingGranularChangesets);
|
|
1956
|
+
if (left === 0) {
|
|
1957
|
+
clearPendingGranularChangesets();
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
granularReapplyAttempts += 1;
|
|
1961
|
+
if (granularReapplyAttempts >= GRANULAR_REAPPLY_MAX) {
|
|
1962
|
+
clearPendingGranularChangesets();
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
scheduleGranularChangesetReapply();
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1096
1968
|
/**
|
|
1097
|
-
* Apply a single changeset entry
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1100
|
-
* and the V2 full-body snapshot:
|
|
1101
|
-
* { selector: '__vvveb_body__', html: '<full body html>' }
|
|
1969
|
+
* Apply a single changeset entry inside the editor iframe.
|
|
1970
|
+
* Backend format: { selector, type: 'content'|'style'|'attribute'|'insert'|'remove', ... }
|
|
1971
|
+
* V2 snapshot: { selector: '__vvveb_body__', html: '<full body html>' }
|
|
1102
1972
|
*/
|
|
1103
1973
|
function applyChangesetEntry(entry, iframeDoc) {
|
|
1104
1974
|
if (!entry || !entry.selector) return;
|
|
1105
1975
|
|
|
1106
1976
|
// \u2500\u2500 V2 full-body snapshot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1107
1977
|
if (entry.selector === '__vvveb_body__' && entry.html != null) {
|
|
1978
|
+
var bkey = '__vvveb_body__|snapshot';
|
|
1979
|
+
if (!appliedChangesetSnapshots[bkey]) {
|
|
1980
|
+
appliedChangesetSnapshots[bkey] = { kind: 'bodyHTML', v: iframeDoc.body.innerHTML };
|
|
1981
|
+
}
|
|
1108
1982
|
iframeDoc.body.innerHTML = entry.html;
|
|
1109
1983
|
varHtmlCache[activeVarId] = entry.html;
|
|
1110
1984
|
return;
|
|
@@ -1115,6 +1989,8 @@ function applyChangesetEntry(entry, iframeDoc) {
|
|
|
1115
1989
|
try { el = iframeDoc.querySelector(entry.selector); } catch(_) {}
|
|
1116
1990
|
if (!el) return;
|
|
1117
1991
|
|
|
1992
|
+
captureChangesetSnapshotBeforeApply(entry, el, iframeDoc);
|
|
1993
|
+
|
|
1118
1994
|
switch (entry.type) {
|
|
1119
1995
|
case 'content':
|
|
1120
1996
|
if (entry.html != null) el.innerHTML = entry.html;
|
|
@@ -1143,24 +2019,120 @@ function applyChangesetEntry(entry, iframeDoc) {
|
|
|
1143
2019
|
}
|
|
1144
2020
|
|
|
1145
2021
|
function applyActiveVariationHtml() {
|
|
2022
|
+
clearPendingGranularChangesets();
|
|
1146
2023
|
if (!activeVarId) return;
|
|
2024
|
+
var iframe = document.getElementById('iframeId');
|
|
2025
|
+
var iframeDoc = iframe && iframe.contentDocument;
|
|
2026
|
+
if (!iframeDoc || !iframeDoc.body) return;
|
|
2027
|
+
|
|
2028
|
+
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2029
|
+
var cs = parseVariationChangesets(variation);
|
|
1147
2030
|
|
|
1148
2031
|
// If we have an in-session HTML snapshot, use it (user edited in this session)
|
|
1149
2032
|
var saved = varHtmlCache[activeVarId];
|
|
1150
2033
|
if (saved) {
|
|
1151
|
-
try {
|
|
2034
|
+
try { iframeDoc.body.innerHTML = saved; } catch(_) {}
|
|
1152
2035
|
return;
|
|
1153
2036
|
}
|
|
1154
2037
|
|
|
1155
|
-
|
|
2038
|
+
if (!cs.length) return;
|
|
2039
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2040
|
+
applyChangesetEntry(cs[i], iframeDoc);
|
|
2041
|
+
}
|
|
2042
|
+
// Selectors often miss on first paint (client-rendered sections). Retry via timer + mutations.
|
|
2043
|
+
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
function changesetsHaveBodySnapshot(cs) {
|
|
2047
|
+
if (!cs || !cs.length) return false;
|
|
2048
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2049
|
+
if (cs[i] && cs[i].selector === '__vvveb_body__' && cs[i].html != null) return true;
|
|
2050
|
+
}
|
|
2051
|
+
return false;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
/**
|
|
2055
|
+
* While document.readyState === 'loading', apply only granular changesets (no __vvveb_body__
|
|
2056
|
+
* replacement) so the first painted nodes can receive edits before iframe/window load completes.
|
|
2057
|
+
*/
|
|
2058
|
+
function applyVariationGranularOnly(iframeDoc) {
|
|
2059
|
+
if (!activeVarId || !iframeDoc || !iframeDoc.body) return;
|
|
2060
|
+
if (varHtmlCache[activeVarId]) return;
|
|
1156
2061
|
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
2062
|
+
var cs = parseVariationChangesets(variation);
|
|
2063
|
+
if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
|
|
2064
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2065
|
+
applyChangesetEntry(cs[i], iframeDoc);
|
|
2066
|
+
}
|
|
2067
|
+
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
/** Re-try granular entries without resetting pending registration (use between poll ticks). */
|
|
2071
|
+
function reapplyActiveVariationGranular(iframeDoc) {
|
|
2072
|
+
if (!activeVarId || !iframeDoc || !iframeDoc.body) return;
|
|
2073
|
+
if (varHtmlCache[activeVarId]) return;
|
|
2074
|
+
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2075
|
+
var cs = parseVariationChangesets(variation);
|
|
2076
|
+
if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
|
|
2077
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2078
|
+
applyChangesetEntry(cs[i], iframeDoc);
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
/** Poll iframe document during navigation; apply granular edits as soon as DOM can match selectors. */
|
|
2083
|
+
function startIframeContentApplyWatcher(navGen) {
|
|
2084
|
+
stopIframeContentApplyWatcher();
|
|
2085
|
+
iframeEarlyGranularPrimedForGen = null;
|
|
2086
|
+
iframeEarlySyncPrimedForGen = null;
|
|
2087
|
+
var iframe = document.getElementById('iframeId');
|
|
2088
|
+
iframeContentApplyTimer = setInterval(function() {
|
|
2089
|
+
if (navGen !== iframeContentNavGen) {
|
|
2090
|
+
stopIframeContentApplyWatcher();
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
2093
|
+
try {
|
|
2094
|
+
var doc = iframe.contentDocument;
|
|
2095
|
+
if (!doc || !doc.body) return;
|
|
2096
|
+
var docUrl = '';
|
|
2097
|
+
try { docUrl = String(doc.URL || ''); } catch(_) {}
|
|
2098
|
+
if (docUrl === 'about:blank') return;
|
|
2099
|
+
if (!iframeDocMatchesNavigatedSrc(iframe, doc)) return;
|
|
2100
|
+
|
|
2101
|
+
if (doc.readyState === 'interactive' || doc.readyState === 'complete') {
|
|
2102
|
+
applyActiveVariationHtml();
|
|
2103
|
+
syncIframeInteractions('iframe-readystate');
|
|
2104
|
+
stopIframeContentApplyWatcher();
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
if (doc.readyState === 'loading') {
|
|
2109
|
+
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2110
|
+
var cs0 = parseVariationChangesets(variation);
|
|
2111
|
+
if (!cs0.length || changesetsHaveBodySnapshot(cs0)) return;
|
|
2112
|
+
var granular = filterGranularChangesetEntries(cs0);
|
|
2113
|
+
if (!granular.length) return;
|
|
2114
|
+
|
|
2115
|
+
var canTry =
|
|
2116
|
+
bodyHasFirstPaintChild(doc.body) ||
|
|
2117
|
+
granularAnySelectorMatches(doc, cs0) ||
|
|
2118
|
+
(doc.body.children && doc.body.children.length > 0);
|
|
2119
|
+
|
|
2120
|
+
if (canTry) {
|
|
2121
|
+
if (iframeEarlyGranularPrimedForGen !== navGen) {
|
|
2122
|
+
iframeEarlyGranularPrimedForGen = navGen;
|
|
2123
|
+
applyVariationGranularOnly(doc);
|
|
2124
|
+
} else {
|
|
2125
|
+
reapplyActiveVariationGranular(doc);
|
|
2126
|
+
scheduleGranularChangesetReapply();
|
|
2127
|
+
}
|
|
2128
|
+
if (iframeEarlySyncPrimedForGen !== navGen) {
|
|
2129
|
+
iframeEarlySyncPrimedForGen = navGen;
|
|
2130
|
+
syncIframeInteractions('iframe-early-paint');
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
} catch(_) {}
|
|
2135
|
+
}, 20);
|
|
1164
2136
|
}
|
|
1165
2137
|
|
|
1166
2138
|
// \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
|
|
@@ -1172,17 +2144,326 @@ function selectElement(el) {
|
|
|
1172
2144
|
document.getElementById('bc-path').style.color = 'var(--accent-txt)';
|
|
1173
2145
|
document.getElementById('no-sel').style.display = 'none';
|
|
1174
2146
|
renderRightPanel(el);
|
|
2147
|
+
updateSelectionToolbar();
|
|
2148
|
+
if (currentLeftTab === 'elements') {
|
|
2149
|
+
var dr = document.getElementById('dom-tree-root');
|
|
2150
|
+
if (dr && dr.querySelector('.dt-row')) syncDomTreeSelection();
|
|
2151
|
+
else scheduleDomTreeRefresh();
|
|
2152
|
+
}
|
|
1175
2153
|
}
|
|
1176
2154
|
|
|
1177
2155
|
function deselectElement() {
|
|
2156
|
+
setDragHandleActive(false);
|
|
1178
2157
|
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} selectedEl = null; }
|
|
1179
2158
|
document.getElementById('no-sel').style.display = '';
|
|
1180
2159
|
document.getElementById('el-info').style.display = 'none';
|
|
1181
2160
|
document.getElementById('rp-accordion').style.display = 'none';
|
|
1182
2161
|
document.getElementById('bc-path').textContent = 'No element selected';
|
|
1183
2162
|
document.getElementById('bc-path').style.color = 'var(--text-3)';
|
|
1184
|
-
// Switch back to Design tab so no-sel message is visible
|
|
1185
2163
|
switchMainTab('design');
|
|
2164
|
+
updateSelectionToolbar();
|
|
2165
|
+
syncDomTreeSelection();
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
// \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
|
|
2169
|
+
function injectIframeSelectionStyles(doc) {
|
|
2170
|
+
if (!doc || !doc.head) return;
|
|
2171
|
+
var sid = '__vve_sel_style';
|
|
2172
|
+
if (doc.getElementById(sid)) return;
|
|
2173
|
+
var st = doc.createElement('style');
|
|
2174
|
+
st.id = sid;
|
|
2175
|
+
st.textContent =
|
|
2176
|
+
'.vve-selected{outline:2px solid #6366f1!important;outline-offset:2px!important;' +
|
|
2177
|
+
'box-shadow:0 0 0 2px rgba(99,102,241,.28),inset 0 0 0 1px rgba(99,102,241,.18)!important;}' +
|
|
2178
|
+
'html.vve-drag-armed .vve-selected{cursor:grab!important;}' +
|
|
2179
|
+
'.vve-dragging{opacity:0.92!important;outline:2px dashed #f59e0b!important;' +
|
|
2180
|
+
'outline-offset:2px!important;cursor:grabbing!important;box-shadow:none!important;}';
|
|
2181
|
+
doc.head.appendChild(st);
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
function setDragHandleActive(on) {
|
|
2185
|
+
dragHandleActive = !!on;
|
|
2186
|
+
var b = document.getElementById('sf-drag');
|
|
2187
|
+
if (b) b.classList.toggle('active', dragHandleActive);
|
|
2188
|
+
try {
|
|
2189
|
+
var iframe = document.getElementById('iframeId');
|
|
2190
|
+
var d = iframe && iframe.contentDocument && iframe.contentDocument.documentElement;
|
|
2191
|
+
if (d) d.classList.toggle('vve-drag-armed', dragHandleActive);
|
|
2192
|
+
} catch(_) {}
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
function positionSelectionToolbar() {
|
|
2196
|
+
var bar = document.getElementById('selection-floater');
|
|
2197
|
+
var iframe = document.getElementById('iframeId');
|
|
2198
|
+
var panel = document.getElementById('iframe-panel');
|
|
2199
|
+
if (!bar || !selectedEl || !iframe || !iframe.contentWindow || !panel) return;
|
|
2200
|
+
var elR = selectedEl.getBoundingClientRect();
|
|
2201
|
+
var iframeR = iframe.getBoundingClientRect();
|
|
2202
|
+
var panelR = panel.getBoundingClientRect();
|
|
2203
|
+
var left = iframeR.left - panelR.left + elR.left + elR.width / 2 - bar.offsetWidth / 2;
|
|
2204
|
+
var top = iframeR.top - panelR.top + elR.top - bar.offsetHeight - 8;
|
|
2205
|
+
if (top < 6) top = iframeR.top - panelR.top + elR.bottom + 8;
|
|
2206
|
+
var maxL = Math.max(6, panel.clientWidth - bar.offsetWidth - 8);
|
|
2207
|
+
left = Math.max(6, Math.min(left, maxL));
|
|
2208
|
+
bar.style.left = Math.round(left) + 'px';
|
|
2209
|
+
bar.style.top = Math.round(top) + 'px';
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
function updateSelectionToolbar() {
|
|
2213
|
+
var bar = document.getElementById('selection-floater');
|
|
2214
|
+
if (!bar) return;
|
|
2215
|
+
if (!selectedEl || currentMode !== 'editor') {
|
|
2216
|
+
bar.style.display = 'none';
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
bar.style.display = 'flex';
|
|
2220
|
+
requestAnimationFrame(function() { positionSelectionToolbar(); });
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
function onFloaterScroll() {
|
|
2224
|
+
if (selectedEl && currentMode === 'editor') positionSelectionToolbar();
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
function bindSelectionToolbarScroll() {
|
|
2228
|
+
var iframe = document.getElementById('iframeId');
|
|
2229
|
+
var w = iframe && iframe.contentWindow;
|
|
2230
|
+
if (!w) return;
|
|
2231
|
+
if (selectionScrollWin && selectionScrollWin !== w) {
|
|
2232
|
+
try { selectionScrollWin.removeEventListener('scroll', onFloaterScroll, true); } catch(_) {}
|
|
2233
|
+
}
|
|
2234
|
+
selectionScrollWin = w;
|
|
2235
|
+
w.addEventListener('scroll', onFloaterScroll, true);
|
|
2236
|
+
if (!selectionResizeBound) {
|
|
2237
|
+
selectionResizeBound = true;
|
|
2238
|
+
window.addEventListener('resize', onFloaterScroll);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
/** Scroll the preview document so a node is visible (used when picking from the DOM tree). */
|
|
2243
|
+
function scrollIframeElementIntoView(el) {
|
|
2244
|
+
if (!el || el.nodeType !== 1) return;
|
|
2245
|
+
try {
|
|
2246
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
|
|
2247
|
+
} catch(_) {
|
|
2248
|
+
try {
|
|
2249
|
+
el.scrollIntoView(true);
|
|
2250
|
+
} catch(__) {}
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
function selectElementFromTree(el) {
|
|
2255
|
+
selectElement(el);
|
|
2256
|
+
scrollIframeElementIntoView(el);
|
|
2257
|
+
if (currentMainTab !== 'design') switchMainTab('design');
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
function duplicateSelectedEl() {
|
|
2261
|
+
if (!selectedEl || !selectedEl.parentNode) return;
|
|
2262
|
+
var clone = selectedEl.cloneNode(true);
|
|
2263
|
+
clone.classList.remove('vve-selected');
|
|
2264
|
+
var all = clone.querySelectorAll ? clone.querySelectorAll('.vve-selected') : [];
|
|
2265
|
+
for (var i = 0; i < all.length; i++) all[i].classList.remove('vve-selected');
|
|
2266
|
+
clone.style.visibility = '';
|
|
2267
|
+
if (clone.removeAttribute) clone.removeAttribute('data-vve-hidden');
|
|
2268
|
+
selectedEl.parentNode.insertBefore(clone, selectedEl.nextSibling);
|
|
2269
|
+
markDirty();
|
|
2270
|
+
scheduleDomTreeRefresh();
|
|
2271
|
+
selectElement(clone);
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
function toggleHideSelectedEl() {
|
|
2275
|
+
if (!selectedEl) return;
|
|
2276
|
+
if (selectedEl.getAttribute('data-vve-hidden') === '1') {
|
|
2277
|
+
selectedEl.style.visibility = '';
|
|
2278
|
+
selectedEl.removeAttribute('data-vve-hidden');
|
|
2279
|
+
} else {
|
|
2280
|
+
selectedEl.style.visibility = 'hidden';
|
|
2281
|
+
selectedEl.setAttribute('data-vve-hidden', '1');
|
|
2282
|
+
}
|
|
2283
|
+
markDirty();
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
function deleteSelectedEl() {
|
|
2287
|
+
if (!selectedEl || !selectedEl.parentNode) return;
|
|
2288
|
+
selectedEl.remove();
|
|
2289
|
+
markDirty();
|
|
2290
|
+
deselectElement();
|
|
2291
|
+
scheduleDomTreeRefresh();
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
function syncDomTreeSelection() {
|
|
2295
|
+
var root = document.getElementById('dom-tree-root');
|
|
2296
|
+
if (!root) return;
|
|
2297
|
+
var rows = root.querySelectorAll('.dt-row');
|
|
2298
|
+
for (var i = 0; i < rows.length; i++) {
|
|
2299
|
+
rows[i].classList.toggle('dt-selected', !!(selectedEl && rows[i]._dtEl === selectedEl));
|
|
2300
|
+
}
|
|
2301
|
+
if (!selectedEl) return;
|
|
2302
|
+
var found = null;
|
|
2303
|
+
for (var j = 0; j < rows.length; j++) {
|
|
2304
|
+
if (rows[j]._dtEl === selectedEl) { found = rows[j]; break; }
|
|
2305
|
+
}
|
|
2306
|
+
if (found) found.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
function scheduleDomTreeRefresh() {
|
|
2310
|
+
if (currentLeftTab !== 'elements') return;
|
|
2311
|
+
if (domTreeRefreshTimer) clearTimeout(domTreeRefreshTimer);
|
|
2312
|
+
domTreeRefreshTimer = setTimeout(function() {
|
|
2313
|
+
domTreeRefreshTimer = null;
|
|
2314
|
+
var inp = document.getElementById('comp-search');
|
|
2315
|
+
renderDomTree(inp ? inp.value : '');
|
|
2316
|
+
}, 150);
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
function setDomTreeStatus(mode) {
|
|
2320
|
+
var root = document.getElementById('dom-tree-root');
|
|
2321
|
+
if (!root) return;
|
|
2322
|
+
if (mode === 'empty') {
|
|
2323
|
+
root.innerHTML = '<div class="dt-muted">Load a page to see the DOM tree.</div>';
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
function domTreePathSegment(el) {
|
|
2328
|
+
var tag = el.tagName.toLowerCase();
|
|
2329
|
+
var idx = 1;
|
|
2330
|
+
var s = el.previousElementSibling;
|
|
2331
|
+
while (s) {
|
|
2332
|
+
if (s.tagName === el.tagName) idx++;
|
|
2333
|
+
s = s.previousElementSibling;
|
|
2334
|
+
}
|
|
2335
|
+
return tag + '[' + idx + ']';
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
function renderDomTree(filterRaw) {
|
|
2339
|
+
var filterText = (filterRaw || '').toLowerCase().trim();
|
|
2340
|
+
var root = document.getElementById('dom-tree-root');
|
|
2341
|
+
if (!root) return;
|
|
2342
|
+
var iframe = document.getElementById('iframeId');
|
|
2343
|
+
var doc = iframe && iframe.contentDocument;
|
|
2344
|
+
if (!isIframeDomReady(iframe, doc)) {
|
|
2345
|
+
if (iframe && iframe.src && iframe.src !== 'about:blank') {
|
|
2346
|
+
if (iframeSyncRetryTimer) clearTimeout(iframeSyncRetryTimer);
|
|
2347
|
+
iframeSyncRetryTimer = setTimeout(function() { syncIframeInteractions('dom-tree-wait'); }, 180);
|
|
2348
|
+
} else {
|
|
2349
|
+
setDomTreeStatus('empty');
|
|
2350
|
+
}
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
function labelFor(el) {
|
|
2355
|
+
var tag = el.tagName.toLowerCase();
|
|
2356
|
+
if (el.id) return tag + '#' + el.id.slice(0, 40);
|
|
2357
|
+
var cn = el.className && typeof el.className === 'string' ? el.className.trim() : '';
|
|
2358
|
+
if (cn) {
|
|
2359
|
+
var parts = cn.split(/s+/).filter(function(x) { return x.indexOf('vve-') !== 0; }).slice(0, 2).join('.');
|
|
2360
|
+
if (parts) return tag + '.' + parts.slice(0, 56);
|
|
2361
|
+
}
|
|
2362
|
+
return tag;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
function skippable(el) {
|
|
2366
|
+
return isDomTreeSkippableTagName(el.tagName);
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
function branchMatches(el) {
|
|
2370
|
+
if (!filterText) return true;
|
|
2371
|
+
if (labelFor(el).toLowerCase().indexOf(filterText) >= 0) return true;
|
|
2372
|
+
var ch = el.children;
|
|
2373
|
+
for (var i = 0; i < ch.length; i++) {
|
|
2374
|
+
if (ch[i].nodeType !== 1 || skippable(ch[i])) continue;
|
|
2375
|
+
if (branchMatches(ch[i])) return true;
|
|
2376
|
+
}
|
|
2377
|
+
return false;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
function nodeIcon(tag) {
|
|
2381
|
+
tag = (tag || '').toLowerCase();
|
|
2382
|
+
if (/^h[1-6]$/.test(tag)) return 'bi bi-type-h1';
|
|
2383
|
+
if (tag === 'a') return 'bi bi-link-45deg';
|
|
2384
|
+
if (tag === 'img') return 'bi bi-image';
|
|
2385
|
+
if (tag === 'section' || tag === 'main' || tag === 'article' || tag === 'header' || tag === 'footer' || tag === 'nav') return 'bi bi-layout-three-columns';
|
|
2386
|
+
if (tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea') return 'bi bi-ui-radios';
|
|
2387
|
+
if (tag === 'ul' || tag === 'ol') return 'bi bi-list-ul';
|
|
2388
|
+
if (tag === 'li') return 'bi bi-dot';
|
|
2389
|
+
if (tag === 'svg') return 'bi bi-bezier2';
|
|
2390
|
+
if (tag === 'p' || tag === 'span') return 'bi bi-text-left';
|
|
2391
|
+
return 'bi bi-square';
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
root.innerHTML = '';
|
|
2395
|
+
|
|
2396
|
+
function walk(el, depth, pathHere) {
|
|
2397
|
+
if (depth > 28) return;
|
|
2398
|
+
if (!el || el.nodeType !== 1) return;
|
|
2399
|
+
if (skippable(el)) return;
|
|
2400
|
+
if (!branchMatches(el)) return;
|
|
2401
|
+
|
|
2402
|
+
var hasKids = false;
|
|
2403
|
+
var i;
|
|
2404
|
+
for (i = 0; i < el.children.length; i++) {
|
|
2405
|
+
var c0 = el.children[i];
|
|
2406
|
+
if (c0.nodeType === 1 && !skippable(c0) && branchMatches(c0)) {
|
|
2407
|
+
hasKids = true;
|
|
2408
|
+
break;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
var collapsed = !!domTreeCollapsed[pathHere];
|
|
2413
|
+
|
|
2414
|
+
var row = document.createElement('div');
|
|
2415
|
+
row.className = 'dt-row';
|
|
2416
|
+
if (el === selectedEl) row.classList.add('dt-selected');
|
|
2417
|
+
row._dtEl = el;
|
|
2418
|
+
row.style.paddingLeft = (4 + depth * 12) + 'px';
|
|
2419
|
+
|
|
2420
|
+
var chev = document.createElement('button');
|
|
2421
|
+
chev.type = 'button';
|
|
2422
|
+
chev.className = 'dt-chev' + (!hasKids ? ' dt-spacer' : '');
|
|
2423
|
+
chev.innerHTML = hasKids ? '<i class="bi bi-chevron-right"></i>' : '';
|
|
2424
|
+
if (hasKids) {
|
|
2425
|
+
chev.style.transform = collapsed ? 'rotate(0deg)' : 'rotate(90deg)';
|
|
2426
|
+
chev.onclick = function(e) {
|
|
2427
|
+
e.stopPropagation();
|
|
2428
|
+
domTreeCollapsed[pathHere] = !domTreeCollapsed[pathHere];
|
|
2429
|
+
renderDomTree(document.getElementById('comp-search').value);
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
var ico = document.createElement('div');
|
|
2434
|
+
ico.className = 'dt-ico';
|
|
2435
|
+
ico.innerHTML = '<i class="' + nodeIcon(el.tagName) + '"></i>';
|
|
2436
|
+
|
|
2437
|
+
var lbl = document.createElement('div');
|
|
2438
|
+
lbl.className = 'dt-lbl';
|
|
2439
|
+
lbl.textContent = labelFor(el);
|
|
2440
|
+
lbl.title = buildSelector(el);
|
|
2441
|
+
|
|
2442
|
+
row.appendChild(chev);
|
|
2443
|
+
row.appendChild(ico);
|
|
2444
|
+
row.appendChild(lbl);
|
|
2445
|
+
row.onclick = function(e) {
|
|
2446
|
+
if (e.target.closest && e.target.closest('.dt-chev')) return;
|
|
2447
|
+
selectElementFromTree(el);
|
|
2448
|
+
};
|
|
2449
|
+
root.appendChild(row);
|
|
2450
|
+
|
|
2451
|
+
if (!hasKids || collapsed) return;
|
|
2452
|
+
for (i = 0; i < el.children.length; i++) {
|
|
2453
|
+
var c = el.children[i];
|
|
2454
|
+
if (c.nodeType !== 1 || skippable(c)) continue;
|
|
2455
|
+
if (!branchMatches(c)) continue;
|
|
2456
|
+
walk(c, depth + 1, pathHere + '/' + domTreePathSegment(c));
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
walk(doc.body, 0, 'body');
|
|
2461
|
+
|
|
2462
|
+
if (!root.querySelector('.dt-row')) {
|
|
2463
|
+
root.innerHTML = filterText
|
|
2464
|
+
? '<div class="dt-muted">No elements match your search.</div>'
|
|
2465
|
+
: '<div class="dt-muted">No visible elements yet.</div>';
|
|
2466
|
+
}
|
|
1186
2467
|
}
|
|
1187
2468
|
|
|
1188
2469
|
// \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
|
|
@@ -1583,11 +2864,113 @@ function buildSelector(el) {
|
|
|
1583
2864
|
}
|
|
1584
2865
|
|
|
1585
2866
|
// \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
|
|
2867
|
+
function repositionDragSibling(dragEl, clientY) {
|
|
2868
|
+
var p = dragEl.parentElement;
|
|
2869
|
+
if (!p) return;
|
|
2870
|
+
var list = Array.prototype.filter.call(p.children, function(n) { return n.nodeType === 1; });
|
|
2871
|
+
var targetBefore = null;
|
|
2872
|
+
for (var i = 0; i < list.length; i++) {
|
|
2873
|
+
var node = list[i];
|
|
2874
|
+
if (node === dragEl) continue;
|
|
2875
|
+
var r = node.getBoundingClientRect();
|
|
2876
|
+
if (clientY < r.top + r.height / 2) {
|
|
2877
|
+
targetBefore = node;
|
|
2878
|
+
break;
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
if (targetBefore) {
|
|
2882
|
+
if (dragEl.nextSibling === targetBefore) return;
|
|
2883
|
+
p.insertBefore(dragEl, targetBefore);
|
|
2884
|
+
} else {
|
|
2885
|
+
var lastEl = list[list.length - 1];
|
|
2886
|
+
if (lastEl === dragEl) return;
|
|
2887
|
+
p.appendChild(dragEl);
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
function attachDragReposition() {
|
|
2892
|
+
try {
|
|
2893
|
+
var iframe = document.getElementById('iframeId');
|
|
2894
|
+
var doc = iframe.contentDocument;
|
|
2895
|
+
if (!doc || !doc.body) return;
|
|
2896
|
+
if (dragAttachDoc === doc) return;
|
|
2897
|
+
dragAttachDoc = doc;
|
|
2898
|
+
doc.addEventListener('mousedown', function(e) {
|
|
2899
|
+
if (!dragHandleActive) return;
|
|
2900
|
+
if (currentMode !== 'editor' || !selectedEl) return;
|
|
2901
|
+
var t = e.target;
|
|
2902
|
+
if (!selectedEl.contains(t) || t === doc.body || t === doc.documentElement) return;
|
|
2903
|
+
var iframeEl = document.getElementById('iframeId');
|
|
2904
|
+
var win = iframeEl.contentWindow;
|
|
2905
|
+
function iframeLocalXY(ev) {
|
|
2906
|
+
if (ev.view === win) return { x: ev.clientX, y: ev.clientY };
|
|
2907
|
+
var ir = iframeEl.getBoundingClientRect();
|
|
2908
|
+
return { x: ev.clientX - ir.left, y: ev.clientY - ir.top };
|
|
2909
|
+
}
|
|
2910
|
+
var start = iframeLocalXY(e);
|
|
2911
|
+
var moved = false;
|
|
2912
|
+
function onMove(e2) {
|
|
2913
|
+
if (!selectedEl) return;
|
|
2914
|
+
var p = iframeLocalXY(e2);
|
|
2915
|
+
var dx = p.x - start.x, dy = p.y - start.y;
|
|
2916
|
+
if (!moved && (dx * dx + dy * dy < 25)) return;
|
|
2917
|
+
if (!moved) {
|
|
2918
|
+
moved = true;
|
|
2919
|
+
try {
|
|
2920
|
+
e.preventDefault();
|
|
2921
|
+
selectedEl.classList.add('vve-dragging');
|
|
2922
|
+
selectedEl.style.pointerEvents = 'none';
|
|
2923
|
+
} catch(_) {}
|
|
2924
|
+
}
|
|
2925
|
+
try { e2.preventDefault(); } catch(_) {}
|
|
2926
|
+
repositionDragSibling(selectedEl, p.y);
|
|
2927
|
+
}
|
|
2928
|
+
function onUp() {
|
|
2929
|
+
doc.removeEventListener('mousemove', onMove);
|
|
2930
|
+
doc.removeEventListener('mouseup', onUp);
|
|
2931
|
+
if (win) {
|
|
2932
|
+
win.removeEventListener('mousemove', onMove, true);
|
|
2933
|
+
win.removeEventListener('mouseup', onUp, true);
|
|
2934
|
+
}
|
|
2935
|
+
window.removeEventListener('mousemove', onMove, true);
|
|
2936
|
+
window.removeEventListener('mouseup', onUp, true);
|
|
2937
|
+
if (moved && selectedEl) {
|
|
2938
|
+
try {
|
|
2939
|
+
selectedEl.classList.remove('vve-dragging');
|
|
2940
|
+
selectedEl.style.pointerEvents = '';
|
|
2941
|
+
} catch(_) {}
|
|
2942
|
+
suppressClickUntil = Date.now() + 200;
|
|
2943
|
+
setDragHandleActive(false);
|
|
2944
|
+
markDirty();
|
|
2945
|
+
updateSelectionToolbar();
|
|
2946
|
+
scheduleDomTreeRefresh();
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
doc.addEventListener('mousemove', onMove);
|
|
2950
|
+
doc.addEventListener('mouseup', onUp);
|
|
2951
|
+
if (win) {
|
|
2952
|
+
win.addEventListener('mousemove', onMove, true);
|
|
2953
|
+
win.addEventListener('mouseup', onUp, true);
|
|
2954
|
+
}
|
|
2955
|
+
window.addEventListener('mousemove', onMove, true);
|
|
2956
|
+
window.addEventListener('mouseup', onUp, true);
|
|
2957
|
+
}, true);
|
|
2958
|
+
} catch(_) {}
|
|
2959
|
+
}
|
|
2960
|
+
|
|
1586
2961
|
function attachClickHandler() {
|
|
1587
2962
|
try {
|
|
1588
2963
|
var iframe = document.getElementById('iframeId');
|
|
1589
2964
|
var doc = iframe.contentDocument;
|
|
2965
|
+
if (!doc || !doc.body) return;
|
|
2966
|
+
if (clickAttachDoc === doc) return;
|
|
2967
|
+
clickAttachDoc = doc;
|
|
1590
2968
|
doc.addEventListener('click', function(e) {
|
|
2969
|
+
if (Date.now() < suppressClickUntil) {
|
|
2970
|
+
e.preventDefault();
|
|
2971
|
+
e.stopPropagation();
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
1591
2974
|
if (currentMode !== 'editor') return;
|
|
1592
2975
|
e.preventDefault();
|
|
1593
2976
|
e.stopPropagation();
|
|
@@ -1603,29 +2986,122 @@ function attachClickHandler() {
|
|
|
1603
2986
|
function attachChangeObserver() {
|
|
1604
2987
|
try {
|
|
1605
2988
|
var iframe = document.getElementById('iframeId');
|
|
1606
|
-
var
|
|
1607
|
-
|
|
2989
|
+
var doc = iframe && iframe.contentDocument;
|
|
2990
|
+
if (!doc || !doc.body) return;
|
|
2991
|
+
if (changeObserverDoc === doc) return;
|
|
2992
|
+
if (changeObserver) {
|
|
2993
|
+
try { changeObserver.disconnect(); } catch(_) {}
|
|
2994
|
+
changeObserver = null;
|
|
2995
|
+
changeObserverDoc = null;
|
|
2996
|
+
}
|
|
2997
|
+
changeObserver = new MutationObserver(function() {
|
|
2998
|
+
markDirty();
|
|
2999
|
+
// Debounced full rebuild of Elements panel as the live DOM grows / changes
|
|
3000
|
+
scheduleDomTreeRefresh();
|
|
3001
|
+
scheduleGranularChangesetReapply();
|
|
3002
|
+
});
|
|
3003
|
+
changeObserver.observe(doc.body, {
|
|
1608
3004
|
childList: true, subtree: true, attributes: true, characterData: true
|
|
1609
3005
|
});
|
|
3006
|
+
changeObserverDoc = doc;
|
|
3007
|
+
} catch(_) {}
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
function syncIframeInteractions(reason) {
|
|
3011
|
+
try {
|
|
3012
|
+
var iframe = document.getElementById('iframeId');
|
|
3013
|
+
var doc = iframe && iframe.contentDocument;
|
|
3014
|
+
if (!isIframeDomReady(iframe, doc)) {
|
|
3015
|
+
iframeSyncAttempts += 1;
|
|
3016
|
+
if (iframeSyncAttempts > 120) return;
|
|
3017
|
+
if (iframeSyncRetryTimer) clearTimeout(iframeSyncRetryTimer);
|
|
3018
|
+
iframeSyncRetryTimer = setTimeout(function() { syncIframeInteractions('retry:' + reason); }, 120);
|
|
3019
|
+
return;
|
|
3020
|
+
}
|
|
3021
|
+
iframeSyncAttempts = 0;
|
|
3022
|
+
if (iframeSyncRetryTimer) {
|
|
3023
|
+
clearTimeout(iframeSyncRetryTimer);
|
|
3024
|
+
iframeSyncRetryTimer = null;
|
|
3025
|
+
}
|
|
3026
|
+
showNoUrl(false);
|
|
3027
|
+
injectIframeSelectionStyles(doc);
|
|
3028
|
+
attachClickHandler();
|
|
3029
|
+
attachDragReposition();
|
|
3030
|
+
attachChangeObserver();
|
|
3031
|
+
bindSelectionToolbarScroll();
|
|
3032
|
+
var inp = document.getElementById('comp-search');
|
|
3033
|
+
renderDomTree(inp ? inp.value : '');
|
|
3034
|
+
updateSelectionToolbar();
|
|
1610
3035
|
} catch(_) {}
|
|
1611
3036
|
}
|
|
1612
3037
|
|
|
1613
|
-
// \u2500\u2500 HTML insertion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3038
|
+
// \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
|
|
3039
|
+
/** Full snippets for Vvveb keys whose html field is placeholder text, not markup. */
|
|
3040
|
+
var VVVEB_INSERT_HTML_OVERRIDES = {
|
|
3041
|
+
'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>',
|
|
3042
|
+
'html/gridcolumn': '<div class="col-sm-6"><p>New column</p></div>',
|
|
3043
|
+
'html/container': '<div class="container py-3"><p>Container</p></div>',
|
|
3044
|
+
'html/btn-link': '<a class="btn btn-primary" href="#">Primary button</a>',
|
|
3045
|
+
'html/btn': '<button type="button" class="btn btn-primary">Primary</button>',
|
|
3046
|
+
'html/pageitem': '<li class="page-item"><a class="page-link" href="#">1</a></li>',
|
|
3047
|
+
'html/breadcrumbitem': '<li class="breadcrumb-item"><a href="#">Item</a></li>',
|
|
3048
|
+
'html/listitem': '<li class="list-group-item">List item</li>',
|
|
3049
|
+
'html/tablebody': '<tbody><tr><td>Cell</td></tr></tbody>',
|
|
3050
|
+
};
|
|
3051
|
+
|
|
3052
|
+
function buildHtmlFromVvvebComponent(comp, typeKey) {
|
|
3053
|
+
if (!comp) return '';
|
|
3054
|
+
if (typeKey && VVVEB_INSERT_HTML_OVERRIDES[typeKey]) return VVVEB_INSERT_HTML_OVERRIDES[typeKey];
|
|
3055
|
+
var raw = (comp.html != null ? String(comp.html) : '').trim();
|
|
3056
|
+
if (raw.indexOf('<') === 0) return comp.html;
|
|
3057
|
+
var tmpl = document.createElement('template');
|
|
3058
|
+
tmpl.innerHTML = raw;
|
|
3059
|
+
if (tmpl.content.children.length > 0) return comp.html;
|
|
3060
|
+
var classes = (comp.classes || []).filter(Boolean);
|
|
3061
|
+
var cls = classes[0] || '';
|
|
3062
|
+
var tag = 'div';
|
|
3063
|
+
if (classes.indexOf('btn') >= 0 || /(^|s)btn(s|$)/.test(cls)) tag = 'a';
|
|
3064
|
+
var extra = tag === 'a' ? ' href="#" role="button"' : '';
|
|
3065
|
+
var inner = raw || comp.name || 'Element';
|
|
3066
|
+
return '<' + tag + (cls ? ' class="' + cls + '"' : '') + extra + '>' + inner + '</' + tag + '>';
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
function insertVvvebComponent(typeKey) {
|
|
3070
|
+
var comp = typeof Vvveb !== 'undefined' && Vvveb.Components && Vvveb.Components.get
|
|
3071
|
+
? Vvveb.Components.get(typeKey)
|
|
3072
|
+
: null;
|
|
3073
|
+
var html = buildHtmlFromVvvebComponent(comp, typeKey);
|
|
3074
|
+
insertHtml(html);
|
|
3075
|
+
}
|
|
3076
|
+
|
|
1614
3077
|
function insertHtml(html) {
|
|
1615
3078
|
if (!html) return;
|
|
1616
3079
|
try {
|
|
1617
3080
|
var iframe = document.getElementById('iframeId');
|
|
1618
|
-
var doc = iframe.contentDocument;
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
3081
|
+
var doc = iframe && iframe.contentDocument;
|
|
3082
|
+
if (!doc || !doc.body) {
|
|
3083
|
+
console.warn('[V2] insertHtml: iframe document not ready');
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
3086
|
+
var t = doc.createElement('template');
|
|
3087
|
+
t.innerHTML = String(html).trim();
|
|
3088
|
+
var frag = doc.createDocumentFragment();
|
|
3089
|
+
var firstEl = null;
|
|
3090
|
+
while (t.content.firstChild) {
|
|
3091
|
+
var n = t.content.firstChild;
|
|
3092
|
+
t.content.removeChild(n);
|
|
3093
|
+
frag.appendChild(n);
|
|
3094
|
+
if (n.nodeType === 1 && !firstEl) firstEl = n;
|
|
3095
|
+
}
|
|
3096
|
+
if (!frag.childNodes.length) return;
|
|
1622
3097
|
if (selectedEl && selectedEl !== doc.body && selectedEl.parentNode) {
|
|
1623
|
-
selectedEl.parentNode.insertBefore(
|
|
3098
|
+
selectedEl.parentNode.insertBefore(frag, selectedEl.nextSibling);
|
|
1624
3099
|
} else {
|
|
1625
|
-
doc.body.appendChild(
|
|
3100
|
+
doc.body.appendChild(frag);
|
|
1626
3101
|
}
|
|
1627
|
-
selectElement(
|
|
3102
|
+
if (firstEl) selectElement(firstEl);
|
|
1628
3103
|
markDirty();
|
|
3104
|
+
scheduleDomTreeRefresh();
|
|
1629
3105
|
} catch(err) { console.warn('[V2] insertHtml:', err); }
|
|
1630
3106
|
}
|
|
1631
3107
|
|
|
@@ -1650,14 +3126,24 @@ function renderSidebar(filter) {
|
|
|
1650
3126
|
}
|
|
1651
3127
|
if (!q && typeof Vvveb !== 'undefined' && Vvveb.Components && Vvveb.Components.list) {
|
|
1652
3128
|
var vvItems = [], clist = Vvveb.Components.list;
|
|
1653
|
-
for (var ck in clist) {
|
|
3129
|
+
for (var ck in clist) {
|
|
3130
|
+
if (!Object.prototype.hasOwnProperty.call(clist, ck)) continue;
|
|
3131
|
+
var cdef = clist[ck];
|
|
3132
|
+
vvItems.push({
|
|
3133
|
+
key: ck,
|
|
3134
|
+
name: (cdef.name || ck).replace(/^html\\//, '').slice(0, 22),
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
vvItems.sort(function(a, b) { return a.name.localeCompare(b.name); });
|
|
1654
3138
|
if (vvItems.length > 0) {
|
|
1655
3139
|
var h2 = document.createElement('div'); h2.className = 'cg-hdr'; h2.textContent = 'Bootstrap 5'; compTab.appendChild(h2);
|
|
1656
3140
|
var g2 = document.createElement('div'); g2.className = 'cg-grid';
|
|
1657
|
-
vvItems.slice(0,
|
|
1658
|
-
var item = document.createElement('div'); item.className = 'cg-item'; item.title =
|
|
1659
|
-
item.innerHTML = '<div class="cg-icon"><i class="bi bi-puzzle"></i></div><div class="cg-name">' +
|
|
1660
|
-
|
|
3141
|
+
vvItems.slice(0, 48).forEach(function(entry) {
|
|
3142
|
+
var item = document.createElement('div'); item.className = 'cg-item'; item.title = entry.key;
|
|
3143
|
+
item.innerHTML = '<div class="cg-icon"><i class="bi bi-puzzle"></i></div><div class="cg-name">' + esc(entry.name) + '</div>';
|
|
3144
|
+
(function(typeKey) {
|
|
3145
|
+
item.onclick = function() { insertVvvebComponent(typeKey); };
|
|
3146
|
+
})(entry.key);
|
|
1661
3147
|
g2.appendChild(item);
|
|
1662
3148
|
});
|
|
1663
3149
|
compTab.appendChild(g2);
|
|
@@ -1694,7 +3180,10 @@ function renderSidebar(filter) {
|
|
|
1694
3180
|
if (!secTab.children.length) secTab.innerHTML = '<div style="padding:20px;text-align:center;color:#444;font-size:12px">No sections match</div>';
|
|
1695
3181
|
}
|
|
1696
3182
|
|
|
1697
|
-
document.getElementById('comp-search').addEventListener('input', function() {
|
|
3183
|
+
document.getElementById('comp-search').addEventListener('input', function() {
|
|
3184
|
+
if (currentLeftTab === 'elements') renderDomTree(this.value);
|
|
3185
|
+
else renderSidebar(this.value);
|
|
3186
|
+
});
|
|
1698
3187
|
|
|
1699
3188
|
// \u2500\u2500 Save / Close \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1700
3189
|
document.getElementById('btn-save').addEventListener('click', handleSave);
|
|
@@ -1712,7 +3201,7 @@ function handleSave() {
|
|
|
1712
3201
|
}
|
|
1713
3202
|
|
|
1714
3203
|
function handleClose() {
|
|
1715
|
-
|
|
3204
|
+
// Unsaved-changes UX lives in the parent (PlatformVisualEditorV2); avoid double confirm here.
|
|
1716
3205
|
send('close-editor', {});
|
|
1717
3206
|
}
|
|
1718
3207
|
|
|
@@ -1722,11 +3211,92 @@ document.addEventListener('keydown', function(e) {
|
|
|
1722
3211
|
if (meta && !e.shiftKey && e.key === 'z') { e.preventDefault(); if (typeof Vvveb !== 'undefined' && Vvveb.Undo) Vvveb.Undo.undo(); }
|
|
1723
3212
|
if (meta && e.shiftKey && e.key === 'z') { e.preventDefault(); if (typeof Vvveb !== 'undefined' && Vvveb.Undo) Vvveb.Undo.redo(); }
|
|
1724
3213
|
if (meta && e.key === 's') { e.preventDefault(); handleSave(); }
|
|
1725
|
-
if (e.key === 'Escape'
|
|
3214
|
+
if (e.key === 'Escape') {
|
|
3215
|
+
var openTips = document.querySelectorAll('.ve-pl-tip.is-tip-open');
|
|
3216
|
+
if (openTips.length) {
|
|
3217
|
+
e.preventDefault();
|
|
3218
|
+
for (var ti = 0; ti < openTips.length; ti++) {
|
|
3219
|
+
var h = openTips[ti];
|
|
3220
|
+
h.classList.remove('is-tip-open');
|
|
3221
|
+
h.classList.remove('is-tip-flip');
|
|
3222
|
+
var tp = h.querySelector('.ve-pl-tooltip');
|
|
3223
|
+
if (tp) tp.style.display = '';
|
|
3224
|
+
}
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
3227
|
+
setDragHandleActive(false);
|
|
3228
|
+
if (selectedEl) deselectElement();
|
|
3229
|
+
}
|
|
1726
3230
|
});
|
|
1727
3231
|
document.getElementById('btn-undo').addEventListener('click', function() { if (typeof Vvveb !== 'undefined' && Vvveb.Undo) Vvveb.Undo.undo(); });
|
|
1728
3232
|
document.getElementById('btn-redo').addEventListener('click', function() { if (typeof Vvveb !== 'undefined' && Vvveb.Undo) Vvveb.Undo.redo(); });
|
|
1729
3233
|
|
|
3234
|
+
function layoutLoadingTooltip(host) {
|
|
3235
|
+
var tip = host.querySelector('.ve-pl-tooltip');
|
|
3236
|
+
if (!tip || !host.classList.contains('is-tip-open')) return;
|
|
3237
|
+
var cr = host.getBoundingClientRect();
|
|
3238
|
+
var gap = 10;
|
|
3239
|
+
var pad = 12;
|
|
3240
|
+
host.classList.remove('is-tip-flip');
|
|
3241
|
+
tip.style.display = 'block';
|
|
3242
|
+
tip.style.visibility = 'hidden';
|
|
3243
|
+
var tw = tip.offsetWidth;
|
|
3244
|
+
var th = tip.offsetHeight;
|
|
3245
|
+
tip.style.visibility = '';
|
|
3246
|
+
var cx = cr.left + cr.width / 2;
|
|
3247
|
+
cx = Math.max(pad + tw / 2, Math.min(cx, window.innerWidth - pad - tw / 2));
|
|
3248
|
+
tip.style.left = Math.round(cx) + 'px';
|
|
3249
|
+
tip.style.transform = 'translateX(-50%)';
|
|
3250
|
+
var preferBelow = host.classList.contains('ve-pl-tip-below');
|
|
3251
|
+
var top;
|
|
3252
|
+
if (preferBelow) {
|
|
3253
|
+
top = cr.bottom + gap;
|
|
3254
|
+
if (top + th > window.innerHeight - pad) {
|
|
3255
|
+
top = cr.top - gap - th;
|
|
3256
|
+
host.classList.add('is-tip-flip');
|
|
3257
|
+
}
|
|
3258
|
+
} else {
|
|
3259
|
+
top = cr.top - gap - th;
|
|
3260
|
+
if (top < pad) {
|
|
3261
|
+
top = cr.bottom + gap;
|
|
3262
|
+
host.classList.add('is-tip-flip');
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
top = Math.max(pad, Math.min(top, window.innerHeight - pad - th));
|
|
3266
|
+
tip.style.top = Math.round(top) + 'px';
|
|
3267
|
+
}
|
|
3268
|
+
|
|
3269
|
+
function bindLoadingTooltipPositioning() {
|
|
3270
|
+
function openTip(host) {
|
|
3271
|
+
host.classList.add('is-tip-open');
|
|
3272
|
+
layoutLoadingTooltip(host);
|
|
3273
|
+
requestAnimationFrame(function() { layoutLoadingTooltip(host); });
|
|
3274
|
+
}
|
|
3275
|
+
function closeTip(host) {
|
|
3276
|
+
host.classList.remove('is-tip-open');
|
|
3277
|
+
host.classList.remove('is-tip-flip');
|
|
3278
|
+
var tip = host.querySelector('.ve-pl-tooltip');
|
|
3279
|
+
if (tip) tip.style.display = '';
|
|
3280
|
+
}
|
|
3281
|
+
var nodes = document.querySelectorAll('.ve-pl-tip');
|
|
3282
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
3283
|
+
(function(host) {
|
|
3284
|
+
host.addEventListener('mouseenter', function() { openTip(host); });
|
|
3285
|
+
host.addEventListener('mouseleave', function() { closeTip(host); });
|
|
3286
|
+
host.addEventListener('focusin', function() { openTip(host); });
|
|
3287
|
+
host.addEventListener('focusout', function(e) {
|
|
3288
|
+
if (!host.contains(e.relatedTarget)) closeTip(host);
|
|
3289
|
+
});
|
|
3290
|
+
})(nodes[i]);
|
|
3291
|
+
}
|
|
3292
|
+
function reflowOpenTips() {
|
|
3293
|
+
var o = document.querySelectorAll('.ve-pl-tip.is-tip-open');
|
|
3294
|
+
for (var j = 0; j < o.length; j++) layoutLoadingTooltip(o[j]);
|
|
3295
|
+
}
|
|
3296
|
+
window.addEventListener('scroll', reflowOpenTips, true);
|
|
3297
|
+
window.addEventListener('resize', reflowOpenTips);
|
|
3298
|
+
}
|
|
3299
|
+
|
|
1730
3300
|
// \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1731
3301
|
function registerCROSections() {
|
|
1732
3302
|
if (typeof Vvveb === 'undefined' || !Vvveb.Sections) return;
|
|
@@ -1736,22 +3306,57 @@ function registerCROSections() {
|
|
|
1736
3306
|
window.addEventListener('load', function() {
|
|
1737
3307
|
registerCROSections();
|
|
1738
3308
|
renderSidebar();
|
|
3309
|
+
renderDomTree(document.getElementById('comp-search').value);
|
|
1739
3310
|
vvvebReady = true;
|
|
3311
|
+
bindLoadingTooltipPositioning();
|
|
1740
3312
|
|
|
1741
|
-
//
|
|
1742
|
-
document.getElementById('loading').classList.add('hidden');
|
|
3313
|
+
// Show no-url state until experiment arrives
|
|
1743
3314
|
showNoUrl(true);
|
|
1744
3315
|
|
|
1745
3316
|
// After each iframe load: apply variation, wire click+mutation handlers
|
|
1746
3317
|
var iframe = document.getElementById('iframeId');
|
|
1747
3318
|
iframe.addEventListener('load', function() {
|
|
1748
3319
|
if (!iframe.src || iframe.src === 'about:blank' || iframe.src === window.location.href) return;
|
|
1749
|
-
|
|
1750
|
-
|
|
3320
|
+
var doc = iframe.contentDocument;
|
|
3321
|
+
if (!doc || !doc.body) return;
|
|
3322
|
+
var docUrl = '';
|
|
3323
|
+
try { docUrl = String(doc.URL || ''); } catch(_) {}
|
|
3324
|
+
// Stale events: src may already be the proxy URL while the document is still
|
|
3325
|
+
// about:blank (e.g. src cleared then reset to force reload).
|
|
3326
|
+
if (docUrl === 'about:blank') return;
|
|
3327
|
+
if (!iframeDocMatchesNavigatedSrc(iframe, doc)) return;
|
|
3328
|
+
attachIframeLoadingUntilComplete(iframe);
|
|
3329
|
+
stopIframeContentApplyWatcher();
|
|
1751
3330
|
deselectElement();
|
|
1752
3331
|
applyActiveVariationHtml();
|
|
1753
|
-
|
|
1754
|
-
|
|
3332
|
+
syncIframeInteractions('iframe-load');
|
|
3333
|
+
});
|
|
3334
|
+
|
|
3335
|
+
document.getElementById('sf-drag').addEventListener('click', function(e) {
|
|
3336
|
+
e.preventDefault();
|
|
3337
|
+
e.stopPropagation();
|
|
3338
|
+
if (!selectedEl) return;
|
|
3339
|
+
setDragHandleActive(!dragHandleActive);
|
|
3340
|
+
});
|
|
3341
|
+
document.getElementById('sf-dup').addEventListener('click', function(e) {
|
|
3342
|
+
e.preventDefault();
|
|
3343
|
+
e.stopPropagation();
|
|
3344
|
+
duplicateSelectedEl();
|
|
3345
|
+
});
|
|
3346
|
+
document.getElementById('sf-hide').addEventListener('click', function(e) {
|
|
3347
|
+
e.preventDefault();
|
|
3348
|
+
e.stopPropagation();
|
|
3349
|
+
toggleHideSelectedEl();
|
|
3350
|
+
});
|
|
3351
|
+
document.getElementById('sf-del').addEventListener('click', function(e) {
|
|
3352
|
+
e.preventDefault();
|
|
3353
|
+
e.stopPropagation();
|
|
3354
|
+
deleteSelectedEl();
|
|
3355
|
+
});
|
|
3356
|
+
document.getElementById('sf-close').addEventListener('click', function(e) {
|
|
3357
|
+
e.preventDefault();
|
|
3358
|
+
e.stopPropagation();
|
|
3359
|
+
deselectElement();
|
|
1755
3360
|
});
|
|
1756
3361
|
|
|
1757
3362
|
send('editor-ready', {});
|