@accelerated-agency/visual-editor 0.4.2 → 0.4.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/vite.js CHANGED
@@ -78,17 +78,26 @@ function hasTrackingMarker(input, markers) {
78
78
  }
79
79
  return false;
80
80
  }
81
+ function patchKnownUnsafeEditorPatterns(scriptTag) {
82
+ let out = String(scriptTag || "");
83
+ out = out.replace(
84
+ /dragRegion\.addEventListener\((['"])pointerdown\1,\s*onPointerDown\);?/g,
85
+ "if (typeof dragRegion !== 'undefined' && dragRegion) dragRegion.addEventListener($1pointerdown$1, onPointerDown);"
86
+ );
87
+ return out;
88
+ }
81
89
  function stripTrackingScriptsFromScrapedHtml(html, markers) {
82
90
  let removedCount = 0;
83
91
  let out = html;
84
92
  out = out.replace(/<script\b[\s\S]*?<\/script>/gi, (tag) => {
93
+ var patchedTag = patchKnownUnsafeEditorPatterns(tag);
85
94
  const srcMatch = tag.match(/\bsrc\s*=\s*(["'])(.*?)\1/i);
86
95
  const src = srcMatch?.[2] || "";
87
96
  if (hasTrackingMarker(src, markers) || hasTrackingMarker(tag, markers)) {
88
97
  removedCount += 1;
89
98
  return "";
90
99
  }
91
- return tag;
100
+ return patchedTag;
92
101
  });
93
102
  out = out.replace(/<noscript\b[\s\S]*?<\/noscript>/gi, (tag) => {
94
103
  if (!hasTrackingMarker(tag, markers)) return tag;
@@ -852,7 +861,7 @@ select.pr-inp{cursor:pointer;background:#fff}
852
861
  <button class="tb-dk-btn" title="More options"><i class="bi bi-three-dots"></i></button>
853
862
  </div>
854
863
  <button class="tb-dk-btn" id="btn-undo" title="Undo (\u2318Z)"><i class="bi bi-arrow-counterclockwise"></i></button>
855
- <button class="tb-dk-btn" id="btn-redo" title="Redo (\u2318\u21E7Z)"><i class="bi bi-arrow-clockwise"></i></button>
864
+ <button class="tb-dk-btn" id="btn-redo" title="Redo (\u2318\u21E7Z)" style="display:none"><i class="bi bi-arrow-clockwise"></i></button>
856
865
  </div>
857
866
 
858
867
  <div id="iframe-loading-toolbar" class="tb-page-loading" aria-live="polite" aria-atomic="true">
@@ -923,8 +932,8 @@ select.pr-inp{cursor:pointer;background:#fff}
923
932
  <!-- Elements -->
924
933
  <div class="lp-sec lp-sec-no-border">
925
934
  <div class="lp-sec-hd">
926
- <span class="lp-sec-hd-left">Elements <i class="bi bi-info-circle lp-info-icon" title="Page elements"></i></span>
927
- <button class="lp-add-btn" title="Add element">+ Add</button>
935
+ <span class="lp-sec-hd-left">Elements <i style="display:none" class="bi bi-info-circle lp-info-icon" title="Page elements"></i></span>
936
+ <button class="lp-add-btn" id="btn-add-element" title="Add element">+ Add</button>
928
937
  </div>
929
938
 
930
939
  <!-- Search (hidden, kept for JS) -->
@@ -994,14 +1003,16 @@ select.pr-inp{cursor:pointer;background:#fff}
994
1003
 
995
1004
  <!-- Right panel -->
996
1005
  <div id="right-panel">
997
- <!-- Left-tab controls moved here -->
998
- <div class="section-components-tabs">
999
- <div class="lp-tab" onclick="switchSectionComponentsTab('components')">Components</div>
1000
- <div class="lp-tab" onclick="switchSectionComponentsTab('sections')">Sections</div>
1001
- </div>
1002
- <div class="lp-body">
1003
- <div id="tab-components" class="tab-pane"></div>
1004
- <div id="tab-sections" class="tab-pane"></div>
1006
+ <div id="section-components-panel" style="display:none">
1007
+ <!-- Left-tab controls moved here -->
1008
+ <div class="section-components-tabs">
1009
+ <div class="lp-tab" onclick="switchSectionComponentsTab('components')">Components</div>
1010
+ <div class="lp-tab" onclick="switchSectionComponentsTab('sections')">Sections</div>
1011
+ </div>
1012
+ <div class="lp-body">
1013
+ <div id="tab-components" class="tab-pane"></div>
1014
+ <div id="tab-sections" class="tab-pane"></div>
1015
+ </div>
1005
1016
  </div>
1006
1017
  <!-- Element badge (hidden until selection) -->
1007
1018
  <div id="el-info" style="display:none">
@@ -1132,6 +1143,8 @@ select.pr-inp{cursor:pointer;background:#fff}
1132
1143
  </div>
1133
1144
 
1134
1145
  <!-- CDN scripts -->
1146
+ <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
1147
+ <script src="https://cdn.jsdelivr.net/npm/jquery-ui-dist@1.13.3/jquery-ui.min.js"></script>
1135
1148
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
1136
1149
  <script>
1137
1150
  window.__veLoadFallback = function(el, candidates){
@@ -1159,10 +1172,6 @@ window.__veLoadFallback = function(el, candidates){
1159
1172
  onerror="window.__veLoadFallback(this,'https://cdn.jsdelivr.net/npm/vvvebjs@latest/libs/builder/inputs.js||https://unpkg.com/vvvebjs@latest/libs/builder/inputs.js')"
1160
1173
  ></script>
1161
1174
  <!-- components.js removed (frequently missing on npm CDN); safety stubs below prevent bootstrap/widgets loader errors -->
1162
- // <script
1163
- // src="https://cdn.jsdelivr.net/npm/vvvebjs@latest/libs/builder/components-widgets.js"
1164
- // onerror="window.__veLoadFallback(this,'https://cdn.jsdelivr.net/npm/vvvebjs@latest/libs/builder/components-widgets.js||https://unpkg.com/vvvebjs@latest/libs/builder/components-widgets.js')"
1165
- // ></script>
1166
1175
  <script>
1167
1176
  /* Safety stub: if components.js didn't define these, create empty arrays so
1168
1177
  components-bootstrap5.js doesn't throw ReferenceError on load. */
@@ -1191,7 +1200,7 @@ if (window.Vvveb && window.Vvveb.Components) {
1191
1200
  </script>
1192
1201
  <script
1193
1202
  src="https://cdn.jsdelivr.net/npm/vvvebjs@latest/libs/builder/components-bootstrap4.js"
1194
- onerror="window.__veLoadFallback(this,'https://cdn.jsdelivr.net/npm/vvvebjs@latest/libs/builder/components-bootstrap4.js||https://unpkg.com/vvvebjs@latest/libs/builder/components-bootstrap4.js')"
1203
+ onerror="window.__veLoadFallback(this,'https://cdn.jsdelivr.net/npm/vvvebjs@latest/libs/builder/components-bootstrap5.js||https://cdn.jsdelivr.net/npm/vvvebjs@latest/libs/builder/components-bootstrap4.js||https://unpkg.com/vvvebjs@latest/libs/builder/components-bootstrap5.js||https://unpkg.com/vvvebjs@latest/libs/builder/components-bootstrap4.js')"
1195
1204
  ></script>
1196
1205
  <script
1197
1206
  src="https://cdn.jsdelivr.net/npm/vvvebjs@latest/libs/builder/components-widgets.js"
@@ -1660,6 +1669,19 @@ function switchSectionComponentsTab(tab) {
1660
1669
  renderSidebar(inp ? inp.value : '');
1661
1670
  }
1662
1671
 
1672
+ function toggleSectionComponentsPanel(forceVisible) {
1673
+ var panel = document.getElementById('section-components-panel');
1674
+ if (!panel) return;
1675
+ var shouldShow =
1676
+ typeof forceVisible === 'boolean'
1677
+ ? forceVisible
1678
+ : panel.style.display === 'none';
1679
+ panel.style.display = shouldShow ? '' : 'none';
1680
+ if (shouldShow) {
1681
+ switchSectionComponentsTab(currentSectionComponentsTab || 'components');
1682
+ }
1683
+ }
1684
+
1663
1685
  // \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
1664
1686
  function toggleAcc(name) {
1665
1687
  var sec = document.getElementById('acc-' + name);
@@ -1869,6 +1891,24 @@ function syncDesignInput(change) {
1869
1891
  function removeStateChange(idx) {
1870
1892
  var change = stateChanges[idx];
1871
1893
  if (!change) return;
1894
+ if (change.isStructuralLive) {
1895
+ removeSessionStructuralRowByTimestamp(
1896
+ change.structuralVarId || activeVarId,
1897
+ change.vveTs,
1898
+ );
1899
+ stateChanges.splice(idx, 1);
1900
+ commitStateChangesForActiveVariation();
1901
+ renderStatesTab();
1902
+ if (currentMainTab === 'history') renderHistoryTab();
1903
+ try {
1904
+ delete varHtmlCache[activeVarId];
1905
+ } catch(_) {}
1906
+ appliedStructuralChangesetKeys = {};
1907
+ recomputeEditorDirty();
1908
+ scheduleDomTreeRefresh();
1909
+ softReloadEditorIframe();
1910
+ return;
1911
+ }
1872
1912
  revertChangeOnDom(change);
1873
1913
  syncDesignInput(change);
1874
1914
  stateChanges.splice(idx, 1);
@@ -2225,6 +2265,14 @@ function getLatestHistoryUndoTarget() {
2225
2265
  return list.length ? list[0] : null;
2226
2266
  }
2227
2267
 
2268
+ function getLatestLiveUndoTarget() {
2269
+ var list = getUnifiedHistoryItems();
2270
+ for (var i = 0; i < list.length; i++) {
2271
+ if (list[i] && list[i].source === 'live') return list[i];
2272
+ }
2273
+ return null;
2274
+ }
2275
+
2228
2276
  function changesetListHasStructural(arr) {
2229
2277
  if (!arr || !arr.length) return false;
2230
2278
  for (var i = 0; i < arr.length; i++) {
@@ -2323,8 +2371,15 @@ function removeHistoryChangeset(idx, evt) {
2323
2371
  function clearAllHistoryChangesets() {
2324
2372
  var v = getActiveVariationForHistory();
2325
2373
  if (!v) return;
2326
- if (!parseVariationChangesets(v).length) return;
2374
+ var hasSavedChangesets = parseVariationChangesets(v).length > 0;
2375
+ var hasSessionStructural =
2376
+ !!(activeVarId && sessionStructuralChainRowsByVarId[activeVarId] && sessionStructuralChainRowsByVarId[activeVarId].length);
2377
+ if (!hasSavedChangesets && !hasSessionStructural) return;
2378
+
2327
2379
  persistActiveVariationChangesets([]);
2380
+ if (activeVarId) {
2381
+ sessionStructuralChainRowsByVarId[activeVarId] = [];
2382
+ }
2328
2383
  appliedChangesetSnapshots = {};
2329
2384
  appliedStructuralChangesetKeys = {};
2330
2385
  try {
@@ -2337,6 +2392,10 @@ function clearAllHistoryChangesets() {
2337
2392
  }
2338
2393
 
2339
2394
  function clearAllUnifiedHistory() {
2395
+ if (activeVarId) {
2396
+ // Ensure structural unsaved rows are also removed by "Clear all changes".
2397
+ sessionStructuralChainRowsByVarId[activeVarId] = [];
2398
+ }
2340
2399
  clearAllStates();
2341
2400
  clearAllHistoryChangesets();
2342
2401
  if (currentMainTab === 'history') renderHistoryTab();
@@ -2348,15 +2407,32 @@ var VVE_LOCAL_STORAGE_PREFIX = 'vve:';
2348
2407
 
2349
2408
  function clearVisualEditorLocalStorage() {
2350
2409
  try {
2351
- for (var i = localStorage.length - 1; i >= 0; i--) {
2352
- var k = localStorage.key(i);
2353
- if (k && k.indexOf(VVE_LOCAL_STORAGE_PREFIX) === 0) {
2354
- localStorage.removeItem(k);
2410
+ var stores = [];
2411
+ try { stores.push(localStorage); } catch(_) {}
2412
+ try { stores.push(sessionStorage); } catch(_) {}
2413
+ for (var si = 0; si < stores.length; si++) {
2414
+ var store = stores[si];
2415
+ if (!store) continue;
2416
+ for (var i = store.length - 1; i >= 0; i--) {
2417
+ var k = store.key(i);
2418
+ if (k && k.indexOf(VVE_LOCAL_STORAGE_PREFIX) === 0) {
2419
+ store.removeItem(k);
2420
+ }
2355
2421
  }
2356
2422
  }
2357
2423
  } catch(_) {}
2358
2424
  }
2359
2425
 
2426
+ function clearPersistedActiveVariationForData(data) {
2427
+ try {
2428
+ var sk = activeVariationStorageKeyFromPayload(data);
2429
+ if (sk && sk !== VVE_LOCAL_STORAGE_PREFIX + 'activeVar::') {
2430
+ try { localStorage.removeItem(sk); } catch(_) {}
2431
+ try { sessionStorage.removeItem(sk); } catch(_) {}
2432
+ }
2433
+ } catch(_) {}
2434
+ }
2435
+
2360
2436
  function activeVariationStorageKeyFromPayload(data) {
2361
2437
  return (
2362
2438
  VVE_LOCAL_STORAGE_PREFIX +
@@ -2395,14 +2471,18 @@ function writePersistedActiveVariationId(varId) {
2395
2471
  * @param allowPrevMemory when true, keep in-session activeVarId if still valid (skip-reload path).
2396
2472
  */
2397
2473
  function pickActiveVariationIdForLoad(data, variationsArr, prevMemoryId, allowPrevMemory) {
2398
- var baseline = variationsArr.find(function(v) { return v.baseline; });
2399
- var fallback = (baseline || variationsArr[0] || {})._id || null;
2474
+ var firstNonBaseline = variationsArr.find(function(v) { return !v.baseline; });
2475
+ var fallback = (firstNonBaseline || variationsArr[0] || {})._id || null;
2400
2476
  if (!variationsArr.length) return null;
2401
2477
  if (allowPrevMemory && prevMemoryId && variationsArr.some(function(v) { return v._id === prevMemoryId; })) {
2402
2478
  return prevMemoryId;
2403
2479
  }
2404
2480
  var stored = readPersistedActiveVariationId(data);
2405
2481
  if (stored && variationsArr.some(function(v) { return v._id === stored; })) {
2482
+ var storedVar = variationsArr.find(function(v) { return v._id === stored; });
2483
+ if (storedVar && storedVar.baseline && firstNonBaseline && firstNonBaseline._id) {
2484
+ return firstNonBaseline._id;
2485
+ }
2406
2486
  return stored;
2407
2487
  }
2408
2488
  return fallback;
@@ -2424,12 +2504,24 @@ function handleLoadExperiment(data) {
2424
2504
  var extraTrackingMarkers = Array.isArray(data && data.trackingMarkers)
2425
2505
  ? data.trackingMarkers.filter(function(m){return typeof m === 'string' && m.trim().length > 0;})
2426
2506
  : [];
2507
+ var conversionProxyBaseUrl = (typeof (data && data.conversionProxyBaseUrl) === 'string'
2508
+ ? data.conversionProxyBaseUrl
2509
+ : '').trim().replace(/\\/+$/, '');
2510
+ var proxyPath = '/api/conversion-proxy';
2511
+ // Keep iframe navigation same-origin for DOM access (selection/editing).
2512
+ // If conversionProxyBaseUrl is provided, middleware delegates upstream fetches
2513
+ // server-side while browser URLs stay local.
2514
+ var proxyRoot = proxyPath;
2427
2515
  var trackingMarkersParam = extraTrackingMarkers.length
2428
2516
  ? '&trackingMarkers=' + encodeURIComponent(JSON.stringify(extraTrackingMarkers))
2429
2517
  : '';
2430
- var proxyUrl = '/api/conversion-proxy?password=' + encodeURIComponent(data.editorPassword || '') +
2518
+ var conversionProxyBaseUrlParam = conversionProxyBaseUrl
2519
+ ? '&conversionProxyBaseUrl=' + encodeURIComponent(conversionProxyBaseUrl)
2520
+ : '';
2521
+ var proxyUrl = proxyRoot + '?password=' + encodeURIComponent(data.editorPassword || '') +
2431
2522
  '&url=' + encodeURIComponent(pageUrl) +
2432
2523
  '&strictObserverFreeze=' + encodeURIComponent(data && data.strictObserverFreeze ? '1' : '0') +
2524
+ conversionProxyBaseUrlParam +
2433
2525
  trackingMarkersParam;
2434
2526
 
2435
2527
  // Parent often re-posts load-experiment when React re-renders (new object identity) or
@@ -2614,7 +2706,13 @@ function granularAnySelectorMatches(doc, cs) {
2614
2706
 
2615
2707
  /** Bust bfcache / same-URL no-op reloads so the iframe actually re-parses (loading \u2192 interactive). */
2616
2708
  function appendIframeReloadBust(url) {
2617
- return url;
2709
+ try {
2710
+ var u = new URL(String(url || ''), window.location.href);
2711
+ u.searchParams.set('_vve_reload', String(Date.now()));
2712
+ return u.toString();
2713
+ } catch(_) {
2714
+ return url;
2715
+ }
2618
2716
  }
2619
2717
 
2620
2718
  // True when the iframe contentDocument belongs to the current iframe.src navigation.
@@ -2992,6 +3090,44 @@ function appendSessionStructuralChainRow(varId, row) {
2992
3090
  sessionStructuralChainRowsByVarId[varId].push(row);
2993
3091
  }
2994
3092
 
3093
+ function markStructuralRowApplied(row) {
3094
+ try {
3095
+ var k = structuralChangesetDedupKey(row);
3096
+ if (k) appliedStructuralChangesetKeys[k] = true;
3097
+ } catch(_) {}
3098
+ }
3099
+
3100
+ function logStructuralStateChange(row, label, value, targetEl) {
3101
+ if (!row || !row.selector) return;
3102
+ stateChanges.push({
3103
+ selector: row.selector,
3104
+ inputId: 'vve-struct',
3105
+ label: label || 'Structure change',
3106
+ cssProp: null,
3107
+ value: value != null ? String(value) : String(row.type || ''),
3108
+ targetEl: targetEl || null,
3109
+ originalValue: '',
3110
+ vveTs: row.vveTs || nextHistoryTimestamp(),
3111
+ isStructuralLive: true,
3112
+ structuralVarId: activeVarId || null,
3113
+ structuralType: row.type || '',
3114
+ });
3115
+ if (currentMainTab === 'history') renderHistoryTab();
3116
+ commitStateChangesForActiveVariation();
3117
+ }
3118
+
3119
+ function removeSessionStructuralRowByTimestamp(varId, ts) {
3120
+ if (!varId || !ts) return;
3121
+ var arr = sessionStructuralChainRowsByVarId[varId];
3122
+ if (!arr || !arr.length) return;
3123
+ for (var i = arr.length - 1; i >= 0; i--) {
3124
+ if (arr[i] && arr[i].vveTs === ts) {
3125
+ arr.splice(i, 1);
3126
+ return;
3127
+ }
3128
+ }
3129
+ }
3130
+
2995
3131
  /** One States-tab row -> Conversion.io chain-set shape (matches applyChangesetEntry). */
2996
3132
  function stateChangeToChainSet(c) {
2997
3133
  if (!c || !c.selector) return null;
@@ -3686,12 +3822,15 @@ function duplicateSelectedEl() {
3686
3822
  clone.setAttribute('data-vve-instance', generateVveInstanceId());
3687
3823
  } catch(_) {}
3688
3824
  if (activeVarId) {
3689
- appendSessionStructuralChainRow(activeVarId, {
3825
+ var dupRow = {
3690
3826
  selector: anchorSel,
3691
3827
  type: 'insert',
3692
3828
  action: 'after',
3693
3829
  html: clone.outerHTML,
3694
- });
3830
+ };
3831
+ appendSessionStructuralChainRow(activeVarId, dupRow);
3832
+ markStructuralRowApplied(dupRow);
3833
+ logStructuralStateChange(dupRow, 'Duplicated via toolbar', 'Duplicate inserted', selectedEl);
3695
3834
  }
3696
3835
  selectedEl.parentNode.insertBefore(clone, selectedEl.nextSibling);
3697
3836
  saveCurrentVariationHtml();
@@ -3707,23 +3846,27 @@ function toggleHideSelectedEl() {
3707
3846
  selectedEl.style.visibility = '';
3708
3847
  selectedEl.removeAttribute('data-vve-hidden');
3709
3848
  if (activeVarId) {
3710
- appendSessionStructuralChainRow(activeVarId, {
3849
+ var showRow = {
3711
3850
  selector: hidSel,
3712
3851
  type: 'style',
3713
3852
  property: 'visibility',
3714
3853
  value: '',
3715
- });
3854
+ };
3855
+ appendSessionStructuralChainRow(activeVarId, showRow);
3856
+ logStructuralStateChange(showRow, 'Shown via toolbar', 'Visibility restored', selectedEl);
3716
3857
  }
3717
3858
  } else {
3718
3859
  selectedEl.style.visibility = 'hidden';
3719
3860
  selectedEl.setAttribute('data-vve-hidden', '1');
3720
3861
  if (activeVarId) {
3721
- appendSessionStructuralChainRow(activeVarId, {
3862
+ var hideRow = {
3722
3863
  selector: hidSel,
3723
3864
  type: 'style',
3724
3865
  property: 'visibility',
3725
3866
  value: 'hidden',
3726
- });
3867
+ };
3868
+ appendSessionStructuralChainRow(activeVarId, hideRow);
3869
+ logStructuralStateChange(hideRow, 'Hidden via toolbar', 'Visibility set to hidden', selectedEl);
3727
3870
  }
3728
3871
  }
3729
3872
  saveCurrentVariationHtml();
@@ -3734,7 +3877,11 @@ function deleteSelectedEl() {
3734
3877
  if (!selectedEl || !selectedEl.parentNode) return;
3735
3878
  var delSel = buildSelector(selectedEl);
3736
3879
  selectedEl.remove();
3737
- if (activeVarId) appendSessionStructuralChainRow(activeVarId, { selector: delSel, type: 'remove' });
3880
+ if (activeVarId) {
3881
+ var delRow = { selector: delSel, type: 'remove' };
3882
+ appendSessionStructuralChainRow(activeVarId, delRow);
3883
+ logStructuralStateChange(delRow, 'Deleted via toolbar', 'Element removed', null);
3884
+ }
3738
3885
  saveCurrentVariationHtml();
3739
3886
  recomputeEditorDirty();
3740
3887
  deselectElement();
@@ -4635,19 +4782,25 @@ function recordReorderAfterDrag(movedEl) {
4635
4782
  var prev = movedEl.previousElementSibling;
4636
4783
  var next = movedEl.nextElementSibling;
4637
4784
  if (prev) {
4638
- appendSessionStructuralChainRow(activeVarId, {
4785
+ var reorderAfterRow = {
4639
4786
  selector: buildSelector(movedEl),
4640
4787
  type: 'reorder',
4641
4788
  targetSelector: buildSelector(prev),
4642
4789
  action: 'after',
4643
- });
4790
+ };
4791
+ appendSessionStructuralChainRow(activeVarId, reorderAfterRow);
4792
+ markStructuralRowApplied(reorderAfterRow);
4793
+ logStructuralStateChange(reorderAfterRow, 'Reordered via drag', 'Moved after sibling', movedEl);
4644
4794
  } else if (next) {
4645
- appendSessionStructuralChainRow(activeVarId, {
4795
+ var reorderBeforeRow = {
4646
4796
  selector: buildSelector(movedEl),
4647
4797
  type: 'reorder',
4648
4798
  targetSelector: buildSelector(next),
4649
4799
  action: 'before',
4650
- });
4800
+ };
4801
+ appendSessionStructuralChainRow(activeVarId, reorderBeforeRow);
4802
+ markStructuralRowApplied(reorderBeforeRow);
4803
+ logStructuralStateChange(reorderBeforeRow, 'Reordered via drag', 'Moved before sibling', movedEl);
4651
4804
  }
4652
4805
  }
4653
4806
 
@@ -4928,12 +5081,15 @@ function insertHtml(html) {
4928
5081
  }
4929
5082
  if (firstEl) selectElement(firstEl);
4930
5083
  if (activeVarId) {
4931
- appendSessionStructuralChainRow(activeVarId, {
5084
+ var insertRow = {
4932
5085
  selector: anchorSel,
4933
5086
  type: 'insert',
4934
5087
  action: 'after',
4935
5088
  html: htmlStr,
4936
- });
5089
+ };
5090
+ appendSessionStructuralChainRow(activeVarId, insertRow);
5091
+ markStructuralRowApplied(insertRow);
5092
+ logStructuralStateChange(insertRow, 'Added component/section', 'Inserted new element', firstEl || selectedEl);
4937
5093
  }
4938
5094
  saveCurrentVariationHtml();
4939
5095
  recomputeEditorDirty();
@@ -5023,6 +5179,14 @@ document.getElementById('comp-search').addEventListener('input', function() {
5023
5179
  renderSidebar(this.value);
5024
5180
  }
5025
5181
  });
5182
+ var btnAddElement = document.getElementById('btn-add-element');
5183
+ if (btnAddElement) {
5184
+ btnAddElement.addEventListener('click', function(e) {
5185
+ e.preventDefault();
5186
+ e.stopPropagation();
5187
+ toggleSectionComponentsPanel();
5188
+ });
5189
+ }
5026
5190
 
5027
5191
  // \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
5028
5192
  document.getElementById('btn-save').addEventListener('click', handleSave);
@@ -5063,11 +5227,21 @@ function handleSave() {
5063
5227
  }
5064
5228
 
5065
5229
  function handleClose() {
5230
+ clearPersistedActiveVariationForData(experimentData);
5066
5231
  clearVisualEditorLocalStorage();
5067
5232
  // Unsaved-changes UX lives in the parent (PlatformVisualEditorV2); avoid double confirm here.
5068
5233
  send('close-editor', {});
5069
5234
  }
5070
5235
 
5236
+ // Defensive cleanup: if parent closes/unmounts the editor shell without
5237
+ // invoking handleClose(), clear persisted VVE keys on unload as well.
5238
+ window.addEventListener('pagehide', function() {
5239
+ clearVisualEditorLocalStorage();
5240
+ });
5241
+ window.addEventListener('beforeunload', function() {
5242
+ clearVisualEditorLocalStorage();
5243
+ });
5244
+
5071
5245
  // \u2500\u2500 Keyboard shortcuts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5072
5246
  function isNativeEditableTarget(target) {
5073
5247
  if (!target || target.nodeType !== 1) return false;
@@ -5089,10 +5263,7 @@ document.addEventListener('keydown', function(e) {
5089
5263
  e.preventDefault();
5090
5264
  runEditorUndo();
5091
5265
  }
5092
- if (meta && e.shiftKey && k === 'z') {
5093
- e.preventDefault();
5094
- runEditorRedo();
5095
- }
5266
+ // Redo is intentionally hidden/disabled in this editor flow.
5096
5267
  if (meta && e.key === 's') { e.preventDefault(); handleSave(); }
5097
5268
  if (e.key === 'Escape') {
5098
5269
  var openTips = document.querySelectorAll('.ve-pl-tip.is-tip-open');
@@ -5112,11 +5283,17 @@ document.addEventListener('keydown', function(e) {
5112
5283
  }
5113
5284
  });
5114
5285
  function runEditorUndo() {
5115
- var target = getLatestHistoryUndoTarget();
5116
- if (target) {
5117
- removeHistoryItem(target.source, target.idx);
5286
+ // Undo only unsaved in-session edits; do not remove persisted history rows.
5287
+ if (!isDirty) return;
5288
+
5289
+ // 1) Prefer live unsaved change stack (stateChanges shown in History tab).
5290
+ var liveTarget = getLatestLiveUndoTarget();
5291
+ if (liveTarget) {
5292
+ removeHistoryItem('live', liveTarget.idx);
5118
5293
  return;
5119
5294
  }
5295
+
5296
+ // 2) Fallback to Vvveb internal undo stack (e.g. structural drag/drop ops).
5120
5297
  if (!(typeof Vvveb !== 'undefined' && Vvveb.Undo)) return;
5121
5298
  Vvveb.Undo.undo();
5122
5299
  saveCurrentVariationHtml();
@@ -5139,11 +5316,10 @@ document.getElementById('btn-undo').addEventListener('click', function(e) {
5139
5316
  e.stopPropagation();
5140
5317
  runEditorUndo();
5141
5318
  });
5142
- document.getElementById('btn-redo').addEventListener('click', function(e) {
5143
- e.preventDefault();
5144
- e.stopPropagation();
5145
- runEditorRedo();
5146
- });
5319
+ var btnRedo = document.getElementById('btn-redo');
5320
+ if (btnRedo) {
5321
+ btnRedo.style.display = 'none';
5322
+ }
5147
5323
 
5148
5324
  function layoutLoadingTooltip(host) {
5149
5325
  var tip = host.querySelector('.ve-pl-tooltip');
@@ -5515,6 +5691,8 @@ function createVisualEditorMiddleware(options) {
5515
5691
  const url = new URL(req.url || "", "http://localhost");
5516
5692
  const targetUrl = url.searchParams.get("url");
5517
5693
  const password = url.searchParams.get("password") || "";
5694
+ const conversionProxyBaseUrlForRequest = (url.searchParams.get("conversionProxyBaseUrl") || "").trim().replace(/\/+$/, "");
5695
+ const proxyRootForRequest = "/api/conversion-proxy";
5518
5696
  const extraTrackingMarkersForRequest = parseTrackingMarkersParam(
5519
5697
  url.searchParams.get("trackingMarkers")
5520
5698
  );
@@ -5567,7 +5745,34 @@ function createVisualEditorMiddleware(options) {
5567
5745
  return false;
5568
5746
  }
5569
5747
  };
5748
+ const workerRawFetch = (input, init = {}) => {
5749
+ const workerUrl = new URL("/api/conversion-proxy", conversionProxyBaseUrlForRequest);
5750
+ workerUrl.searchParams.set("url", input);
5751
+ workerUrl.searchParams.set("raw", "1");
5752
+ const method2 = (init?.method || "GET").toUpperCase();
5753
+ const nextHeaders = {};
5754
+ const h = init?.headers;
5755
+ if (h && typeof h.forEach === "function") {
5756
+ h.forEach((v, k) => {
5757
+ if (!v) return;
5758
+ nextHeaders[k] = String(v);
5759
+ });
5760
+ } else {
5761
+ Object.entries(h || {}).forEach(([k, v]) => {
5762
+ if (!v) return;
5763
+ nextHeaders[k] = Array.isArray(v) ? v.join(",") : String(v);
5764
+ });
5765
+ }
5766
+ return fetch(workerUrl.toString(), {
5767
+ ...init,
5768
+ method: method2,
5769
+ headers: nextHeaders
5770
+ });
5771
+ };
5570
5772
  const upstreamFetch = async (input, init = {}) => {
5773
+ if (conversionProxyBaseUrlForRequest) {
5774
+ return workerRawFetch(input, init);
5775
+ }
5571
5776
  const primary = await scraperFetch(input, init);
5572
5777
  if (!await shouldFallbackFromScraper(primary)) {
5573
5778
  return primary;
@@ -5598,7 +5803,7 @@ function createVisualEditorMiddleware(options) {
5598
5803
  )}`,
5599
5804
  redirect: "manual"
5600
5805
  });
5601
- const setCookies = passResp.headers.getSetCookie?.() || [];
5806
+ const setCookies = passResp.headers.getSetCookie?.() || (passResp.headers.get("set-cookie") ? [String(passResp.headers.get("set-cookie"))] : []);
5602
5807
  cookieHeader = setCookies.map((c) => c.split(";")[0]).join("; ");
5603
5808
  }
5604
5809
  const fetchHeaders = { ...headers };
@@ -5696,7 +5901,7 @@ function createVisualEditorMiddleware(options) {
5696
5901
  html = stripTrackingScriptsFromScrapedHtml(html, trackingMarkersForRequest);
5697
5902
  html = html.replace(/(href|src|action)="\/(?!\/)/g, `$1="${origin}/`);
5698
5903
  const escapedOrigin = origin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5699
- const proxyBase = `/api/conversion-proxy?password=${encodeURIComponent(password)}&url=`;
5904
+ const proxyBase = `${proxyRootForRequest}?password=${encodeURIComponent(password)}&url=`;
5700
5905
  html = html.replace(
5701
5906
  new RegExp(`(href|action)="${escapedOrigin}(/[^"]*)"`, "g"),
5702
5907
  (match, attr, urlPath) => {
@@ -5709,7 +5914,7 @@ function createVisualEditorMiddleware(options) {
5709
5914
  );
5710
5915
  html = html.replace(
5711
5916
  /data-link="\/(?!\/)([^"]*)"/g,
5712
- (_match, pathPart) => `data-link="/api/conversion-proxy?password=${encodeURIComponent(password)}&url=${encodeURIComponent(
5917
+ (_match, pathPart) => `data-link="${proxyRootForRequest}?password=${encodeURIComponent(password)}&url=${encodeURIComponent(
5713
5918
  `${origin}/${pathPart}`
5714
5919
  )}"`
5715
5920
  );
@@ -5733,6 +5938,7 @@ ${iframeAlwaysShowCssGuardScript}
5733
5938
  var TARGET_ORIGIN=${JSON.stringify(origin)};
5734
5939
  var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
5735
5940
  var PROXY_PASSWORD=${JSON.stringify(password)};
5941
+ var PROXY_BASE_URL="";
5736
5942
  var STRICT_OBSERVER_FREEZE=${JSON.stringify(strictObserverFreezeForRequest)};
5737
5943
  var PARENT_URL_CHANNEL="vvveb-proxy-url";
5738
5944
  window.__CONVERSION_EDITOR_ACTIVE__=true;
@@ -5783,7 +5989,7 @@ function shouldBlockEditorTracking(rawUrl){
5783
5989
  var looksLikeHeartbeatPayload=hasViewportPing&&hasSessionIds;
5784
5990
  var isCrossOriginCollector=host!==TARGET_HOSTNAME&&hasMarker(host,TRACKING_HOST_MARKERS)&&hasMarker(path,TRACKING_PATH_MARKERS);
5785
5991
  var isSnowplowStylePath=path==="/i"||path.indexOf("/com.snowplowanalytics.snowplow/")!==-1||path.indexOf("/tp2")!==-1;
5786
- var isTaboolaUnip=host==="trc-events.taboola.com"&&//log/d+/unip(?:/|$)/.test(path);
5992
+ var isTaboolaUnip=host==="trc-events.taboola.com"&&/\\/log\\/\\d+\\/unip(?:\\/|$)/.test(path);
5787
5993
  var isCollectApiPath=path==="/api/collect"||path.indexOf("/api/collect/")===0;
5788
5994
  var isNbCollectorPath=path==="/nb-collector"||path.indexOf("/nb-collector/")===0;
5789
5995
  return isCrossOriginCollector||(looksLikeHeartbeatPayload&&isSnowplowStylePath)||isCollectApiPath||isTaboolaUnip||isNbCollectorPath;
@@ -5853,7 +6059,7 @@ try{window.addEventListener("popstate",notifyEditorUrlChanged,true);}catch(_){}
5853
6059
  try{window.addEventListener("hashchange",notifyEditorUrlChanged,true);}catch(_){}
5854
6060
  function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
5855
6061
  function toAbsolute(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")||raw.startsWith("//")?TARGET_ORIGIN:TARGET_PAGE_URL;return new URL(raw,base).toString();}catch(_){return raw;}}
5856
- function toProxy(raw){if(isSkippable(raw))return null;var abs=toAbsolute(raw);if(!abs||typeof abs!=="string")return null;try{var parsed=new URL(abs);if(parsed.origin!==TARGET_ORIGIN)return null;return "/api/conversion-proxy?password="+encodeURIComponent(PROXY_PASSWORD||"")+"&url="+encodeURIComponent(parsed.toString());}catch(_){return null;}}
6062
+ function toProxy(raw){if(isSkippable(raw))return null;var abs=toAbsolute(raw);if(!abs||typeof abs!=="string")return null;try{var parsed=new URL(abs);if(parsed.origin!==TARGET_ORIGIN)return null;var root="/api/conversion-proxy";return root+"?password="+encodeURIComponent(PROXY_PASSWORD||"")+"&url="+encodeURIComponent(parsed.toString());}catch(_){return null;}}
5857
6063
  var nativeAssign=window.location.assign?window.location.assign.bind(window.location):null;
5858
6064
  var nativeReplace=window.location.replace?window.location.replace.bind(window.location):null;
5859
6065
  function safeNavigate(raw,mode){var abs=toAbsolute(raw);var prox=toProxy(raw);if(!prox){try{console.warn("[conversion-proxy] redirect blocked",{mode:mode,requested:raw,resolved:abs,origin:TARGET_ORIGIN});}catch(_){}return false;}try{console.info("[conversion-proxy] redirect intercepted",{mode:mode,requested:raw,resolved:abs,proxied:prox});if(mode==="replace"&&nativeReplace){nativeReplace(prox);return true;}if(nativeAssign){nativeAssign(prox);return true;}window.location.href=prox;return true;}catch(err){try{console.warn("[conversion-proxy] redirect interception failed",{mode:mode,requested:raw,resolved:abs,proxied:prox,error:err&&err.message?err.message:String(err)});}catch(_){}return false;}}
@@ -5922,8 +6128,22 @@ if(window.fetch){
5922
6128
  try{
5923
6129
  var u=afterUrl?resolveUrl(afterUrl):null;
5924
6130
  var sameOrigin=!!(u&&u.origin===TARGET_ORIGIN);
5925
- var likelyThirdPartyBg=!sameOrigin||!!(u&&/(^|\\/)apps?(\\/|$)|(^|\\/)a(\\/|$)/.test(u.pathname||""));
5926
- if(window.__CONVERSION_EDITOR_ACTIVE__&&likelyThirdPartyBg){
6131
+ var reqMethod=(init&&init.method?String(init.method):(input&&input.method?String(input.method):"GET")).toUpperCase();
6132
+ var isSafeMethod=reqMethod==="GET"||reqMethod==="HEAD";
6133
+ var path=(u&&u.pathname?u.pathname:"").toLowerCase();
6134
+ var qs=(u&&u.search?u.search:"").toLowerCase();
6135
+ var likelyBackgroundEndpoint=!!(u&&(
6136
+ /(^|\\/)apps?(\\/|$)|(^|\\/)a(\\/|$)/.test(path)||
6137
+ path==="/cart"||
6138
+ path==="/cart.js"||
6139
+ path.indexOf("/cart/")===0||
6140
+ path.indexOf("/recommendations/")===0||
6141
+ path.indexOf("/search/suggest")===0||
6142
+ qs.indexOf("sections=")!==-1||
6143
+ qs.indexOf("section_id=")!==-1
6144
+ ));
6145
+ var likelyThirdPartyBg=!sameOrigin||likelyBackgroundEndpoint;
6146
+ if(window.__CONVERSION_EDITOR_ACTIVE__&&isSafeMethod&&likelyThirdPartyBg){
5927
6147
  console.warn("[conversion-proxy] suppressed fetch failure",{url:afterUrl,error:err&&err.message?err.message:String(err)});
5928
6148
  return new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}});
5929
6149
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@accelerated-agency/visual-editor",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "private": false,
5
5
  "description": "Conversion visual editor as a reusable React package",
6
6
  "type": "module",
@@ -26,7 +26,8 @@
26
26
  "clean": "rm -rf dist",
27
27
  "build": "tsup",
28
28
  "local:build": "tsup && yarn --cwd ../codebase-server clean && yarn --cwd ../codebase-server dev",
29
- "watch": "tsup --watch"
29
+ "watch": "tsup --watch",
30
+ "deploy:worker": "wrangler deploy"
30
31
  },
31
32
  "peerDependencies": {
32
33
  "react": ">=18",