@accelerated-agency/visual-editor 0.2.7 → 0.2.9
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.cjs +264 -23
- package/dist/vite.js +264 -23
- package/package.json +1 -1
package/dist/vite.cjs
CHANGED
|
@@ -995,6 +995,11 @@ var vvvebReady = false;
|
|
|
995
995
|
var currentMode = 'editor';
|
|
996
996
|
var currentDevice = 'desktop';
|
|
997
997
|
var selectedEl = null;
|
|
998
|
+
/** Stable selector fingerprint for resilient selection recovery after DOM churn. */
|
|
999
|
+
var selectedElFingerprint = '';
|
|
1000
|
+
var selectedElRecoverMisses = 0;
|
|
1001
|
+
var MAX_SELECTED_RECOVER_MISSES = 12;
|
|
1002
|
+
var isDeselectingSelection = false;
|
|
998
1003
|
var suppressClickUntil = 0;
|
|
999
1004
|
var dragAttachDoc = null;
|
|
1000
1005
|
var currentMainTab = 'design';
|
|
@@ -1023,6 +1028,8 @@ var iframeDocLoadingListeners = null;
|
|
|
1023
1028
|
// Each entry: {selector, label, cssProp, value, targetEl}
|
|
1024
1029
|
// cssProp is null for non-CSS attributes (href, alt, classes\u2026)
|
|
1025
1030
|
var stateChanges = [];
|
|
1031
|
+
/** Per-variation unsaved Design-tab state changes (input edits not yet finalized). */
|
|
1032
|
+
var stateChangesByVarId = {};
|
|
1026
1033
|
/** Pre-apply DOM snapshots for granular + body changesets (used when removing History rows) */
|
|
1027
1034
|
var appliedChangesetSnapshots = {};
|
|
1028
1035
|
/** Canonical JSON fingerprints of persisted changesets per variation (last load / finalize) */
|
|
@@ -1037,6 +1044,95 @@ function endSuppressIframeMutationDirty() {
|
|
|
1037
1044
|
suppressIframeMutationDirty = Math.max(0, suppressIframeMutationDirty - 1);
|
|
1038
1045
|
}
|
|
1039
1046
|
|
|
1047
|
+
function commitStateChangesForActiveVariation() {
|
|
1048
|
+
if (!activeVarId) return;
|
|
1049
|
+
stateChangesByVarId[activeVarId] = (stateChanges || []).slice();
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function loadStateChangesForActiveVariation() {
|
|
1053
|
+
if (!activeVarId) {
|
|
1054
|
+
stateChanges = [];
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
stateChanges = (stateChangesByVarId[activeVarId] || []).slice();
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
function recoverSelectedElement(forceDeselectOnMiss) {
|
|
1061
|
+
if (selectedEl && selectedEl.ownerDocument && selectedEl.ownerDocument.contains(selectedEl)) {
|
|
1062
|
+
selectedElRecoverMisses = 0;
|
|
1063
|
+
return selectedEl;
|
|
1064
|
+
}
|
|
1065
|
+
if (!selectedElFingerprint) {
|
|
1066
|
+
// Nothing to recover; clear stale reference without calling deselectElement
|
|
1067
|
+
// (which can recurse back through toolbar updates during rapid DOM churn).
|
|
1068
|
+
if (forceDeselectOnMiss && selectedEl) {
|
|
1069
|
+
beginSuppressIframeMutationDirty();
|
|
1070
|
+
try {
|
|
1071
|
+
try { selectedEl.classList.remove('vve-selected'); } catch(_) {}
|
|
1072
|
+
selectedEl = null;
|
|
1073
|
+
selectedElRecoverMisses = 0;
|
|
1074
|
+
} finally {
|
|
1075
|
+
endSuppressIframeMutationDirty();
|
|
1076
|
+
}
|
|
1077
|
+
var noSel = document.getElementById('no-sel');
|
|
1078
|
+
if (noSel) noSel.style.display = '';
|
|
1079
|
+
var elInfo = document.getElementById('el-info');
|
|
1080
|
+
if (elInfo) elInfo.style.display = 'none';
|
|
1081
|
+
var rp = document.getElementById('rp-accordion');
|
|
1082
|
+
if (rp) rp.style.display = 'none';
|
|
1083
|
+
var bc = document.getElementById('bc-path');
|
|
1084
|
+
if (bc) {
|
|
1085
|
+
bc.textContent = 'No element selected';
|
|
1086
|
+
bc.style.color = 'var(--text-3)';
|
|
1087
|
+
}
|
|
1088
|
+
syncDomTreeSelection();
|
|
1089
|
+
}
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
var iframe = document.getElementById('iframeId');
|
|
1093
|
+
var doc = iframe && iframe.contentDocument;
|
|
1094
|
+
if (!doc) return null;
|
|
1095
|
+
var recovered = querySelectorResolved(doc, selectedElFingerprint);
|
|
1096
|
+
if (recovered) {
|
|
1097
|
+
beginSuppressIframeMutationDirty();
|
|
1098
|
+
try {
|
|
1099
|
+
selectedEl = recovered;
|
|
1100
|
+
if (selectedEl.classList) selectedEl.classList.add('vve-selected');
|
|
1101
|
+
} finally {
|
|
1102
|
+
endSuppressIframeMutationDirty();
|
|
1103
|
+
}
|
|
1104
|
+
selectedElRecoverMisses = 0;
|
|
1105
|
+
return recovered;
|
|
1106
|
+
}
|
|
1107
|
+
selectedElRecoverMisses += 1;
|
|
1108
|
+
if (forceDeselectOnMiss && selectedElRecoverMisses >= MAX_SELECTED_RECOVER_MISSES) {
|
|
1109
|
+
beginSuppressIframeMutationDirty();
|
|
1110
|
+
try {
|
|
1111
|
+
if (selectedEl) {
|
|
1112
|
+
try { selectedEl.classList.remove('vve-selected'); } catch(_) {}
|
|
1113
|
+
}
|
|
1114
|
+
selectedEl = null;
|
|
1115
|
+
selectedElFingerprint = '';
|
|
1116
|
+
selectedElRecoverMisses = 0;
|
|
1117
|
+
} finally {
|
|
1118
|
+
endSuppressIframeMutationDirty();
|
|
1119
|
+
}
|
|
1120
|
+
var noSel2 = document.getElementById('no-sel');
|
|
1121
|
+
if (noSel2) noSel2.style.display = '';
|
|
1122
|
+
var elInfo2 = document.getElementById('el-info');
|
|
1123
|
+
if (elInfo2) elInfo2.style.display = 'none';
|
|
1124
|
+
var rp2 = document.getElementById('rp-accordion');
|
|
1125
|
+
if (rp2) rp2.style.display = 'none';
|
|
1126
|
+
var bc2 = document.getElementById('bc-path');
|
|
1127
|
+
if (bc2) {
|
|
1128
|
+
bc2.textContent = 'No element selected';
|
|
1129
|
+
bc2.style.color = 'var(--text-3)';
|
|
1130
|
+
}
|
|
1131
|
+
syncDomTreeSelection();
|
|
1132
|
+
}
|
|
1133
|
+
return null;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1040
1136
|
/** Stable stringify of a variation's changesets field (string or array from API). */
|
|
1041
1137
|
function fingerprintChangesetsField(raw) {
|
|
1042
1138
|
if (raw == null) return '[]';
|
|
@@ -1293,6 +1389,7 @@ function logChange(selector, inputId, value, targetEl, originalValue) {
|
|
|
1293
1389
|
if (idx >= 0) { stateChanges[idx] = entry; } else { stateChanges.push(entry); }
|
|
1294
1390
|
}
|
|
1295
1391
|
if (currentMainTab === 'states') renderStatesTab();
|
|
1392
|
+
commitStateChangesForActiveVariation();
|
|
1296
1393
|
recomputeEditorDirty();
|
|
1297
1394
|
}
|
|
1298
1395
|
|
|
@@ -1399,6 +1496,7 @@ function removeStateChange(idx) {
|
|
|
1399
1496
|
revertChangeOnDom(change);
|
|
1400
1497
|
syncDesignInput(change);
|
|
1401
1498
|
stateChanges.splice(idx, 1);
|
|
1499
|
+
commitStateChangesForActiveVariation();
|
|
1402
1500
|
renderStatesTab();
|
|
1403
1501
|
recomputeEditorDirty();
|
|
1404
1502
|
}
|
|
@@ -1409,6 +1507,7 @@ function clearAllStates() {
|
|
|
1409
1507
|
syncDesignInput(c);
|
|
1410
1508
|
});
|
|
1411
1509
|
stateChanges = [];
|
|
1510
|
+
commitStateChangesForActiveVariation();
|
|
1412
1511
|
renderStatesTab();
|
|
1413
1512
|
recomputeEditorDirty();
|
|
1414
1513
|
}
|
|
@@ -1783,7 +1882,9 @@ function handleLoadExperiment(data) {
|
|
|
1783
1882
|
experimentData = data;
|
|
1784
1883
|
variations = Array.isArray(data.variations) ? data.variations : [];
|
|
1785
1884
|
var prevActive = activeVarId;
|
|
1885
|
+
commitStateChangesForActiveVariation();
|
|
1786
1886
|
activeVarId = pickActiveVariationIdForLoad(data, variations, prevActive, true);
|
|
1887
|
+
loadStateChangesForActiveVariation();
|
|
1787
1888
|
writePersistedActiveVariationId(activeVarId);
|
|
1788
1889
|
renderVariationTabs();
|
|
1789
1890
|
var urlBarSkip = document.getElementById('url-bar');
|
|
@@ -1806,6 +1907,8 @@ function handleLoadExperiment(data) {
|
|
|
1806
1907
|
if (!experimentData || prevKey !== nextKey) {
|
|
1807
1908
|
varHtmlCache = {};
|
|
1808
1909
|
sessionStructuralChainRowsByVarId = {};
|
|
1910
|
+
stateChangesByVarId = {};
|
|
1911
|
+
stateChanges = [];
|
|
1809
1912
|
appliedChangesetSnapshots = {};
|
|
1810
1913
|
appliedStructuralChangesetKeys = {};
|
|
1811
1914
|
}
|
|
@@ -1813,6 +1916,7 @@ function handleLoadExperiment(data) {
|
|
|
1813
1916
|
variations = Array.isArray(data.variations) ? data.variations : [];
|
|
1814
1917
|
var sameExpPage = prevKey === nextKey;
|
|
1815
1918
|
activeVarId = pickActiveVariationIdForLoad(data, variations, activeVarId, sameExpPage);
|
|
1919
|
+
loadStateChangesForActiveVariation();
|
|
1816
1920
|
writePersistedActiveVariationId(activeVarId);
|
|
1817
1921
|
renderVariationTabs();
|
|
1818
1922
|
|
|
@@ -2136,8 +2240,10 @@ function renderVariationTabs() {
|
|
|
2136
2240
|
function switchVariation(varId) {
|
|
2137
2241
|
if (varId === activeVarId) return;
|
|
2138
2242
|
saveCurrentVariationHtml();
|
|
2243
|
+
commitStateChangesForActiveVariation();
|
|
2139
2244
|
clearPendingGranularChangesets();
|
|
2140
2245
|
activeVarId = varId;
|
|
2246
|
+
loadStateChangesForActiveVariation();
|
|
2141
2247
|
writePersistedActiveVariationId(varId);
|
|
2142
2248
|
renderVariationTabs();
|
|
2143
2249
|
deselectElement();
|
|
@@ -2173,6 +2279,7 @@ function switchVariation(varId) {
|
|
|
2173
2279
|
}
|
|
2174
2280
|
} catch(_) {}
|
|
2175
2281
|
if (currentMainTab === 'history') renderHistoryTab();
|
|
2282
|
+
if (currentMainTab === 'states') renderStatesTab();
|
|
2176
2283
|
recomputeEditorDirty();
|
|
2177
2284
|
}
|
|
2178
2285
|
|
|
@@ -2529,12 +2636,13 @@ function buildPersistedChainSetsForVariation(v) {
|
|
|
2529
2636
|
var parsed = parseVariationChangesets(v);
|
|
2530
2637
|
var base = filterGranularChangesetEntries(parsed);
|
|
2531
2638
|
var sessionExtra = sessionStructuralChainRowsByVarId[v._id] || [];
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2639
|
+
var sourceStateChanges =
|
|
2640
|
+
v._id === activeVarId
|
|
2641
|
+
? stateChanges
|
|
2642
|
+
: (stateChangesByVarId[v._id] || []);
|
|
2535
2643
|
var overlay = [];
|
|
2536
|
-
for (var si = 0; si <
|
|
2537
|
-
var row = stateChangeToChainSet(
|
|
2644
|
+
for (var si = 0; si < sourceStateChanges.length; si++) {
|
|
2645
|
+
var row = stateChangeToChainSet(sourceStateChanges[si]);
|
|
2538
2646
|
if (row) overlay.push(row);
|
|
2539
2647
|
}
|
|
2540
2648
|
return mergeGranularChainSets(mergeGranularChainSets(base, sessionExtra), overlay);
|
|
@@ -2640,6 +2748,8 @@ function selectElement(el) {
|
|
|
2640
2748
|
try {
|
|
2641
2749
|
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} }
|
|
2642
2750
|
selectedEl = el;
|
|
2751
|
+
selectedElFingerprint = buildSelector(el);
|
|
2752
|
+
selectedElRecoverMisses = 0;
|
|
2643
2753
|
try { el.classList.add('vve-selected'); } catch(_) {}
|
|
2644
2754
|
} finally {
|
|
2645
2755
|
endSuppressIframeMutationDirty();
|
|
@@ -2656,22 +2766,31 @@ function selectElement(el) {
|
|
|
2656
2766
|
}
|
|
2657
2767
|
}
|
|
2658
2768
|
|
|
2659
|
-
function deselectElement() {
|
|
2660
|
-
|
|
2661
|
-
|
|
2769
|
+
function deselectElement(options) {
|
|
2770
|
+
if (isDeselectingSelection) return;
|
|
2771
|
+
isDeselectingSelection = true;
|
|
2772
|
+
var skipToolbarUpdate = !!(options && options.skipToolbarUpdate);
|
|
2662
2773
|
try {
|
|
2663
|
-
|
|
2774
|
+
setDragHandleActive(false);
|
|
2775
|
+
beginSuppressIframeMutationDirty();
|
|
2776
|
+
try {
|
|
2777
|
+
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} selectedEl = null; }
|
|
2778
|
+
selectedElFingerprint = '';
|
|
2779
|
+
selectedElRecoverMisses = 0;
|
|
2780
|
+
} finally {
|
|
2781
|
+
endSuppressIframeMutationDirty();
|
|
2782
|
+
}
|
|
2783
|
+
document.getElementById('no-sel').style.display = '';
|
|
2784
|
+
document.getElementById('el-info').style.display = 'none';
|
|
2785
|
+
document.getElementById('rp-accordion').style.display = 'none';
|
|
2786
|
+
document.getElementById('bc-path').textContent = 'No element selected';
|
|
2787
|
+
document.getElementById('bc-path').style.color = 'var(--text-3)';
|
|
2788
|
+
switchMainTab('design');
|
|
2789
|
+
if (!skipToolbarUpdate) updateSelectionToolbar();
|
|
2790
|
+
syncDomTreeSelection();
|
|
2664
2791
|
} finally {
|
|
2665
|
-
|
|
2792
|
+
isDeselectingSelection = false;
|
|
2666
2793
|
}
|
|
2667
|
-
document.getElementById('no-sel').style.display = '';
|
|
2668
|
-
document.getElementById('el-info').style.display = 'none';
|
|
2669
|
-
document.getElementById('rp-accordion').style.display = 'none';
|
|
2670
|
-
document.getElementById('bc-path').textContent = 'No element selected';
|
|
2671
|
-
document.getElementById('bc-path').style.color = 'var(--text-3)';
|
|
2672
|
-
switchMainTab('design');
|
|
2673
|
-
updateSelectionToolbar();
|
|
2674
|
-
syncDomTreeSelection();
|
|
2675
2794
|
}
|
|
2676
2795
|
|
|
2677
2796
|
// \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
|
|
@@ -2714,7 +2833,13 @@ function positionSelectionToolbar() {
|
|
|
2714
2833
|
var bar = document.getElementById('selection-floater');
|
|
2715
2834
|
var iframe = document.getElementById('iframeId');
|
|
2716
2835
|
var panel = document.getElementById('iframe-panel');
|
|
2717
|
-
|
|
2836
|
+
var liveSelected = recoverSelectedElement(false);
|
|
2837
|
+
if (!bar || !liveSelected || !iframe || !iframe.contentWindow || !panel) return;
|
|
2838
|
+
if (selectedEl !== liveSelected) {
|
|
2839
|
+
selectedEl = liveSelected;
|
|
2840
|
+
renderRightPanel(liveSelected);
|
|
2841
|
+
syncDomTreeSelection();
|
|
2842
|
+
}
|
|
2718
2843
|
var elR = selectedEl.getBoundingClientRect();
|
|
2719
2844
|
var iframeR = iframe.getBoundingClientRect();
|
|
2720
2845
|
var panelR = panel.getBoundingClientRect();
|
|
@@ -2730,10 +2855,17 @@ function positionSelectionToolbar() {
|
|
|
2730
2855
|
function updateSelectionToolbar() {
|
|
2731
2856
|
var bar = document.getElementById('selection-floater');
|
|
2732
2857
|
if (!bar) return;
|
|
2733
|
-
if (
|
|
2858
|
+
if (currentMode !== 'editor') {
|
|
2734
2859
|
bar.style.display = 'none';
|
|
2735
2860
|
return;
|
|
2736
2861
|
}
|
|
2862
|
+
var liveSelected = recoverSelectedElement(true);
|
|
2863
|
+
if (!liveSelected) {
|
|
2864
|
+
bar.style.display = 'none';
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
if (selectedEl !== liveSelected) selectedEl = liveSelected;
|
|
2868
|
+
selectedElFingerprint = buildSelector(liveSelected);
|
|
2737
2869
|
bar.style.display = 'flex';
|
|
2738
2870
|
requestAnimationFrame(function() { positionSelectionToolbar(); });
|
|
2739
2871
|
}
|
|
@@ -3701,9 +3833,13 @@ function attachChangeObserver() {
|
|
|
3701
3833
|
// Page JS replaced body children; allow structural rows (insert/reorder) to apply again.
|
|
3702
3834
|
appliedStructuralChangesetKeys = {};
|
|
3703
3835
|
}
|
|
3836
|
+
// Host scripts can replace selected nodes every few frames (e.g. A/B tool observers).
|
|
3837
|
+
// Keep selection sticky by re-resolving from fingerprint.
|
|
3838
|
+
recoverSelectedElement(false);
|
|
3704
3839
|
scheduleDomTreeRefresh();
|
|
3705
3840
|
scheduleGranularChangesetReapply();
|
|
3706
3841
|
scheduleConsistencyReconcile();
|
|
3842
|
+
updateSelectionToolbar();
|
|
3707
3843
|
});
|
|
3708
3844
|
changeObserver.observe(doc.body, {
|
|
3709
3845
|
childList: true, subtree: true, attributes: true, characterData: true
|
|
@@ -3911,6 +4047,7 @@ document.getElementById('btn-close').addEventListener('click', handleClose);
|
|
|
3911
4047
|
|
|
3912
4048
|
function handleSave() {
|
|
3913
4049
|
saveCurrentVariationHtml();
|
|
4050
|
+
commitStateChangesForActiveVariation();
|
|
3914
4051
|
var updatedVariations = variations.map(function(v) {
|
|
3915
4052
|
var prevParsed = parseVariationChangesets(v);
|
|
3916
4053
|
var granularPrev = filterGranularChangesetEntries(prevParsed);
|
|
@@ -3931,6 +4068,7 @@ function handleSave() {
|
|
|
3931
4068
|
variations = updatedVariations;
|
|
3932
4069
|
varHtmlCache = {};
|
|
3933
4070
|
sessionStructuralChainRowsByVarId = {};
|
|
4071
|
+
stateChangesByVarId = {};
|
|
3934
4072
|
stateChanges = [];
|
|
3935
4073
|
if (currentMainTab === 'states') renderStatesTab();
|
|
3936
4074
|
captureBaselineFromVariations(variations);
|
|
@@ -3948,9 +4086,23 @@ function handleClose() {
|
|
|
3948
4086
|
}
|
|
3949
4087
|
|
|
3950
4088
|
// \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
|
|
4089
|
+
function isNativeEditableTarget(target) {
|
|
4090
|
+
if (!target || target.nodeType !== 1) return false;
|
|
4091
|
+
if (target.isContentEditable) return true;
|
|
4092
|
+
if (target.closest && target.closest('[contenteditable=""],[contenteditable="true"],[contenteditable="plaintext-only"]')) {
|
|
4093
|
+
return true;
|
|
4094
|
+
}
|
|
4095
|
+
if (!target.tagName) return false;
|
|
4096
|
+
var tag = String(target.tagName).toLowerCase();
|
|
4097
|
+
return tag === 'input' || tag === 'textarea' || tag === 'select';
|
|
4098
|
+
}
|
|
4099
|
+
|
|
3951
4100
|
document.addEventListener('keydown', function(e) {
|
|
4101
|
+
// Keep native browser undo/redo inside text inputs/contenteditable fields.
|
|
4102
|
+
if (isNativeEditableTarget(e.target)) return;
|
|
3952
4103
|
var meta = e.metaKey || e.ctrlKey;
|
|
3953
|
-
|
|
4104
|
+
var k = (e.key || '').toLowerCase();
|
|
4105
|
+
if (meta && !e.shiftKey && k === 'z') {
|
|
3954
4106
|
e.preventDefault();
|
|
3955
4107
|
if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
|
|
3956
4108
|
Vvveb.Undo.undo();
|
|
@@ -3958,7 +4110,7 @@ document.addEventListener('keydown', function(e) {
|
|
|
3958
4110
|
recomputeEditorDirty();
|
|
3959
4111
|
}
|
|
3960
4112
|
}
|
|
3961
|
-
if (meta && e.shiftKey &&
|
|
4113
|
+
if (meta && e.shiftKey && k === 'z') {
|
|
3962
4114
|
e.preventDefault();
|
|
3963
4115
|
if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
|
|
3964
4116
|
Vvveb.Undo.redo();
|
|
@@ -4427,7 +4579,96 @@ function createVisualEditorMiddleware(options) {
|
|
|
4427
4579
|
html = html.replace("</head>", `${popupHideCss}
|
|
4428
4580
|
</head>`);
|
|
4429
4581
|
}
|
|
4430
|
-
|
|
4582
|
+
html = html.replace(
|
|
4583
|
+
/<meta[^>]+http-equiv=["']?\s*(x-frame-options|content-security-policy)\s*["']?[^>]*>/gi,
|
|
4584
|
+
""
|
|
4585
|
+
);
|
|
4586
|
+
html = html.replace(
|
|
4587
|
+
/<meta[^>]+name=["']?\s*content-security-policy\s*["']?[^>]*>/gi,
|
|
4588
|
+
""
|
|
4589
|
+
);
|
|
4590
|
+
const runtimePreflightScript = `<script>(function(){try{
|
|
4591
|
+
var TARGET_ORIGIN=${JSON.stringify(origin)};
|
|
4592
|
+
var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
|
|
4593
|
+
var PROXY_PASSWORD=${JSON.stringify(password)};
|
|
4594
|
+
window.__CONVERSION_EDITOR_ACTIVE__=true;
|
|
4595
|
+
function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
|
|
4596
|
+
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;}}
|
|
4597
|
+
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;}}
|
|
4598
|
+
var nativeAssign=window.location.assign?window.location.assign.bind(window.location):null;
|
|
4599
|
+
var nativeReplace=window.location.replace?window.location.replace.bind(window.location):null;
|
|
4600
|
+
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;}}
|
|
4601
|
+
try{if(nativeAssign){window.location.assign=function(url){return safeNavigate(url,"assign");};}}catch(_){}
|
|
4602
|
+
try{if(nativeReplace){window.location.replace=function(url){return safeNavigate(url,"replace");};}}catch(_){}
|
|
4603
|
+
try{var hrefDesc=Object.getOwnPropertyDescriptor(Location.prototype,"href");if(hrefDesc&&hrefDesc.configurable&&hrefDesc.get&&hrefDesc.set){Object.defineProperty(Location.prototype,"href",{configurable:true,enumerable:hrefDesc.enumerable,get:function(){return hrefDesc.get.call(this);},set:function(v){safeNavigate(v,"assign");}});}}catch(_){}
|
|
4604
|
+
try{
|
|
4605
|
+
var NativeMO=window.MutationObserver;
|
|
4606
|
+
if(NativeMO&&!window.__CONVERSION_MO_GUARDED__){
|
|
4607
|
+
window.__CONVERSION_MO_GUARDED__=true;
|
|
4608
|
+
window.MutationObserver=function(cb){
|
|
4609
|
+
var last=0;
|
|
4610
|
+
var wrapped=function(list,obs){
|
|
4611
|
+
try{
|
|
4612
|
+
if(!window.__CONVERSION_EDITOR_ACTIVE__)return cb(list,obs);
|
|
4613
|
+
var now=Date.now();
|
|
4614
|
+
if(now-last<120)return;
|
|
4615
|
+
last=now;
|
|
4616
|
+
return cb(list,obs);
|
|
4617
|
+
}catch(_){}
|
|
4618
|
+
};
|
|
4619
|
+
return new NativeMO(wrapped);
|
|
4620
|
+
};
|
|
4621
|
+
window.MutationObserver.prototype=NativeMO.prototype;
|
|
4622
|
+
}
|
|
4623
|
+
}catch(_){}
|
|
4624
|
+
}catch(_){}})();</script>`;
|
|
4625
|
+
html = html.replace(/<head([^>]*)>/i, `<head$1>
|
|
4626
|
+
${runtimePreflightScript}
|
|
4627
|
+
`);
|
|
4628
|
+
const runtimeProxyScript = `<script>(function(){try{
|
|
4629
|
+
var TARGET_ORIGIN=${JSON.stringify(origin)};
|
|
4630
|
+
var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
|
|
4631
|
+
var EMPTY_JSON_DATA="data:application/json;charset=utf-8,%7B%7D";
|
|
4632
|
+
function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#")||raw.startsWith("http")||raw.startsWith("//");}
|
|
4633
|
+
function toAbsoluteOriginUrl(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;var abs=new URL(raw,base);if(abs.origin!==TARGET_ORIGIN)return raw;return abs.toString();}catch(_){return raw;}}
|
|
4634
|
+
function resolveUrl(s){try{return new URL(s,window.location.href);}catch(_){return null;}}
|
|
4635
|
+
function isNestedMalformedProxy(u){if(!u)return false;var p=u.pathname||"";if(p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0)return false;return p.indexOf("api/conversion-proxy")!==-1;}
|
|
4636
|
+
function skipNestedProxyNetwork(s){var u=typeof s==="string"?resolveUrl(s):null;return u&&isNestedMalformedProxy(u);}
|
|
4637
|
+
function emptyJsonFetchResponse(){return Promise.resolve(new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}}));}
|
|
4638
|
+
if(window.fetch){
|
|
4639
|
+
var _fetch=window.fetch.bind(window);
|
|
4640
|
+
window.fetch=function(input,init){
|
|
4641
|
+
var afterUrl="";
|
|
4642
|
+
try{
|
|
4643
|
+
var rawUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");
|
|
4644
|
+
if(rawUrl&&skipNestedProxyNetwork(rawUrl))return emptyJsonFetchResponse();
|
|
4645
|
+
if(typeof input==="string"){
|
|
4646
|
+
input=toAbsoluteOriginUrl(input);
|
|
4647
|
+
}else if(input&&input.url){
|
|
4648
|
+
var next=toAbsoluteOriginUrl(input.url);
|
|
4649
|
+
if(next!==input.url){input=new Request(next,input);}
|
|
4650
|
+
}
|
|
4651
|
+
afterUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");
|
|
4652
|
+
if(afterUrl&&skipNestedProxyNetwork(afterUrl))return emptyJsonFetchResponse();
|
|
4653
|
+
}catch(_){}
|
|
4654
|
+
return _fetch(input,init).catch(function(err){
|
|
4655
|
+
try{
|
|
4656
|
+
var u=afterUrl?resolveUrl(afterUrl):null;
|
|
4657
|
+
var sameOrigin=!!(u&&u.origin===TARGET_ORIGIN);
|
|
4658
|
+
var likelyThirdPartyBg=!sameOrigin||!!(u&&/(^|\\/)apps?(\\/|$)|(^|\\/)a(\\/|$)/.test(u.pathname||""));
|
|
4659
|
+
if(window.__CONVERSION_EDITOR_ACTIVE__&&likelyThirdPartyBg){
|
|
4660
|
+
console.warn("[conversion-proxy] suppressed fetch failure",{url:afterUrl,error:err&&err.message?err.message:String(err)});
|
|
4661
|
+
return new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}});
|
|
4662
|
+
}
|
|
4663
|
+
}catch(_){}
|
|
4664
|
+
throw err;
|
|
4665
|
+
});
|
|
4666
|
+
};
|
|
4667
|
+
}
|
|
4668
|
+
if(window.XMLHttpRequest&&window.XMLHttpRequest.prototype&&window.XMLHttpRequest.prototype.open){var _open=window.XMLHttpRequest.prototype.open;window.XMLHttpRequest.prototype.open=function(method,url){try{var u=resolveUrl(String(url));if(u&&isNestedMalformedProxy(u)){arguments[1]=EMPTY_JSON_DATA;}else{arguments[1]=toAbsoluteOriginUrl(url);}}catch(_){}return _open.apply(this,arguments);};}
|
|
4669
|
+
if(window.navigator&&typeof window.navigator.sendBeacon==="function"){var _beacon=window.navigator.sendBeacon.bind(window.navigator);window.navigator.sendBeacon=function(url,data){try{if(skipNestedProxyNetwork(String(url)))return true;}catch(_){}return _beacon(url,data);};}
|
|
4670
|
+
if(window.navigator&&window.navigator.serviceWorker&&typeof window.navigator.serviceWorker.register==="function"){window.navigator.serviceWorker.register=function(){return Promise.resolve({scope:"disabled-in-editor-proxy"});};}
|
|
4671
|
+
}catch(_){}})();</script>`;
|
|
4431
4672
|
if (html.includes("</head>")) {
|
|
4432
4673
|
html = html.replace("</head>", `${runtimeProxyScript}
|
|
4433
4674
|
</head>`);
|
package/dist/vite.js
CHANGED
|
@@ -987,6 +987,11 @@ var vvvebReady = false;
|
|
|
987
987
|
var currentMode = 'editor';
|
|
988
988
|
var currentDevice = 'desktop';
|
|
989
989
|
var selectedEl = null;
|
|
990
|
+
/** Stable selector fingerprint for resilient selection recovery after DOM churn. */
|
|
991
|
+
var selectedElFingerprint = '';
|
|
992
|
+
var selectedElRecoverMisses = 0;
|
|
993
|
+
var MAX_SELECTED_RECOVER_MISSES = 12;
|
|
994
|
+
var isDeselectingSelection = false;
|
|
990
995
|
var suppressClickUntil = 0;
|
|
991
996
|
var dragAttachDoc = null;
|
|
992
997
|
var currentMainTab = 'design';
|
|
@@ -1015,6 +1020,8 @@ var iframeDocLoadingListeners = null;
|
|
|
1015
1020
|
// Each entry: {selector, label, cssProp, value, targetEl}
|
|
1016
1021
|
// cssProp is null for non-CSS attributes (href, alt, classes\u2026)
|
|
1017
1022
|
var stateChanges = [];
|
|
1023
|
+
/** Per-variation unsaved Design-tab state changes (input edits not yet finalized). */
|
|
1024
|
+
var stateChangesByVarId = {};
|
|
1018
1025
|
/** Pre-apply DOM snapshots for granular + body changesets (used when removing History rows) */
|
|
1019
1026
|
var appliedChangesetSnapshots = {};
|
|
1020
1027
|
/** Canonical JSON fingerprints of persisted changesets per variation (last load / finalize) */
|
|
@@ -1029,6 +1036,95 @@ function endSuppressIframeMutationDirty() {
|
|
|
1029
1036
|
suppressIframeMutationDirty = Math.max(0, suppressIframeMutationDirty - 1);
|
|
1030
1037
|
}
|
|
1031
1038
|
|
|
1039
|
+
function commitStateChangesForActiveVariation() {
|
|
1040
|
+
if (!activeVarId) return;
|
|
1041
|
+
stateChangesByVarId[activeVarId] = (stateChanges || []).slice();
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function loadStateChangesForActiveVariation() {
|
|
1045
|
+
if (!activeVarId) {
|
|
1046
|
+
stateChanges = [];
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
stateChanges = (stateChangesByVarId[activeVarId] || []).slice();
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function recoverSelectedElement(forceDeselectOnMiss) {
|
|
1053
|
+
if (selectedEl && selectedEl.ownerDocument && selectedEl.ownerDocument.contains(selectedEl)) {
|
|
1054
|
+
selectedElRecoverMisses = 0;
|
|
1055
|
+
return selectedEl;
|
|
1056
|
+
}
|
|
1057
|
+
if (!selectedElFingerprint) {
|
|
1058
|
+
// Nothing to recover; clear stale reference without calling deselectElement
|
|
1059
|
+
// (which can recurse back through toolbar updates during rapid DOM churn).
|
|
1060
|
+
if (forceDeselectOnMiss && selectedEl) {
|
|
1061
|
+
beginSuppressIframeMutationDirty();
|
|
1062
|
+
try {
|
|
1063
|
+
try { selectedEl.classList.remove('vve-selected'); } catch(_) {}
|
|
1064
|
+
selectedEl = null;
|
|
1065
|
+
selectedElRecoverMisses = 0;
|
|
1066
|
+
} finally {
|
|
1067
|
+
endSuppressIframeMutationDirty();
|
|
1068
|
+
}
|
|
1069
|
+
var noSel = document.getElementById('no-sel');
|
|
1070
|
+
if (noSel) noSel.style.display = '';
|
|
1071
|
+
var elInfo = document.getElementById('el-info');
|
|
1072
|
+
if (elInfo) elInfo.style.display = 'none';
|
|
1073
|
+
var rp = document.getElementById('rp-accordion');
|
|
1074
|
+
if (rp) rp.style.display = 'none';
|
|
1075
|
+
var bc = document.getElementById('bc-path');
|
|
1076
|
+
if (bc) {
|
|
1077
|
+
bc.textContent = 'No element selected';
|
|
1078
|
+
bc.style.color = 'var(--text-3)';
|
|
1079
|
+
}
|
|
1080
|
+
syncDomTreeSelection();
|
|
1081
|
+
}
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
var iframe = document.getElementById('iframeId');
|
|
1085
|
+
var doc = iframe && iframe.contentDocument;
|
|
1086
|
+
if (!doc) return null;
|
|
1087
|
+
var recovered = querySelectorResolved(doc, selectedElFingerprint);
|
|
1088
|
+
if (recovered) {
|
|
1089
|
+
beginSuppressIframeMutationDirty();
|
|
1090
|
+
try {
|
|
1091
|
+
selectedEl = recovered;
|
|
1092
|
+
if (selectedEl.classList) selectedEl.classList.add('vve-selected');
|
|
1093
|
+
} finally {
|
|
1094
|
+
endSuppressIframeMutationDirty();
|
|
1095
|
+
}
|
|
1096
|
+
selectedElRecoverMisses = 0;
|
|
1097
|
+
return recovered;
|
|
1098
|
+
}
|
|
1099
|
+
selectedElRecoverMisses += 1;
|
|
1100
|
+
if (forceDeselectOnMiss && selectedElRecoverMisses >= MAX_SELECTED_RECOVER_MISSES) {
|
|
1101
|
+
beginSuppressIframeMutationDirty();
|
|
1102
|
+
try {
|
|
1103
|
+
if (selectedEl) {
|
|
1104
|
+
try { selectedEl.classList.remove('vve-selected'); } catch(_) {}
|
|
1105
|
+
}
|
|
1106
|
+
selectedEl = null;
|
|
1107
|
+
selectedElFingerprint = '';
|
|
1108
|
+
selectedElRecoverMisses = 0;
|
|
1109
|
+
} finally {
|
|
1110
|
+
endSuppressIframeMutationDirty();
|
|
1111
|
+
}
|
|
1112
|
+
var noSel2 = document.getElementById('no-sel');
|
|
1113
|
+
if (noSel2) noSel2.style.display = '';
|
|
1114
|
+
var elInfo2 = document.getElementById('el-info');
|
|
1115
|
+
if (elInfo2) elInfo2.style.display = 'none';
|
|
1116
|
+
var rp2 = document.getElementById('rp-accordion');
|
|
1117
|
+
if (rp2) rp2.style.display = 'none';
|
|
1118
|
+
var bc2 = document.getElementById('bc-path');
|
|
1119
|
+
if (bc2) {
|
|
1120
|
+
bc2.textContent = 'No element selected';
|
|
1121
|
+
bc2.style.color = 'var(--text-3)';
|
|
1122
|
+
}
|
|
1123
|
+
syncDomTreeSelection();
|
|
1124
|
+
}
|
|
1125
|
+
return null;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1032
1128
|
/** Stable stringify of a variation's changesets field (string or array from API). */
|
|
1033
1129
|
function fingerprintChangesetsField(raw) {
|
|
1034
1130
|
if (raw == null) return '[]';
|
|
@@ -1285,6 +1381,7 @@ function logChange(selector, inputId, value, targetEl, originalValue) {
|
|
|
1285
1381
|
if (idx >= 0) { stateChanges[idx] = entry; } else { stateChanges.push(entry); }
|
|
1286
1382
|
}
|
|
1287
1383
|
if (currentMainTab === 'states') renderStatesTab();
|
|
1384
|
+
commitStateChangesForActiveVariation();
|
|
1288
1385
|
recomputeEditorDirty();
|
|
1289
1386
|
}
|
|
1290
1387
|
|
|
@@ -1391,6 +1488,7 @@ function removeStateChange(idx) {
|
|
|
1391
1488
|
revertChangeOnDom(change);
|
|
1392
1489
|
syncDesignInput(change);
|
|
1393
1490
|
stateChanges.splice(idx, 1);
|
|
1491
|
+
commitStateChangesForActiveVariation();
|
|
1394
1492
|
renderStatesTab();
|
|
1395
1493
|
recomputeEditorDirty();
|
|
1396
1494
|
}
|
|
@@ -1401,6 +1499,7 @@ function clearAllStates() {
|
|
|
1401
1499
|
syncDesignInput(c);
|
|
1402
1500
|
});
|
|
1403
1501
|
stateChanges = [];
|
|
1502
|
+
commitStateChangesForActiveVariation();
|
|
1404
1503
|
renderStatesTab();
|
|
1405
1504
|
recomputeEditorDirty();
|
|
1406
1505
|
}
|
|
@@ -1775,7 +1874,9 @@ function handleLoadExperiment(data) {
|
|
|
1775
1874
|
experimentData = data;
|
|
1776
1875
|
variations = Array.isArray(data.variations) ? data.variations : [];
|
|
1777
1876
|
var prevActive = activeVarId;
|
|
1877
|
+
commitStateChangesForActiveVariation();
|
|
1778
1878
|
activeVarId = pickActiveVariationIdForLoad(data, variations, prevActive, true);
|
|
1879
|
+
loadStateChangesForActiveVariation();
|
|
1779
1880
|
writePersistedActiveVariationId(activeVarId);
|
|
1780
1881
|
renderVariationTabs();
|
|
1781
1882
|
var urlBarSkip = document.getElementById('url-bar');
|
|
@@ -1798,6 +1899,8 @@ function handleLoadExperiment(data) {
|
|
|
1798
1899
|
if (!experimentData || prevKey !== nextKey) {
|
|
1799
1900
|
varHtmlCache = {};
|
|
1800
1901
|
sessionStructuralChainRowsByVarId = {};
|
|
1902
|
+
stateChangesByVarId = {};
|
|
1903
|
+
stateChanges = [];
|
|
1801
1904
|
appliedChangesetSnapshots = {};
|
|
1802
1905
|
appliedStructuralChangesetKeys = {};
|
|
1803
1906
|
}
|
|
@@ -1805,6 +1908,7 @@ function handleLoadExperiment(data) {
|
|
|
1805
1908
|
variations = Array.isArray(data.variations) ? data.variations : [];
|
|
1806
1909
|
var sameExpPage = prevKey === nextKey;
|
|
1807
1910
|
activeVarId = pickActiveVariationIdForLoad(data, variations, activeVarId, sameExpPage);
|
|
1911
|
+
loadStateChangesForActiveVariation();
|
|
1808
1912
|
writePersistedActiveVariationId(activeVarId);
|
|
1809
1913
|
renderVariationTabs();
|
|
1810
1914
|
|
|
@@ -2128,8 +2232,10 @@ function renderVariationTabs() {
|
|
|
2128
2232
|
function switchVariation(varId) {
|
|
2129
2233
|
if (varId === activeVarId) return;
|
|
2130
2234
|
saveCurrentVariationHtml();
|
|
2235
|
+
commitStateChangesForActiveVariation();
|
|
2131
2236
|
clearPendingGranularChangesets();
|
|
2132
2237
|
activeVarId = varId;
|
|
2238
|
+
loadStateChangesForActiveVariation();
|
|
2133
2239
|
writePersistedActiveVariationId(varId);
|
|
2134
2240
|
renderVariationTabs();
|
|
2135
2241
|
deselectElement();
|
|
@@ -2165,6 +2271,7 @@ function switchVariation(varId) {
|
|
|
2165
2271
|
}
|
|
2166
2272
|
} catch(_) {}
|
|
2167
2273
|
if (currentMainTab === 'history') renderHistoryTab();
|
|
2274
|
+
if (currentMainTab === 'states') renderStatesTab();
|
|
2168
2275
|
recomputeEditorDirty();
|
|
2169
2276
|
}
|
|
2170
2277
|
|
|
@@ -2521,12 +2628,13 @@ function buildPersistedChainSetsForVariation(v) {
|
|
|
2521
2628
|
var parsed = parseVariationChangesets(v);
|
|
2522
2629
|
var base = filterGranularChangesetEntries(parsed);
|
|
2523
2630
|
var sessionExtra = sessionStructuralChainRowsByVarId[v._id] || [];
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2631
|
+
var sourceStateChanges =
|
|
2632
|
+
v._id === activeVarId
|
|
2633
|
+
? stateChanges
|
|
2634
|
+
: (stateChangesByVarId[v._id] || []);
|
|
2527
2635
|
var overlay = [];
|
|
2528
|
-
for (var si = 0; si <
|
|
2529
|
-
var row = stateChangeToChainSet(
|
|
2636
|
+
for (var si = 0; si < sourceStateChanges.length; si++) {
|
|
2637
|
+
var row = stateChangeToChainSet(sourceStateChanges[si]);
|
|
2530
2638
|
if (row) overlay.push(row);
|
|
2531
2639
|
}
|
|
2532
2640
|
return mergeGranularChainSets(mergeGranularChainSets(base, sessionExtra), overlay);
|
|
@@ -2632,6 +2740,8 @@ function selectElement(el) {
|
|
|
2632
2740
|
try {
|
|
2633
2741
|
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} }
|
|
2634
2742
|
selectedEl = el;
|
|
2743
|
+
selectedElFingerprint = buildSelector(el);
|
|
2744
|
+
selectedElRecoverMisses = 0;
|
|
2635
2745
|
try { el.classList.add('vve-selected'); } catch(_) {}
|
|
2636
2746
|
} finally {
|
|
2637
2747
|
endSuppressIframeMutationDirty();
|
|
@@ -2648,22 +2758,31 @@ function selectElement(el) {
|
|
|
2648
2758
|
}
|
|
2649
2759
|
}
|
|
2650
2760
|
|
|
2651
|
-
function deselectElement() {
|
|
2652
|
-
|
|
2653
|
-
|
|
2761
|
+
function deselectElement(options) {
|
|
2762
|
+
if (isDeselectingSelection) return;
|
|
2763
|
+
isDeselectingSelection = true;
|
|
2764
|
+
var skipToolbarUpdate = !!(options && options.skipToolbarUpdate);
|
|
2654
2765
|
try {
|
|
2655
|
-
|
|
2766
|
+
setDragHandleActive(false);
|
|
2767
|
+
beginSuppressIframeMutationDirty();
|
|
2768
|
+
try {
|
|
2769
|
+
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} selectedEl = null; }
|
|
2770
|
+
selectedElFingerprint = '';
|
|
2771
|
+
selectedElRecoverMisses = 0;
|
|
2772
|
+
} finally {
|
|
2773
|
+
endSuppressIframeMutationDirty();
|
|
2774
|
+
}
|
|
2775
|
+
document.getElementById('no-sel').style.display = '';
|
|
2776
|
+
document.getElementById('el-info').style.display = 'none';
|
|
2777
|
+
document.getElementById('rp-accordion').style.display = 'none';
|
|
2778
|
+
document.getElementById('bc-path').textContent = 'No element selected';
|
|
2779
|
+
document.getElementById('bc-path').style.color = 'var(--text-3)';
|
|
2780
|
+
switchMainTab('design');
|
|
2781
|
+
if (!skipToolbarUpdate) updateSelectionToolbar();
|
|
2782
|
+
syncDomTreeSelection();
|
|
2656
2783
|
} finally {
|
|
2657
|
-
|
|
2784
|
+
isDeselectingSelection = false;
|
|
2658
2785
|
}
|
|
2659
|
-
document.getElementById('no-sel').style.display = '';
|
|
2660
|
-
document.getElementById('el-info').style.display = 'none';
|
|
2661
|
-
document.getElementById('rp-accordion').style.display = 'none';
|
|
2662
|
-
document.getElementById('bc-path').textContent = 'No element selected';
|
|
2663
|
-
document.getElementById('bc-path').style.color = 'var(--text-3)';
|
|
2664
|
-
switchMainTab('design');
|
|
2665
|
-
updateSelectionToolbar();
|
|
2666
|
-
syncDomTreeSelection();
|
|
2667
2786
|
}
|
|
2668
2787
|
|
|
2669
2788
|
// \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
|
|
@@ -2706,7 +2825,13 @@ function positionSelectionToolbar() {
|
|
|
2706
2825
|
var bar = document.getElementById('selection-floater');
|
|
2707
2826
|
var iframe = document.getElementById('iframeId');
|
|
2708
2827
|
var panel = document.getElementById('iframe-panel');
|
|
2709
|
-
|
|
2828
|
+
var liveSelected = recoverSelectedElement(false);
|
|
2829
|
+
if (!bar || !liveSelected || !iframe || !iframe.contentWindow || !panel) return;
|
|
2830
|
+
if (selectedEl !== liveSelected) {
|
|
2831
|
+
selectedEl = liveSelected;
|
|
2832
|
+
renderRightPanel(liveSelected);
|
|
2833
|
+
syncDomTreeSelection();
|
|
2834
|
+
}
|
|
2710
2835
|
var elR = selectedEl.getBoundingClientRect();
|
|
2711
2836
|
var iframeR = iframe.getBoundingClientRect();
|
|
2712
2837
|
var panelR = panel.getBoundingClientRect();
|
|
@@ -2722,10 +2847,17 @@ function positionSelectionToolbar() {
|
|
|
2722
2847
|
function updateSelectionToolbar() {
|
|
2723
2848
|
var bar = document.getElementById('selection-floater');
|
|
2724
2849
|
if (!bar) return;
|
|
2725
|
-
if (
|
|
2850
|
+
if (currentMode !== 'editor') {
|
|
2726
2851
|
bar.style.display = 'none';
|
|
2727
2852
|
return;
|
|
2728
2853
|
}
|
|
2854
|
+
var liveSelected = recoverSelectedElement(true);
|
|
2855
|
+
if (!liveSelected) {
|
|
2856
|
+
bar.style.display = 'none';
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2859
|
+
if (selectedEl !== liveSelected) selectedEl = liveSelected;
|
|
2860
|
+
selectedElFingerprint = buildSelector(liveSelected);
|
|
2729
2861
|
bar.style.display = 'flex';
|
|
2730
2862
|
requestAnimationFrame(function() { positionSelectionToolbar(); });
|
|
2731
2863
|
}
|
|
@@ -3693,9 +3825,13 @@ function attachChangeObserver() {
|
|
|
3693
3825
|
// Page JS replaced body children; allow structural rows (insert/reorder) to apply again.
|
|
3694
3826
|
appliedStructuralChangesetKeys = {};
|
|
3695
3827
|
}
|
|
3828
|
+
// Host scripts can replace selected nodes every few frames (e.g. A/B tool observers).
|
|
3829
|
+
// Keep selection sticky by re-resolving from fingerprint.
|
|
3830
|
+
recoverSelectedElement(false);
|
|
3696
3831
|
scheduleDomTreeRefresh();
|
|
3697
3832
|
scheduleGranularChangesetReapply();
|
|
3698
3833
|
scheduleConsistencyReconcile();
|
|
3834
|
+
updateSelectionToolbar();
|
|
3699
3835
|
});
|
|
3700
3836
|
changeObserver.observe(doc.body, {
|
|
3701
3837
|
childList: true, subtree: true, attributes: true, characterData: true
|
|
@@ -3903,6 +4039,7 @@ document.getElementById('btn-close').addEventListener('click', handleClose);
|
|
|
3903
4039
|
|
|
3904
4040
|
function handleSave() {
|
|
3905
4041
|
saveCurrentVariationHtml();
|
|
4042
|
+
commitStateChangesForActiveVariation();
|
|
3906
4043
|
var updatedVariations = variations.map(function(v) {
|
|
3907
4044
|
var prevParsed = parseVariationChangesets(v);
|
|
3908
4045
|
var granularPrev = filterGranularChangesetEntries(prevParsed);
|
|
@@ -3923,6 +4060,7 @@ function handleSave() {
|
|
|
3923
4060
|
variations = updatedVariations;
|
|
3924
4061
|
varHtmlCache = {};
|
|
3925
4062
|
sessionStructuralChainRowsByVarId = {};
|
|
4063
|
+
stateChangesByVarId = {};
|
|
3926
4064
|
stateChanges = [];
|
|
3927
4065
|
if (currentMainTab === 'states') renderStatesTab();
|
|
3928
4066
|
captureBaselineFromVariations(variations);
|
|
@@ -3940,9 +4078,23 @@ function handleClose() {
|
|
|
3940
4078
|
}
|
|
3941
4079
|
|
|
3942
4080
|
// \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
|
|
4081
|
+
function isNativeEditableTarget(target) {
|
|
4082
|
+
if (!target || target.nodeType !== 1) return false;
|
|
4083
|
+
if (target.isContentEditable) return true;
|
|
4084
|
+
if (target.closest && target.closest('[contenteditable=""],[contenteditable="true"],[contenteditable="plaintext-only"]')) {
|
|
4085
|
+
return true;
|
|
4086
|
+
}
|
|
4087
|
+
if (!target.tagName) return false;
|
|
4088
|
+
var tag = String(target.tagName).toLowerCase();
|
|
4089
|
+
return tag === 'input' || tag === 'textarea' || tag === 'select';
|
|
4090
|
+
}
|
|
4091
|
+
|
|
3943
4092
|
document.addEventListener('keydown', function(e) {
|
|
4093
|
+
// Keep native browser undo/redo inside text inputs/contenteditable fields.
|
|
4094
|
+
if (isNativeEditableTarget(e.target)) return;
|
|
3944
4095
|
var meta = e.metaKey || e.ctrlKey;
|
|
3945
|
-
|
|
4096
|
+
var k = (e.key || '').toLowerCase();
|
|
4097
|
+
if (meta && !e.shiftKey && k === 'z') {
|
|
3946
4098
|
e.preventDefault();
|
|
3947
4099
|
if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
|
|
3948
4100
|
Vvveb.Undo.undo();
|
|
@@ -3950,7 +4102,7 @@ document.addEventListener('keydown', function(e) {
|
|
|
3950
4102
|
recomputeEditorDirty();
|
|
3951
4103
|
}
|
|
3952
4104
|
}
|
|
3953
|
-
if (meta && e.shiftKey &&
|
|
4105
|
+
if (meta && e.shiftKey && k === 'z') {
|
|
3954
4106
|
e.preventDefault();
|
|
3955
4107
|
if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
|
|
3956
4108
|
Vvveb.Undo.redo();
|
|
@@ -4419,7 +4571,96 @@ function createVisualEditorMiddleware(options) {
|
|
|
4419
4571
|
html = html.replace("</head>", `${popupHideCss}
|
|
4420
4572
|
</head>`);
|
|
4421
4573
|
}
|
|
4422
|
-
|
|
4574
|
+
html = html.replace(
|
|
4575
|
+
/<meta[^>]+http-equiv=["']?\s*(x-frame-options|content-security-policy)\s*["']?[^>]*>/gi,
|
|
4576
|
+
""
|
|
4577
|
+
);
|
|
4578
|
+
html = html.replace(
|
|
4579
|
+
/<meta[^>]+name=["']?\s*content-security-policy\s*["']?[^>]*>/gi,
|
|
4580
|
+
""
|
|
4581
|
+
);
|
|
4582
|
+
const runtimePreflightScript = `<script>(function(){try{
|
|
4583
|
+
var TARGET_ORIGIN=${JSON.stringify(origin)};
|
|
4584
|
+
var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
|
|
4585
|
+
var PROXY_PASSWORD=${JSON.stringify(password)};
|
|
4586
|
+
window.__CONVERSION_EDITOR_ACTIVE__=true;
|
|
4587
|
+
function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
|
|
4588
|
+
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;}}
|
|
4589
|
+
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;}}
|
|
4590
|
+
var nativeAssign=window.location.assign?window.location.assign.bind(window.location):null;
|
|
4591
|
+
var nativeReplace=window.location.replace?window.location.replace.bind(window.location):null;
|
|
4592
|
+
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;}}
|
|
4593
|
+
try{if(nativeAssign){window.location.assign=function(url){return safeNavigate(url,"assign");};}}catch(_){}
|
|
4594
|
+
try{if(nativeReplace){window.location.replace=function(url){return safeNavigate(url,"replace");};}}catch(_){}
|
|
4595
|
+
try{var hrefDesc=Object.getOwnPropertyDescriptor(Location.prototype,"href");if(hrefDesc&&hrefDesc.configurable&&hrefDesc.get&&hrefDesc.set){Object.defineProperty(Location.prototype,"href",{configurable:true,enumerable:hrefDesc.enumerable,get:function(){return hrefDesc.get.call(this);},set:function(v){safeNavigate(v,"assign");}});}}catch(_){}
|
|
4596
|
+
try{
|
|
4597
|
+
var NativeMO=window.MutationObserver;
|
|
4598
|
+
if(NativeMO&&!window.__CONVERSION_MO_GUARDED__){
|
|
4599
|
+
window.__CONVERSION_MO_GUARDED__=true;
|
|
4600
|
+
window.MutationObserver=function(cb){
|
|
4601
|
+
var last=0;
|
|
4602
|
+
var wrapped=function(list,obs){
|
|
4603
|
+
try{
|
|
4604
|
+
if(!window.__CONVERSION_EDITOR_ACTIVE__)return cb(list,obs);
|
|
4605
|
+
var now=Date.now();
|
|
4606
|
+
if(now-last<120)return;
|
|
4607
|
+
last=now;
|
|
4608
|
+
return cb(list,obs);
|
|
4609
|
+
}catch(_){}
|
|
4610
|
+
};
|
|
4611
|
+
return new NativeMO(wrapped);
|
|
4612
|
+
};
|
|
4613
|
+
window.MutationObserver.prototype=NativeMO.prototype;
|
|
4614
|
+
}
|
|
4615
|
+
}catch(_){}
|
|
4616
|
+
}catch(_){}})();</script>`;
|
|
4617
|
+
html = html.replace(/<head([^>]*)>/i, `<head$1>
|
|
4618
|
+
${runtimePreflightScript}
|
|
4619
|
+
`);
|
|
4620
|
+
const runtimeProxyScript = `<script>(function(){try{
|
|
4621
|
+
var TARGET_ORIGIN=${JSON.stringify(origin)};
|
|
4622
|
+
var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
|
|
4623
|
+
var EMPTY_JSON_DATA="data:application/json;charset=utf-8,%7B%7D";
|
|
4624
|
+
function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#")||raw.startsWith("http")||raw.startsWith("//");}
|
|
4625
|
+
function toAbsoluteOriginUrl(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;var abs=new URL(raw,base);if(abs.origin!==TARGET_ORIGIN)return raw;return abs.toString();}catch(_){return raw;}}
|
|
4626
|
+
function resolveUrl(s){try{return new URL(s,window.location.href);}catch(_){return null;}}
|
|
4627
|
+
function isNestedMalformedProxy(u){if(!u)return false;var p=u.pathname||"";if(p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0)return false;return p.indexOf("api/conversion-proxy")!==-1;}
|
|
4628
|
+
function skipNestedProxyNetwork(s){var u=typeof s==="string"?resolveUrl(s):null;return u&&isNestedMalformedProxy(u);}
|
|
4629
|
+
function emptyJsonFetchResponse(){return Promise.resolve(new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}}));}
|
|
4630
|
+
if(window.fetch){
|
|
4631
|
+
var _fetch=window.fetch.bind(window);
|
|
4632
|
+
window.fetch=function(input,init){
|
|
4633
|
+
var afterUrl="";
|
|
4634
|
+
try{
|
|
4635
|
+
var rawUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");
|
|
4636
|
+
if(rawUrl&&skipNestedProxyNetwork(rawUrl))return emptyJsonFetchResponse();
|
|
4637
|
+
if(typeof input==="string"){
|
|
4638
|
+
input=toAbsoluteOriginUrl(input);
|
|
4639
|
+
}else if(input&&input.url){
|
|
4640
|
+
var next=toAbsoluteOriginUrl(input.url);
|
|
4641
|
+
if(next!==input.url){input=new Request(next,input);}
|
|
4642
|
+
}
|
|
4643
|
+
afterUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");
|
|
4644
|
+
if(afterUrl&&skipNestedProxyNetwork(afterUrl))return emptyJsonFetchResponse();
|
|
4645
|
+
}catch(_){}
|
|
4646
|
+
return _fetch(input,init).catch(function(err){
|
|
4647
|
+
try{
|
|
4648
|
+
var u=afterUrl?resolveUrl(afterUrl):null;
|
|
4649
|
+
var sameOrigin=!!(u&&u.origin===TARGET_ORIGIN);
|
|
4650
|
+
var likelyThirdPartyBg=!sameOrigin||!!(u&&/(^|\\/)apps?(\\/|$)|(^|\\/)a(\\/|$)/.test(u.pathname||""));
|
|
4651
|
+
if(window.__CONVERSION_EDITOR_ACTIVE__&&likelyThirdPartyBg){
|
|
4652
|
+
console.warn("[conversion-proxy] suppressed fetch failure",{url:afterUrl,error:err&&err.message?err.message:String(err)});
|
|
4653
|
+
return new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}});
|
|
4654
|
+
}
|
|
4655
|
+
}catch(_){}
|
|
4656
|
+
throw err;
|
|
4657
|
+
});
|
|
4658
|
+
};
|
|
4659
|
+
}
|
|
4660
|
+
if(window.XMLHttpRequest&&window.XMLHttpRequest.prototype&&window.XMLHttpRequest.prototype.open){var _open=window.XMLHttpRequest.prototype.open;window.XMLHttpRequest.prototype.open=function(method,url){try{var u=resolveUrl(String(url));if(u&&isNestedMalformedProxy(u)){arguments[1]=EMPTY_JSON_DATA;}else{arguments[1]=toAbsoluteOriginUrl(url);}}catch(_){}return _open.apply(this,arguments);};}
|
|
4661
|
+
if(window.navigator&&typeof window.navigator.sendBeacon==="function"){var _beacon=window.navigator.sendBeacon.bind(window.navigator);window.navigator.sendBeacon=function(url,data){try{if(skipNestedProxyNetwork(String(url)))return true;}catch(_){}return _beacon(url,data);};}
|
|
4662
|
+
if(window.navigator&&window.navigator.serviceWorker&&typeof window.navigator.serviceWorker.register==="function"){window.navigator.serviceWorker.register=function(){return Promise.resolve({scope:"disabled-in-editor-proxy"});};}
|
|
4663
|
+
}catch(_){}})();</script>`;
|
|
4423
4664
|
if (html.includes("</head>")) {
|
|
4424
4665
|
html = html.replace("</head>", `${runtimeProxyScript}
|
|
4425
4666
|
</head>`);
|