@accelerated-agency/visual-editor 0.4.3 → 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.
Files changed (3) hide show
  1. package/dist/vite.cjs +227 -47
  2. package/dist/vite.js +227 -47
  3. package/package.json +1 -1
package/dist/vite.cjs CHANGED
@@ -86,17 +86,26 @@ function hasTrackingMarker(input, markers) {
86
86
  }
87
87
  return false;
88
88
  }
89
+ function patchKnownUnsafeEditorPatterns(scriptTag) {
90
+ let out = String(scriptTag || "");
91
+ out = out.replace(
92
+ /dragRegion\.addEventListener\((['"])pointerdown\1,\s*onPointerDown\);?/g,
93
+ "if (typeof dragRegion !== 'undefined' && dragRegion) dragRegion.addEventListener($1pointerdown$1, onPointerDown);"
94
+ );
95
+ return out;
96
+ }
89
97
  function stripTrackingScriptsFromScrapedHtml(html, markers) {
90
98
  let removedCount = 0;
91
99
  let out = html;
92
100
  out = out.replace(/<script\b[\s\S]*?<\/script>/gi, (tag) => {
101
+ var patchedTag = patchKnownUnsafeEditorPatterns(tag);
93
102
  const srcMatch = tag.match(/\bsrc\s*=\s*(["'])(.*?)\1/i);
94
103
  const src = srcMatch?.[2] || "";
95
104
  if (hasTrackingMarker(src, markers) || hasTrackingMarker(tag, markers)) {
96
105
  removedCount += 1;
97
106
  return "";
98
107
  }
99
- return tag;
108
+ return patchedTag;
100
109
  });
101
110
  out = out.replace(/<noscript\b[\s\S]*?<\/noscript>/gi, (tag) => {
102
111
  if (!hasTrackingMarker(tag, markers)) return tag;
@@ -860,7 +869,7 @@ select.pr-inp{cursor:pointer;background:#fff}
860
869
  <button class="tb-dk-btn" title="More options"><i class="bi bi-three-dots"></i></button>
861
870
  </div>
862
871
  <button class="tb-dk-btn" id="btn-undo" title="Undo (\u2318Z)"><i class="bi bi-arrow-counterclockwise"></i></button>
863
- <button class="tb-dk-btn" id="btn-redo" title="Redo (\u2318\u21E7Z)"><i class="bi bi-arrow-clockwise"></i></button>
872
+ <button class="tb-dk-btn" id="btn-redo" title="Redo (\u2318\u21E7Z)" style="display:none"><i class="bi bi-arrow-clockwise"></i></button>
864
873
  </div>
865
874
 
866
875
  <div id="iframe-loading-toolbar" class="tb-page-loading" aria-live="polite" aria-atomic="true">
@@ -931,8 +940,8 @@ select.pr-inp{cursor:pointer;background:#fff}
931
940
  <!-- Elements -->
932
941
  <div class="lp-sec lp-sec-no-border">
933
942
  <div class="lp-sec-hd">
934
- <span class="lp-sec-hd-left">Elements <i class="bi bi-info-circle lp-info-icon" title="Page elements"></i></span>
935
- <button class="lp-add-btn" title="Add element">+ Add</button>
943
+ <span class="lp-sec-hd-left">Elements <i style="display:none" class="bi bi-info-circle lp-info-icon" title="Page elements"></i></span>
944
+ <button class="lp-add-btn" id="btn-add-element" title="Add element">+ Add</button>
936
945
  </div>
937
946
 
938
947
  <!-- Search (hidden, kept for JS) -->
@@ -1002,14 +1011,16 @@ select.pr-inp{cursor:pointer;background:#fff}
1002
1011
 
1003
1012
  <!-- Right panel -->
1004
1013
  <div id="right-panel">
1005
- <!-- Left-tab controls moved here -->
1006
- <div class="section-components-tabs">
1007
- <div class="lp-tab" onclick="switchSectionComponentsTab('components')">Components</div>
1008
- <div class="lp-tab" onclick="switchSectionComponentsTab('sections')">Sections</div>
1009
- </div>
1010
- <div class="lp-body">
1011
- <div id="tab-components" class="tab-pane"></div>
1012
- <div id="tab-sections" class="tab-pane"></div>
1014
+ <div id="section-components-panel" style="display:none">
1015
+ <!-- Left-tab controls moved here -->
1016
+ <div class="section-components-tabs">
1017
+ <div class="lp-tab" onclick="switchSectionComponentsTab('components')">Components</div>
1018
+ <div class="lp-tab" onclick="switchSectionComponentsTab('sections')">Sections</div>
1019
+ </div>
1020
+ <div class="lp-body">
1021
+ <div id="tab-components" class="tab-pane"></div>
1022
+ <div id="tab-sections" class="tab-pane"></div>
1023
+ </div>
1013
1024
  </div>
1014
1025
  <!-- Element badge (hidden until selection) -->
1015
1026
  <div id="el-info" style="display:none">
@@ -1666,6 +1677,19 @@ function switchSectionComponentsTab(tab) {
1666
1677
  renderSidebar(inp ? inp.value : '');
1667
1678
  }
1668
1679
 
1680
+ function toggleSectionComponentsPanel(forceVisible) {
1681
+ var panel = document.getElementById('section-components-panel');
1682
+ if (!panel) return;
1683
+ var shouldShow =
1684
+ typeof forceVisible === 'boolean'
1685
+ ? forceVisible
1686
+ : panel.style.display === 'none';
1687
+ panel.style.display = shouldShow ? '' : 'none';
1688
+ if (shouldShow) {
1689
+ switchSectionComponentsTab(currentSectionComponentsTab || 'components');
1690
+ }
1691
+ }
1692
+
1669
1693
  // \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
1670
1694
  function toggleAcc(name) {
1671
1695
  var sec = document.getElementById('acc-' + name);
@@ -1875,6 +1899,24 @@ function syncDesignInput(change) {
1875
1899
  function removeStateChange(idx) {
1876
1900
  var change = stateChanges[idx];
1877
1901
  if (!change) return;
1902
+ if (change.isStructuralLive) {
1903
+ removeSessionStructuralRowByTimestamp(
1904
+ change.structuralVarId || activeVarId,
1905
+ change.vveTs,
1906
+ );
1907
+ stateChanges.splice(idx, 1);
1908
+ commitStateChangesForActiveVariation();
1909
+ renderStatesTab();
1910
+ if (currentMainTab === 'history') renderHistoryTab();
1911
+ try {
1912
+ delete varHtmlCache[activeVarId];
1913
+ } catch(_) {}
1914
+ appliedStructuralChangesetKeys = {};
1915
+ recomputeEditorDirty();
1916
+ scheduleDomTreeRefresh();
1917
+ softReloadEditorIframe();
1918
+ return;
1919
+ }
1878
1920
  revertChangeOnDom(change);
1879
1921
  syncDesignInput(change);
1880
1922
  stateChanges.splice(idx, 1);
@@ -2231,6 +2273,14 @@ function getLatestHistoryUndoTarget() {
2231
2273
  return list.length ? list[0] : null;
2232
2274
  }
2233
2275
 
2276
+ function getLatestLiveUndoTarget() {
2277
+ var list = getUnifiedHistoryItems();
2278
+ for (var i = 0; i < list.length; i++) {
2279
+ if (list[i] && list[i].source === 'live') return list[i];
2280
+ }
2281
+ return null;
2282
+ }
2283
+
2234
2284
  function changesetListHasStructural(arr) {
2235
2285
  if (!arr || !arr.length) return false;
2236
2286
  for (var i = 0; i < arr.length; i++) {
@@ -2329,8 +2379,15 @@ function removeHistoryChangeset(idx, evt) {
2329
2379
  function clearAllHistoryChangesets() {
2330
2380
  var v = getActiveVariationForHistory();
2331
2381
  if (!v) return;
2332
- if (!parseVariationChangesets(v).length) return;
2382
+ var hasSavedChangesets = parseVariationChangesets(v).length > 0;
2383
+ var hasSessionStructural =
2384
+ !!(activeVarId && sessionStructuralChainRowsByVarId[activeVarId] && sessionStructuralChainRowsByVarId[activeVarId].length);
2385
+ if (!hasSavedChangesets && !hasSessionStructural) return;
2386
+
2333
2387
  persistActiveVariationChangesets([]);
2388
+ if (activeVarId) {
2389
+ sessionStructuralChainRowsByVarId[activeVarId] = [];
2390
+ }
2334
2391
  appliedChangesetSnapshots = {};
2335
2392
  appliedStructuralChangesetKeys = {};
2336
2393
  try {
@@ -2343,6 +2400,10 @@ function clearAllHistoryChangesets() {
2343
2400
  }
2344
2401
 
2345
2402
  function clearAllUnifiedHistory() {
2403
+ if (activeVarId) {
2404
+ // Ensure structural unsaved rows are also removed by "Clear all changes".
2405
+ sessionStructuralChainRowsByVarId[activeVarId] = [];
2406
+ }
2346
2407
  clearAllStates();
2347
2408
  clearAllHistoryChangesets();
2348
2409
  if (currentMainTab === 'history') renderHistoryTab();
@@ -2354,15 +2415,32 @@ var VVE_LOCAL_STORAGE_PREFIX = 'vve:';
2354
2415
 
2355
2416
  function clearVisualEditorLocalStorage() {
2356
2417
  try {
2357
- for (var i = localStorage.length - 1; i >= 0; i--) {
2358
- var k = localStorage.key(i);
2359
- if (k && k.indexOf(VVE_LOCAL_STORAGE_PREFIX) === 0) {
2360
- localStorage.removeItem(k);
2418
+ var stores = [];
2419
+ try { stores.push(localStorage); } catch(_) {}
2420
+ try { stores.push(sessionStorage); } catch(_) {}
2421
+ for (var si = 0; si < stores.length; si++) {
2422
+ var store = stores[si];
2423
+ if (!store) continue;
2424
+ for (var i = store.length - 1; i >= 0; i--) {
2425
+ var k = store.key(i);
2426
+ if (k && k.indexOf(VVE_LOCAL_STORAGE_PREFIX) === 0) {
2427
+ store.removeItem(k);
2428
+ }
2361
2429
  }
2362
2430
  }
2363
2431
  } catch(_) {}
2364
2432
  }
2365
2433
 
2434
+ function clearPersistedActiveVariationForData(data) {
2435
+ try {
2436
+ var sk = activeVariationStorageKeyFromPayload(data);
2437
+ if (sk && sk !== VVE_LOCAL_STORAGE_PREFIX + 'activeVar::') {
2438
+ try { localStorage.removeItem(sk); } catch(_) {}
2439
+ try { sessionStorage.removeItem(sk); } catch(_) {}
2440
+ }
2441
+ } catch(_) {}
2442
+ }
2443
+
2366
2444
  function activeVariationStorageKeyFromPayload(data) {
2367
2445
  return (
2368
2446
  VVE_LOCAL_STORAGE_PREFIX +
@@ -2401,14 +2479,18 @@ function writePersistedActiveVariationId(varId) {
2401
2479
  * @param allowPrevMemory when true, keep in-session activeVarId if still valid (skip-reload path).
2402
2480
  */
2403
2481
  function pickActiveVariationIdForLoad(data, variationsArr, prevMemoryId, allowPrevMemory) {
2404
- var baseline = variationsArr.find(function(v) { return v.baseline; });
2405
- var fallback = (baseline || variationsArr[0] || {})._id || null;
2482
+ var firstNonBaseline = variationsArr.find(function(v) { return !v.baseline; });
2483
+ var fallback = (firstNonBaseline || variationsArr[0] || {})._id || null;
2406
2484
  if (!variationsArr.length) return null;
2407
2485
  if (allowPrevMemory && prevMemoryId && variationsArr.some(function(v) { return v._id === prevMemoryId; })) {
2408
2486
  return prevMemoryId;
2409
2487
  }
2410
2488
  var stored = readPersistedActiveVariationId(data);
2411
2489
  if (stored && variationsArr.some(function(v) { return v._id === stored; })) {
2490
+ var storedVar = variationsArr.find(function(v) { return v._id === stored; });
2491
+ if (storedVar && storedVar.baseline && firstNonBaseline && firstNonBaseline._id) {
2492
+ return firstNonBaseline._id;
2493
+ }
2412
2494
  return stored;
2413
2495
  }
2414
2496
  return fallback;
@@ -2632,7 +2714,13 @@ function granularAnySelectorMatches(doc, cs) {
2632
2714
 
2633
2715
  /** Bust bfcache / same-URL no-op reloads so the iframe actually re-parses (loading \u2192 interactive). */
2634
2716
  function appendIframeReloadBust(url) {
2635
- return url;
2717
+ try {
2718
+ var u = new URL(String(url || ''), window.location.href);
2719
+ u.searchParams.set('_vve_reload', String(Date.now()));
2720
+ return u.toString();
2721
+ } catch(_) {
2722
+ return url;
2723
+ }
2636
2724
  }
2637
2725
 
2638
2726
  // True when the iframe contentDocument belongs to the current iframe.src navigation.
@@ -3010,6 +3098,44 @@ function appendSessionStructuralChainRow(varId, row) {
3010
3098
  sessionStructuralChainRowsByVarId[varId].push(row);
3011
3099
  }
3012
3100
 
3101
+ function markStructuralRowApplied(row) {
3102
+ try {
3103
+ var k = structuralChangesetDedupKey(row);
3104
+ if (k) appliedStructuralChangesetKeys[k] = true;
3105
+ } catch(_) {}
3106
+ }
3107
+
3108
+ function logStructuralStateChange(row, label, value, targetEl) {
3109
+ if (!row || !row.selector) return;
3110
+ stateChanges.push({
3111
+ selector: row.selector,
3112
+ inputId: 'vve-struct',
3113
+ label: label || 'Structure change',
3114
+ cssProp: null,
3115
+ value: value != null ? String(value) : String(row.type || ''),
3116
+ targetEl: targetEl || null,
3117
+ originalValue: '',
3118
+ vveTs: row.vveTs || nextHistoryTimestamp(),
3119
+ isStructuralLive: true,
3120
+ structuralVarId: activeVarId || null,
3121
+ structuralType: row.type || '',
3122
+ });
3123
+ if (currentMainTab === 'history') renderHistoryTab();
3124
+ commitStateChangesForActiveVariation();
3125
+ }
3126
+
3127
+ function removeSessionStructuralRowByTimestamp(varId, ts) {
3128
+ if (!varId || !ts) return;
3129
+ var arr = sessionStructuralChainRowsByVarId[varId];
3130
+ if (!arr || !arr.length) return;
3131
+ for (var i = arr.length - 1; i >= 0; i--) {
3132
+ if (arr[i] && arr[i].vveTs === ts) {
3133
+ arr.splice(i, 1);
3134
+ return;
3135
+ }
3136
+ }
3137
+ }
3138
+
3013
3139
  /** One States-tab row -> Conversion.io chain-set shape (matches applyChangesetEntry). */
3014
3140
  function stateChangeToChainSet(c) {
3015
3141
  if (!c || !c.selector) return null;
@@ -3704,12 +3830,15 @@ function duplicateSelectedEl() {
3704
3830
  clone.setAttribute('data-vve-instance', generateVveInstanceId());
3705
3831
  } catch(_) {}
3706
3832
  if (activeVarId) {
3707
- appendSessionStructuralChainRow(activeVarId, {
3833
+ var dupRow = {
3708
3834
  selector: anchorSel,
3709
3835
  type: 'insert',
3710
3836
  action: 'after',
3711
3837
  html: clone.outerHTML,
3712
- });
3838
+ };
3839
+ appendSessionStructuralChainRow(activeVarId, dupRow);
3840
+ markStructuralRowApplied(dupRow);
3841
+ logStructuralStateChange(dupRow, 'Duplicated via toolbar', 'Duplicate inserted', selectedEl);
3713
3842
  }
3714
3843
  selectedEl.parentNode.insertBefore(clone, selectedEl.nextSibling);
3715
3844
  saveCurrentVariationHtml();
@@ -3725,23 +3854,27 @@ function toggleHideSelectedEl() {
3725
3854
  selectedEl.style.visibility = '';
3726
3855
  selectedEl.removeAttribute('data-vve-hidden');
3727
3856
  if (activeVarId) {
3728
- appendSessionStructuralChainRow(activeVarId, {
3857
+ var showRow = {
3729
3858
  selector: hidSel,
3730
3859
  type: 'style',
3731
3860
  property: 'visibility',
3732
3861
  value: '',
3733
- });
3862
+ };
3863
+ appendSessionStructuralChainRow(activeVarId, showRow);
3864
+ logStructuralStateChange(showRow, 'Shown via toolbar', 'Visibility restored', selectedEl);
3734
3865
  }
3735
3866
  } else {
3736
3867
  selectedEl.style.visibility = 'hidden';
3737
3868
  selectedEl.setAttribute('data-vve-hidden', '1');
3738
3869
  if (activeVarId) {
3739
- appendSessionStructuralChainRow(activeVarId, {
3870
+ var hideRow = {
3740
3871
  selector: hidSel,
3741
3872
  type: 'style',
3742
3873
  property: 'visibility',
3743
3874
  value: 'hidden',
3744
- });
3875
+ };
3876
+ appendSessionStructuralChainRow(activeVarId, hideRow);
3877
+ logStructuralStateChange(hideRow, 'Hidden via toolbar', 'Visibility set to hidden', selectedEl);
3745
3878
  }
3746
3879
  }
3747
3880
  saveCurrentVariationHtml();
@@ -3752,7 +3885,11 @@ function deleteSelectedEl() {
3752
3885
  if (!selectedEl || !selectedEl.parentNode) return;
3753
3886
  var delSel = buildSelector(selectedEl);
3754
3887
  selectedEl.remove();
3755
- if (activeVarId) appendSessionStructuralChainRow(activeVarId, { selector: delSel, type: 'remove' });
3888
+ if (activeVarId) {
3889
+ var delRow = { selector: delSel, type: 'remove' };
3890
+ appendSessionStructuralChainRow(activeVarId, delRow);
3891
+ logStructuralStateChange(delRow, 'Deleted via toolbar', 'Element removed', null);
3892
+ }
3756
3893
  saveCurrentVariationHtml();
3757
3894
  recomputeEditorDirty();
3758
3895
  deselectElement();
@@ -4653,19 +4790,25 @@ function recordReorderAfterDrag(movedEl) {
4653
4790
  var prev = movedEl.previousElementSibling;
4654
4791
  var next = movedEl.nextElementSibling;
4655
4792
  if (prev) {
4656
- appendSessionStructuralChainRow(activeVarId, {
4793
+ var reorderAfterRow = {
4657
4794
  selector: buildSelector(movedEl),
4658
4795
  type: 'reorder',
4659
4796
  targetSelector: buildSelector(prev),
4660
4797
  action: 'after',
4661
- });
4798
+ };
4799
+ appendSessionStructuralChainRow(activeVarId, reorderAfterRow);
4800
+ markStructuralRowApplied(reorderAfterRow);
4801
+ logStructuralStateChange(reorderAfterRow, 'Reordered via drag', 'Moved after sibling', movedEl);
4662
4802
  } else if (next) {
4663
- appendSessionStructuralChainRow(activeVarId, {
4803
+ var reorderBeforeRow = {
4664
4804
  selector: buildSelector(movedEl),
4665
4805
  type: 'reorder',
4666
4806
  targetSelector: buildSelector(next),
4667
4807
  action: 'before',
4668
- });
4808
+ };
4809
+ appendSessionStructuralChainRow(activeVarId, reorderBeforeRow);
4810
+ markStructuralRowApplied(reorderBeforeRow);
4811
+ logStructuralStateChange(reorderBeforeRow, 'Reordered via drag', 'Moved before sibling', movedEl);
4669
4812
  }
4670
4813
  }
4671
4814
 
@@ -4946,12 +5089,15 @@ function insertHtml(html) {
4946
5089
  }
4947
5090
  if (firstEl) selectElement(firstEl);
4948
5091
  if (activeVarId) {
4949
- appendSessionStructuralChainRow(activeVarId, {
5092
+ var insertRow = {
4950
5093
  selector: anchorSel,
4951
5094
  type: 'insert',
4952
5095
  action: 'after',
4953
5096
  html: htmlStr,
4954
- });
5097
+ };
5098
+ appendSessionStructuralChainRow(activeVarId, insertRow);
5099
+ markStructuralRowApplied(insertRow);
5100
+ logStructuralStateChange(insertRow, 'Added component/section', 'Inserted new element', firstEl || selectedEl);
4955
5101
  }
4956
5102
  saveCurrentVariationHtml();
4957
5103
  recomputeEditorDirty();
@@ -5041,6 +5187,14 @@ document.getElementById('comp-search').addEventListener('input', function() {
5041
5187
  renderSidebar(this.value);
5042
5188
  }
5043
5189
  });
5190
+ var btnAddElement = document.getElementById('btn-add-element');
5191
+ if (btnAddElement) {
5192
+ btnAddElement.addEventListener('click', function(e) {
5193
+ e.preventDefault();
5194
+ e.stopPropagation();
5195
+ toggleSectionComponentsPanel();
5196
+ });
5197
+ }
5044
5198
 
5045
5199
  // \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
5046
5200
  document.getElementById('btn-save').addEventListener('click', handleSave);
@@ -5081,11 +5235,21 @@ function handleSave() {
5081
5235
  }
5082
5236
 
5083
5237
  function handleClose() {
5238
+ clearPersistedActiveVariationForData(experimentData);
5084
5239
  clearVisualEditorLocalStorage();
5085
5240
  // Unsaved-changes UX lives in the parent (PlatformVisualEditorV2); avoid double confirm here.
5086
5241
  send('close-editor', {});
5087
5242
  }
5088
5243
 
5244
+ // Defensive cleanup: if parent closes/unmounts the editor shell without
5245
+ // invoking handleClose(), clear persisted VVE keys on unload as well.
5246
+ window.addEventListener('pagehide', function() {
5247
+ clearVisualEditorLocalStorage();
5248
+ });
5249
+ window.addEventListener('beforeunload', function() {
5250
+ clearVisualEditorLocalStorage();
5251
+ });
5252
+
5089
5253
  // \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
5090
5254
  function isNativeEditableTarget(target) {
5091
5255
  if (!target || target.nodeType !== 1) return false;
@@ -5107,10 +5271,7 @@ document.addEventListener('keydown', function(e) {
5107
5271
  e.preventDefault();
5108
5272
  runEditorUndo();
5109
5273
  }
5110
- if (meta && e.shiftKey && k === 'z') {
5111
- e.preventDefault();
5112
- runEditorRedo();
5113
- }
5274
+ // Redo is intentionally hidden/disabled in this editor flow.
5114
5275
  if (meta && e.key === 's') { e.preventDefault(); handleSave(); }
5115
5276
  if (e.key === 'Escape') {
5116
5277
  var openTips = document.querySelectorAll('.ve-pl-tip.is-tip-open');
@@ -5130,11 +5291,17 @@ document.addEventListener('keydown', function(e) {
5130
5291
  }
5131
5292
  });
5132
5293
  function runEditorUndo() {
5133
- var target = getLatestHistoryUndoTarget();
5134
- if (target) {
5135
- removeHistoryItem(target.source, target.idx);
5294
+ // Undo only unsaved in-session edits; do not remove persisted history rows.
5295
+ if (!isDirty) return;
5296
+
5297
+ // 1) Prefer live unsaved change stack (stateChanges shown in History tab).
5298
+ var liveTarget = getLatestLiveUndoTarget();
5299
+ if (liveTarget) {
5300
+ removeHistoryItem('live', liveTarget.idx);
5136
5301
  return;
5137
5302
  }
5303
+
5304
+ // 2) Fallback to Vvveb internal undo stack (e.g. structural drag/drop ops).
5138
5305
  if (!(typeof Vvveb !== 'undefined' && Vvveb.Undo)) return;
5139
5306
  Vvveb.Undo.undo();
5140
5307
  saveCurrentVariationHtml();
@@ -5157,11 +5324,10 @@ document.getElementById('btn-undo').addEventListener('click', function(e) {
5157
5324
  e.stopPropagation();
5158
5325
  runEditorUndo();
5159
5326
  });
5160
- document.getElementById('btn-redo').addEventListener('click', function(e) {
5161
- e.preventDefault();
5162
- e.stopPropagation();
5163
- runEditorRedo();
5164
- });
5327
+ var btnRedo = document.getElementById('btn-redo');
5328
+ if (btnRedo) {
5329
+ btnRedo.style.display = 'none';
5330
+ }
5165
5331
 
5166
5332
  function layoutLoadingTooltip(host) {
5167
5333
  var tip = host.querySelector('.ve-pl-tooltip');
@@ -5970,8 +6136,22 @@ if(window.fetch){
5970
6136
  try{
5971
6137
  var u=afterUrl?resolveUrl(afterUrl):null;
5972
6138
  var sameOrigin=!!(u&&u.origin===TARGET_ORIGIN);
5973
- var likelyThirdPartyBg=!sameOrigin||!!(u&&/(^|\\/)apps?(\\/|$)|(^|\\/)a(\\/|$)/.test(u.pathname||""));
5974
- if(window.__CONVERSION_EDITOR_ACTIVE__&&likelyThirdPartyBg){
6139
+ var reqMethod=(init&&init.method?String(init.method):(input&&input.method?String(input.method):"GET")).toUpperCase();
6140
+ var isSafeMethod=reqMethod==="GET"||reqMethod==="HEAD";
6141
+ var path=(u&&u.pathname?u.pathname:"").toLowerCase();
6142
+ var qs=(u&&u.search?u.search:"").toLowerCase();
6143
+ var likelyBackgroundEndpoint=!!(u&&(
6144
+ /(^|\\/)apps?(\\/|$)|(^|\\/)a(\\/|$)/.test(path)||
6145
+ path==="/cart"||
6146
+ path==="/cart.js"||
6147
+ path.indexOf("/cart/")===0||
6148
+ path.indexOf("/recommendations/")===0||
6149
+ path.indexOf("/search/suggest")===0||
6150
+ qs.indexOf("sections=")!==-1||
6151
+ qs.indexOf("section_id=")!==-1
6152
+ ));
6153
+ var likelyThirdPartyBg=!sameOrigin||likelyBackgroundEndpoint;
6154
+ if(window.__CONVERSION_EDITOR_ACTIVE__&&isSafeMethod&&likelyThirdPartyBg){
5975
6155
  console.warn("[conversion-proxy] suppressed fetch failure",{url:afterUrl,error:err&&err.message?err.message:String(err)});
5976
6156
  return new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}});
5977
6157
  }
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">
@@ -1658,6 +1669,19 @@ function switchSectionComponentsTab(tab) {
1658
1669
  renderSidebar(inp ? inp.value : '');
1659
1670
  }
1660
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
+
1661
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
1662
1686
  function toggleAcc(name) {
1663
1687
  var sec = document.getElementById('acc-' + name);
@@ -1867,6 +1891,24 @@ function syncDesignInput(change) {
1867
1891
  function removeStateChange(idx) {
1868
1892
  var change = stateChanges[idx];
1869
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
+ }
1870
1912
  revertChangeOnDom(change);
1871
1913
  syncDesignInput(change);
1872
1914
  stateChanges.splice(idx, 1);
@@ -2223,6 +2265,14 @@ function getLatestHistoryUndoTarget() {
2223
2265
  return list.length ? list[0] : null;
2224
2266
  }
2225
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
+
2226
2276
  function changesetListHasStructural(arr) {
2227
2277
  if (!arr || !arr.length) return false;
2228
2278
  for (var i = 0; i < arr.length; i++) {
@@ -2321,8 +2371,15 @@ function removeHistoryChangeset(idx, evt) {
2321
2371
  function clearAllHistoryChangesets() {
2322
2372
  var v = getActiveVariationForHistory();
2323
2373
  if (!v) return;
2324
- 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
+
2325
2379
  persistActiveVariationChangesets([]);
2380
+ if (activeVarId) {
2381
+ sessionStructuralChainRowsByVarId[activeVarId] = [];
2382
+ }
2326
2383
  appliedChangesetSnapshots = {};
2327
2384
  appliedStructuralChangesetKeys = {};
2328
2385
  try {
@@ -2335,6 +2392,10 @@ function clearAllHistoryChangesets() {
2335
2392
  }
2336
2393
 
2337
2394
  function clearAllUnifiedHistory() {
2395
+ if (activeVarId) {
2396
+ // Ensure structural unsaved rows are also removed by "Clear all changes".
2397
+ sessionStructuralChainRowsByVarId[activeVarId] = [];
2398
+ }
2338
2399
  clearAllStates();
2339
2400
  clearAllHistoryChangesets();
2340
2401
  if (currentMainTab === 'history') renderHistoryTab();
@@ -2346,15 +2407,32 @@ var VVE_LOCAL_STORAGE_PREFIX = 'vve:';
2346
2407
 
2347
2408
  function clearVisualEditorLocalStorage() {
2348
2409
  try {
2349
- for (var i = localStorage.length - 1; i >= 0; i--) {
2350
- var k = localStorage.key(i);
2351
- if (k && k.indexOf(VVE_LOCAL_STORAGE_PREFIX) === 0) {
2352
- 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
+ }
2353
2421
  }
2354
2422
  }
2355
2423
  } catch(_) {}
2356
2424
  }
2357
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
+
2358
2436
  function activeVariationStorageKeyFromPayload(data) {
2359
2437
  return (
2360
2438
  VVE_LOCAL_STORAGE_PREFIX +
@@ -2393,14 +2471,18 @@ function writePersistedActiveVariationId(varId) {
2393
2471
  * @param allowPrevMemory when true, keep in-session activeVarId if still valid (skip-reload path).
2394
2472
  */
2395
2473
  function pickActiveVariationIdForLoad(data, variationsArr, prevMemoryId, allowPrevMemory) {
2396
- var baseline = variationsArr.find(function(v) { return v.baseline; });
2397
- 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;
2398
2476
  if (!variationsArr.length) return null;
2399
2477
  if (allowPrevMemory && prevMemoryId && variationsArr.some(function(v) { return v._id === prevMemoryId; })) {
2400
2478
  return prevMemoryId;
2401
2479
  }
2402
2480
  var stored = readPersistedActiveVariationId(data);
2403
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
+ }
2404
2486
  return stored;
2405
2487
  }
2406
2488
  return fallback;
@@ -2624,7 +2706,13 @@ function granularAnySelectorMatches(doc, cs) {
2624
2706
 
2625
2707
  /** Bust bfcache / same-URL no-op reloads so the iframe actually re-parses (loading \u2192 interactive). */
2626
2708
  function appendIframeReloadBust(url) {
2627
- 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
+ }
2628
2716
  }
2629
2717
 
2630
2718
  // True when the iframe contentDocument belongs to the current iframe.src navigation.
@@ -3002,6 +3090,44 @@ function appendSessionStructuralChainRow(varId, row) {
3002
3090
  sessionStructuralChainRowsByVarId[varId].push(row);
3003
3091
  }
3004
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
+
3005
3131
  /** One States-tab row -> Conversion.io chain-set shape (matches applyChangesetEntry). */
3006
3132
  function stateChangeToChainSet(c) {
3007
3133
  if (!c || !c.selector) return null;
@@ -3696,12 +3822,15 @@ function duplicateSelectedEl() {
3696
3822
  clone.setAttribute('data-vve-instance', generateVveInstanceId());
3697
3823
  } catch(_) {}
3698
3824
  if (activeVarId) {
3699
- appendSessionStructuralChainRow(activeVarId, {
3825
+ var dupRow = {
3700
3826
  selector: anchorSel,
3701
3827
  type: 'insert',
3702
3828
  action: 'after',
3703
3829
  html: clone.outerHTML,
3704
- });
3830
+ };
3831
+ appendSessionStructuralChainRow(activeVarId, dupRow);
3832
+ markStructuralRowApplied(dupRow);
3833
+ logStructuralStateChange(dupRow, 'Duplicated via toolbar', 'Duplicate inserted', selectedEl);
3705
3834
  }
3706
3835
  selectedEl.parentNode.insertBefore(clone, selectedEl.nextSibling);
3707
3836
  saveCurrentVariationHtml();
@@ -3717,23 +3846,27 @@ function toggleHideSelectedEl() {
3717
3846
  selectedEl.style.visibility = '';
3718
3847
  selectedEl.removeAttribute('data-vve-hidden');
3719
3848
  if (activeVarId) {
3720
- appendSessionStructuralChainRow(activeVarId, {
3849
+ var showRow = {
3721
3850
  selector: hidSel,
3722
3851
  type: 'style',
3723
3852
  property: 'visibility',
3724
3853
  value: '',
3725
- });
3854
+ };
3855
+ appendSessionStructuralChainRow(activeVarId, showRow);
3856
+ logStructuralStateChange(showRow, 'Shown via toolbar', 'Visibility restored', selectedEl);
3726
3857
  }
3727
3858
  } else {
3728
3859
  selectedEl.style.visibility = 'hidden';
3729
3860
  selectedEl.setAttribute('data-vve-hidden', '1');
3730
3861
  if (activeVarId) {
3731
- appendSessionStructuralChainRow(activeVarId, {
3862
+ var hideRow = {
3732
3863
  selector: hidSel,
3733
3864
  type: 'style',
3734
3865
  property: 'visibility',
3735
3866
  value: 'hidden',
3736
- });
3867
+ };
3868
+ appendSessionStructuralChainRow(activeVarId, hideRow);
3869
+ logStructuralStateChange(hideRow, 'Hidden via toolbar', 'Visibility set to hidden', selectedEl);
3737
3870
  }
3738
3871
  }
3739
3872
  saveCurrentVariationHtml();
@@ -3744,7 +3877,11 @@ function deleteSelectedEl() {
3744
3877
  if (!selectedEl || !selectedEl.parentNode) return;
3745
3878
  var delSel = buildSelector(selectedEl);
3746
3879
  selectedEl.remove();
3747
- 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
+ }
3748
3885
  saveCurrentVariationHtml();
3749
3886
  recomputeEditorDirty();
3750
3887
  deselectElement();
@@ -4645,19 +4782,25 @@ function recordReorderAfterDrag(movedEl) {
4645
4782
  var prev = movedEl.previousElementSibling;
4646
4783
  var next = movedEl.nextElementSibling;
4647
4784
  if (prev) {
4648
- appendSessionStructuralChainRow(activeVarId, {
4785
+ var reorderAfterRow = {
4649
4786
  selector: buildSelector(movedEl),
4650
4787
  type: 'reorder',
4651
4788
  targetSelector: buildSelector(prev),
4652
4789
  action: 'after',
4653
- });
4790
+ };
4791
+ appendSessionStructuralChainRow(activeVarId, reorderAfterRow);
4792
+ markStructuralRowApplied(reorderAfterRow);
4793
+ logStructuralStateChange(reorderAfterRow, 'Reordered via drag', 'Moved after sibling', movedEl);
4654
4794
  } else if (next) {
4655
- appendSessionStructuralChainRow(activeVarId, {
4795
+ var reorderBeforeRow = {
4656
4796
  selector: buildSelector(movedEl),
4657
4797
  type: 'reorder',
4658
4798
  targetSelector: buildSelector(next),
4659
4799
  action: 'before',
4660
- });
4800
+ };
4801
+ appendSessionStructuralChainRow(activeVarId, reorderBeforeRow);
4802
+ markStructuralRowApplied(reorderBeforeRow);
4803
+ logStructuralStateChange(reorderBeforeRow, 'Reordered via drag', 'Moved before sibling', movedEl);
4661
4804
  }
4662
4805
  }
4663
4806
 
@@ -4938,12 +5081,15 @@ function insertHtml(html) {
4938
5081
  }
4939
5082
  if (firstEl) selectElement(firstEl);
4940
5083
  if (activeVarId) {
4941
- appendSessionStructuralChainRow(activeVarId, {
5084
+ var insertRow = {
4942
5085
  selector: anchorSel,
4943
5086
  type: 'insert',
4944
5087
  action: 'after',
4945
5088
  html: htmlStr,
4946
- });
5089
+ };
5090
+ appendSessionStructuralChainRow(activeVarId, insertRow);
5091
+ markStructuralRowApplied(insertRow);
5092
+ logStructuralStateChange(insertRow, 'Added component/section', 'Inserted new element', firstEl || selectedEl);
4947
5093
  }
4948
5094
  saveCurrentVariationHtml();
4949
5095
  recomputeEditorDirty();
@@ -5033,6 +5179,14 @@ document.getElementById('comp-search').addEventListener('input', function() {
5033
5179
  renderSidebar(this.value);
5034
5180
  }
5035
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
+ }
5036
5190
 
5037
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
5038
5192
  document.getElementById('btn-save').addEventListener('click', handleSave);
@@ -5073,11 +5227,21 @@ function handleSave() {
5073
5227
  }
5074
5228
 
5075
5229
  function handleClose() {
5230
+ clearPersistedActiveVariationForData(experimentData);
5076
5231
  clearVisualEditorLocalStorage();
5077
5232
  // Unsaved-changes UX lives in the parent (PlatformVisualEditorV2); avoid double confirm here.
5078
5233
  send('close-editor', {});
5079
5234
  }
5080
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
+
5081
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
5082
5246
  function isNativeEditableTarget(target) {
5083
5247
  if (!target || target.nodeType !== 1) return false;
@@ -5099,10 +5263,7 @@ document.addEventListener('keydown', function(e) {
5099
5263
  e.preventDefault();
5100
5264
  runEditorUndo();
5101
5265
  }
5102
- if (meta && e.shiftKey && k === 'z') {
5103
- e.preventDefault();
5104
- runEditorRedo();
5105
- }
5266
+ // Redo is intentionally hidden/disabled in this editor flow.
5106
5267
  if (meta && e.key === 's') { e.preventDefault(); handleSave(); }
5107
5268
  if (e.key === 'Escape') {
5108
5269
  var openTips = document.querySelectorAll('.ve-pl-tip.is-tip-open');
@@ -5122,11 +5283,17 @@ document.addEventListener('keydown', function(e) {
5122
5283
  }
5123
5284
  });
5124
5285
  function runEditorUndo() {
5125
- var target = getLatestHistoryUndoTarget();
5126
- if (target) {
5127
- 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);
5128
5293
  return;
5129
5294
  }
5295
+
5296
+ // 2) Fallback to Vvveb internal undo stack (e.g. structural drag/drop ops).
5130
5297
  if (!(typeof Vvveb !== 'undefined' && Vvveb.Undo)) return;
5131
5298
  Vvveb.Undo.undo();
5132
5299
  saveCurrentVariationHtml();
@@ -5149,11 +5316,10 @@ document.getElementById('btn-undo').addEventListener('click', function(e) {
5149
5316
  e.stopPropagation();
5150
5317
  runEditorUndo();
5151
5318
  });
5152
- document.getElementById('btn-redo').addEventListener('click', function(e) {
5153
- e.preventDefault();
5154
- e.stopPropagation();
5155
- runEditorRedo();
5156
- });
5319
+ var btnRedo = document.getElementById('btn-redo');
5320
+ if (btnRedo) {
5321
+ btnRedo.style.display = 'none';
5322
+ }
5157
5323
 
5158
5324
  function layoutLoadingTooltip(host) {
5159
5325
  var tip = host.querySelector('.ve-pl-tooltip');
@@ -5962,8 +6128,22 @@ if(window.fetch){
5962
6128
  try{
5963
6129
  var u=afterUrl?resolveUrl(afterUrl):null;
5964
6130
  var sameOrigin=!!(u&&u.origin===TARGET_ORIGIN);
5965
- var likelyThirdPartyBg=!sameOrigin||!!(u&&/(^|\\/)apps?(\\/|$)|(^|\\/)a(\\/|$)/.test(u.pathname||""));
5966
- 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){
5967
6147
  console.warn("[conversion-proxy] suppressed fetch failure",{url:afterUrl,error:err&&err.message?err.message:String(err)});
5968
6148
  return new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}});
5969
6149
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@accelerated-agency/visual-editor",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "private": false,
5
5
  "description": "Conversion visual editor as a reusable React package",
6
6
  "type": "module",