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