@accelerated-agency/visual-editor 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +17 -0
- package/dist/vite.cjs +856 -100
- package/dist/vite.js +856 -100
- package/package.json +1 -1
package/dist/vite.cjs
CHANGED
|
@@ -627,7 +627,7 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
627
627
|
</div>
|
|
628
628
|
<!-- btn-close: hidden visually, kept for JS event listener -->
|
|
629
629
|
<button id="btn-close" style="display:none" title="Close editor"></button>
|
|
630
|
-
<button class="tb-sim-btn" id="btn-simulate"><i class="bi bi-lightning-charge-fill"></i> Simulate</button>
|
|
630
|
+
<button class="tb-sim-btn" id="btn-simulate" onclick="simulateExperiment()"><i class="bi bi-lightning-charge-fill"></i> Simulate</button>
|
|
631
631
|
<button class="tb-fin-btn" id="btn-save">Finalize</button>
|
|
632
632
|
</div>
|
|
633
633
|
|
|
@@ -850,6 +850,7 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
850
850
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/inputs.js"></script>
|
|
851
851
|
<!-- components.js defines shared colour-class arrays used by bootstrap5/widgets components -->
|
|
852
852
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/components.js"></script>
|
|
853
|
+
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/components-html.js"></script>
|
|
853
854
|
<script>
|
|
854
855
|
/* Safety stub: if components.js didn't define these, create empty arrays so
|
|
855
856
|
components-bootstrap5.js doesn't throw ReferenceError on load. */
|
|
@@ -863,6 +864,18 @@ if (typeof colorSelectOptions === 'undefined') window.colorSelectOptions =
|
|
|
863
864
|
if (typeof textColorSelectOptions=== 'undefined') window.textColorSelectOptions= [];
|
|
864
865
|
if (typeof borderSelectOptions === 'undefined') window.borderSelectOptions = [];
|
|
865
866
|
if (typeof sizeSelectOptions === 'undefined') window.sizeSelectOptions = [];
|
|
867
|
+
if (window.Vvveb && window.Vvveb.Components) {
|
|
868
|
+
if (!window.Vvveb.ComponentsGroup) window.Vvveb.ComponentsGroup = {};
|
|
869
|
+
if (!window.Vvveb.ComponentsGroup['Bootstrap 5']) window.Vvveb.ComponentsGroup['Bootstrap 5'] = [];
|
|
870
|
+
try {
|
|
871
|
+
var baseExists =
|
|
872
|
+
window.Vvveb.Components._components &&
|
|
873
|
+
Object.prototype.hasOwnProperty.call(window.Vvveb.Components._components, '_base');
|
|
874
|
+
if (!baseExists && typeof window.Vvveb.Components.add === 'function') {
|
|
875
|
+
window.Vvveb.Components.add('_base', { name: 'Base', properties: [] });
|
|
876
|
+
}
|
|
877
|
+
} catch(_) {}
|
|
878
|
+
}
|
|
866
879
|
</script>
|
|
867
880
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/components-bootstrap5.js"></script>
|
|
868
881
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/components-widgets.js"></script>
|
|
@@ -901,6 +914,52 @@ function send(type, payload) {
|
|
|
901
914
|
window.parent.postMessage({ channel: CHANNEL, type: type, payload: payload || {} }, '*');
|
|
902
915
|
}
|
|
903
916
|
|
|
917
|
+
function generatePreviewUrlString(args) {
|
|
918
|
+
var baseUrl = (args && args.url) || '';
|
|
919
|
+
var test = (args && args.test) || {};
|
|
920
|
+
var variation = (args && args.variation) || {};
|
|
921
|
+
if (!baseUrl) return '';
|
|
922
|
+
var testId = test.iid || test.experimentId || test._id || '';
|
|
923
|
+
var variationId = variation.iid || variation._id || '';
|
|
924
|
+
var cId = String(testId || '') + '_' + String(variationId || '');
|
|
925
|
+
var hasQueryParams = String(baseUrl).indexOf('?') >= 0;
|
|
926
|
+
return (
|
|
927
|
+
baseUrl +
|
|
928
|
+
(hasQueryParams ? '&' : '?') +
|
|
929
|
+
'codebase_debug=true&cId=' +
|
|
930
|
+
encodeURIComponent(cId)
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function simulateExperiment() {
|
|
935
|
+
var test = experimentData || {};
|
|
936
|
+
var activeVariation = null;
|
|
937
|
+
if (Array.isArray(variations) && variations.length) {
|
|
938
|
+
activeVariation =
|
|
939
|
+
variations.find(function(v) { return v && v._id === activeVarId; }) ||
|
|
940
|
+
variations[0];
|
|
941
|
+
}
|
|
942
|
+
var targetUrl =
|
|
943
|
+
(Array.isArray(test.urltargeting) && test.urltargeting[0]) ||
|
|
944
|
+
test.pageUrl ||
|
|
945
|
+
(test.metadata_1 && test.metadata_1.editor_url) ||
|
|
946
|
+
'';
|
|
947
|
+
var url = generatePreviewUrlString({
|
|
948
|
+
url: targetUrl,
|
|
949
|
+
test: test,
|
|
950
|
+
variation: activeVariation || {},
|
|
951
|
+
});
|
|
952
|
+
if (!url) {
|
|
953
|
+
console.warn('[V2] simulateExperiment: missing target URL');
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
try {
|
|
957
|
+
window.open(url, '_blank');
|
|
958
|
+
} catch(err) {
|
|
959
|
+
console.warn('[V2] simulateExperiment:', err);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
904
963
|
window.addEventListener('message', function(e) {
|
|
905
964
|
if (!e.data || e.data.channel !== CHANNEL) return;
|
|
906
965
|
switch (e.data.type) {
|
|
@@ -914,6 +973,8 @@ var experimentData = null;
|
|
|
914
973
|
var variations = [];
|
|
915
974
|
var activeVarId = null;
|
|
916
975
|
var varHtmlCache = {};
|
|
976
|
+
/** Per-variation chain rows from structural actions (insert/duplicate/delete/reorder/hide), merged on Finalize. */
|
|
977
|
+
var sessionStructuralChainRowsByVarId = {};
|
|
917
978
|
/** Last iframe proxy URL we navigated to \u2014 used to skip redundant reloads when parent re-sends load-experiment */
|
|
918
979
|
var lastLoadedProxyUrl = '';
|
|
919
980
|
/** API changeset rows (excluding __vvveb_body__) reapplied until selectors match late-hydrated DOM */
|
|
@@ -927,6 +988,8 @@ var iframeContentNavGen = 0;
|
|
|
927
988
|
var iframeContentApplyTimer = null;
|
|
928
989
|
var iframeEarlyGranularPrimedForGen = null;
|
|
929
990
|
var iframeEarlySyncPrimedForGen = null;
|
|
991
|
+
/** insert/reorder entries are applied from early granular + full apply \u2014 skip exact duplicates per iframe nav */
|
|
992
|
+
var appliedStructuralChangesetKeys = {};
|
|
930
993
|
var isDirty = false;
|
|
931
994
|
var vvvebReady = false;
|
|
932
995
|
var currentMode = 'editor';
|
|
@@ -946,6 +1009,15 @@ var selectionResizeBound = false;
|
|
|
946
1009
|
var clickAttachDoc = null;
|
|
947
1010
|
var changeObserver = null;
|
|
948
1011
|
var changeObserverDoc = null;
|
|
1012
|
+
/** Incremented while applying changesets / selection chrome so MutationObserver does not mark dirty */
|
|
1013
|
+
var suppressIframeMutationDirty = 0;
|
|
1014
|
+
/** Throttled reconcile timer to keep DOM aligned with saved changesets. */
|
|
1015
|
+
var consistencyReconcileTimer = null;
|
|
1016
|
+
/** Background watchdog for late client-side re-renders that wipe applied changesets. */
|
|
1017
|
+
var consistencyWatchTimer = null;
|
|
1018
|
+
var consistencyWatchDoc = null;
|
|
1019
|
+
var consistencyWatchTicks = 0;
|
|
1020
|
+
var CONSISTENCY_WATCH_MAX_TICKS = 160;
|
|
949
1021
|
/** { doc, onRS } \u2014 iframe document readystate until complete */
|
|
950
1022
|
var iframeDocLoadingListeners = null;
|
|
951
1023
|
// Each entry: {selector, label, cssProp, value, targetEl}
|
|
@@ -953,12 +1025,89 @@ var iframeDocLoadingListeners = null;
|
|
|
953
1025
|
var stateChanges = [];
|
|
954
1026
|
/** Pre-apply DOM snapshots for granular + body changesets (used when removing History rows) */
|
|
955
1027
|
var appliedChangesetSnapshots = {};
|
|
1028
|
+
/** Canonical JSON fingerprints of persisted changesets per variation (last load / finalize) */
|
|
1029
|
+
var baselineChangesetsByVarId = {};
|
|
1030
|
+
|
|
1031
|
+
// \u2500\u2500 Dirty tracking (compare DB baseline + session stateChanges vs current export) \u2500\u2500
|
|
1032
|
+
function beginSuppressIframeMutationDirty() {
|
|
1033
|
+
suppressIframeMutationDirty += 1;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function endSuppressIframeMutationDirty() {
|
|
1037
|
+
suppressIframeMutationDirty = Math.max(0, suppressIframeMutationDirty - 1);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/** Stable stringify of a variation's changesets field (string or array from API). */
|
|
1041
|
+
function fingerprintChangesetsField(raw) {
|
|
1042
|
+
if (raw == null) return '[]';
|
|
1043
|
+
if (Array.isArray(raw)) {
|
|
1044
|
+
try {
|
|
1045
|
+
return JSON.stringify(raw);
|
|
1046
|
+
} catch(_) {
|
|
1047
|
+
return '[]';
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
if (typeof raw !== 'string') return '[]';
|
|
1051
|
+
var s = raw.trim();
|
|
1052
|
+
if (!s) return '[]';
|
|
1053
|
+
try {
|
|
1054
|
+
var p = JSON.parse(s);
|
|
1055
|
+
return JSON.stringify(Array.isArray(p) ? p : []);
|
|
1056
|
+
} catch(_) {
|
|
1057
|
+
return '[]';
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function captureBaselineFromVariations(list) {
|
|
1062
|
+
baselineChangesetsByVarId = {};
|
|
1063
|
+
if (!list || !list.length) return;
|
|
1064
|
+
for (var i = 0; i < list.length; i++) {
|
|
1065
|
+
var v = list[i];
|
|
1066
|
+
if (!v || !v._id) continue;
|
|
1067
|
+
baselineChangesetsByVarId[v._id] = fingerprintChangesetsField(v.changesets);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/** Fingerprint of what Finalize would send for this variation (matches buildPersistedChainSetsForVariation). */
|
|
1072
|
+
function persistedExportFingerprintForVariation(v) {
|
|
1073
|
+
if (!v || !v._id) return '[]';
|
|
1074
|
+
try {
|
|
1075
|
+
var rows = buildPersistedChainSetsForVariation(v);
|
|
1076
|
+
return JSON.stringify(rows || []);
|
|
1077
|
+
} catch(_) {
|
|
1078
|
+
return '[]';
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
function setEditorDirty(dirty) {
|
|
1083
|
+
var was = isDirty;
|
|
1084
|
+
isDirty = !!dirty;
|
|
1085
|
+
var dot = document.getElementById('dirty-dot');
|
|
1086
|
+
if (dot) dot.classList.toggle('on', isDirty);
|
|
1087
|
+
if (isDirty && !was) send('mutations-changed', {});
|
|
1088
|
+
if (!isDirty && was) send('editor-dirty', { dirty: false });
|
|
1089
|
+
if (!isDirty) {
|
|
1090
|
+
savedAt = Date.now();
|
|
1091
|
+
updateSaveTime();
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
956
1094
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1095
|
+
function recomputeEditorDirty() {
|
|
1096
|
+
var d = stateChanges.length > 0;
|
|
1097
|
+
if (!d && variations && variations.length) {
|
|
1098
|
+
for (var i = 0; i < variations.length; i++) {
|
|
1099
|
+
var v = variations[i];
|
|
1100
|
+
var vid = v._id;
|
|
1101
|
+
var cur = persistedExportFingerprintForVariation(v);
|
|
1102
|
+
var base = baselineChangesetsByVarId[vid];
|
|
1103
|
+
if (base == null) base = '[]';
|
|
1104
|
+
if (cur !== base) {
|
|
1105
|
+
d = true;
|
|
1106
|
+
break;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
setEditorDirty(d);
|
|
962
1111
|
}
|
|
963
1112
|
var savedAt = null;
|
|
964
1113
|
function updateSaveTime() {
|
|
@@ -968,12 +1117,6 @@ function updateSaveTime() {
|
|
|
968
1117
|
el.textContent = s < 60 ? s + 's ago' : Math.floor(s / 60) + 'm ago';
|
|
969
1118
|
}
|
|
970
1119
|
setInterval(updateSaveTime, 10000);
|
|
971
|
-
function markClean() {
|
|
972
|
-
isDirty = false;
|
|
973
|
-
savedAt = Date.now();
|
|
974
|
-
document.getElementById('dirty-dot').classList.remove('on');
|
|
975
|
-
updateSaveTime();
|
|
976
|
-
}
|
|
977
1120
|
|
|
978
1121
|
// \u2500\u2500 Mode toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
979
1122
|
function setMode(mode) {
|
|
@@ -1150,6 +1293,7 @@ function logChange(selector, inputId, value, targetEl, originalValue) {
|
|
|
1150
1293
|
if (idx >= 0) { stateChanges[idx] = entry; } else { stateChanges.push(entry); }
|
|
1151
1294
|
}
|
|
1152
1295
|
if (currentMainTab === 'states') renderStatesTab();
|
|
1296
|
+
recomputeEditorDirty();
|
|
1153
1297
|
}
|
|
1154
1298
|
|
|
1155
1299
|
function renderStatesTab() {
|
|
@@ -1183,7 +1327,7 @@ function renderStatesTab() {
|
|
|
1183
1327
|
|
|
1184
1328
|
// Resolve a live DOM element for a state-change entry.
|
|
1185
1329
|
// Tries the stored direct reference first; if it's detached or missing,
|
|
1186
|
-
// falls back to querySelector(
|
|
1330
|
+
// falls back to querySelector (with .vve-* class stripped) inside the iframe document.
|
|
1187
1331
|
function resolveChangeEl(change) {
|
|
1188
1332
|
try {
|
|
1189
1333
|
var iframeDoc = document.getElementById('iframeId').contentDocument;
|
|
@@ -1193,7 +1337,7 @@ function resolveChangeEl(change) {
|
|
|
1193
1337
|
return change.targetEl;
|
|
1194
1338
|
}
|
|
1195
1339
|
// Fallback: re-query by the stored CSS selector
|
|
1196
|
-
return iframeDoc
|
|
1340
|
+
return querySelectorResolved(iframeDoc, change.selector);
|
|
1197
1341
|
} catch (e) {
|
|
1198
1342
|
console.warn('[V2] resolveChangeEl:', e);
|
|
1199
1343
|
return null;
|
|
@@ -1256,7 +1400,7 @@ function removeStateChange(idx) {
|
|
|
1256
1400
|
syncDesignInput(change);
|
|
1257
1401
|
stateChanges.splice(idx, 1);
|
|
1258
1402
|
renderStatesTab();
|
|
1259
|
-
|
|
1403
|
+
recomputeEditorDirty();
|
|
1260
1404
|
}
|
|
1261
1405
|
|
|
1262
1406
|
function clearAllStates() {
|
|
@@ -1266,7 +1410,7 @@ function clearAllStates() {
|
|
|
1266
1410
|
});
|
|
1267
1411
|
stateChanges = [];
|
|
1268
1412
|
renderStatesTab();
|
|
1269
|
-
|
|
1413
|
+
recomputeEditorDirty();
|
|
1270
1414
|
}
|
|
1271
1415
|
|
|
1272
1416
|
// \u2500\u2500 History tab (saved changesets from DB for active variation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -1291,10 +1435,11 @@ function persistActiveVariationChangesets(arr) {
|
|
|
1291
1435
|
|
|
1292
1436
|
function entrySnapshotKey(entry) {
|
|
1293
1437
|
if (!entry || !entry.selector) return '';
|
|
1438
|
+
var selKey = sanitizeSelectorForMatch(entry.selector) || entry.selector;
|
|
1294
1439
|
return (
|
|
1295
|
-
|
|
1440
|
+
selKey +
|
|
1296
1441
|
'\0' +
|
|
1297
|
-
(entry
|
|
1442
|
+
normalizeChangesetType(entry) +
|
|
1298
1443
|
'\0' +
|
|
1299
1444
|
String(entry.property || '') +
|
|
1300
1445
|
'\0' +
|
|
@@ -1312,7 +1457,7 @@ function captureChangesetSnapshotBeforeApply(entry, el, iframeDoc) {
|
|
|
1312
1457
|
if (!entry || !el || entry.selector === '__vvveb_body__') return;
|
|
1313
1458
|
var k = entrySnapshotKey(entry);
|
|
1314
1459
|
if (appliedChangesetSnapshots[k]) return;
|
|
1315
|
-
switch (entry
|
|
1460
|
+
switch (normalizeChangesetType(entry)) {
|
|
1316
1461
|
case 'content':
|
|
1317
1462
|
if (entry.html != null) {
|
|
1318
1463
|
appliedChangesetSnapshots[k] = { kind: 'innerHTML', v: el.innerHTML };
|
|
@@ -1380,10 +1525,7 @@ function revertChangesetEntryOnDom(entry) {
|
|
|
1380
1525
|
if (!iframeDoc) return false;
|
|
1381
1526
|
var k = entrySnapshotKey(entry);
|
|
1382
1527
|
var snap = appliedChangesetSnapshots[k];
|
|
1383
|
-
var el =
|
|
1384
|
-
try {
|
|
1385
|
-
el = iframeDoc.querySelector(entry.selector);
|
|
1386
|
-
} catch(_) {}
|
|
1528
|
+
var el = querySelectorResolved(iframeDoc, entry.selector);
|
|
1387
1529
|
if (!snap || !el) {
|
|
1388
1530
|
softReloadEditorIframe();
|
|
1389
1531
|
delete appliedChangesetSnapshots[k];
|
|
@@ -1410,7 +1552,7 @@ function revertChangesetEntryOnDom(entry) {
|
|
|
1410
1552
|
function historyEntryTypeLabel(entry) {
|
|
1411
1553
|
if (!entry) return 'Change';
|
|
1412
1554
|
if (entry.selector === '__vvveb_body__') return 'Full page HTML';
|
|
1413
|
-
var t = (entry
|
|
1555
|
+
var t = normalizeChangesetType(entry);
|
|
1414
1556
|
if (t === 'content') return entry.html != null ? 'Inner HTML' : 'Text / content';
|
|
1415
1557
|
if (t === 'style') return 'Style: ' + (entry.property || '');
|
|
1416
1558
|
if (t === 'attribute') return 'Attribute: ' + (entry.attribute || '');
|
|
@@ -1424,7 +1566,8 @@ function historyEntryValuePreview(entry) {
|
|
|
1424
1566
|
if (entry.selector === '__vvveb_body__') return '(body snapshot)';
|
|
1425
1567
|
if (entry.html != null) return String(entry.html).slice(0, 120);
|
|
1426
1568
|
if (entry.value != null) return String(entry.value).slice(0, 120);
|
|
1427
|
-
|
|
1569
|
+
var nt = normalizeChangesetType(entry);
|
|
1570
|
+
if (nt === 'style' || nt === 'attribute') return String(entry.value != null ? entry.value : '').slice(0, 120);
|
|
1428
1571
|
return '';
|
|
1429
1572
|
}
|
|
1430
1573
|
|
|
@@ -1458,6 +1601,9 @@ function renderHistoryTab() {
|
|
|
1458
1601
|
var val = historyEntryValuePreview(item.entry);
|
|
1459
1602
|
html +=
|
|
1460
1603
|
'<div class="state-item">' +
|
|
1604
|
+
'<span class="state-item-idx" style="opacity:0.55;font-size:10px;min-width:2.2em;display:inline-block" title="Order in saved changesets">#' +
|
|
1605
|
+
item.idx +
|
|
1606
|
+
'</span>' +
|
|
1461
1607
|
'<span class="state-item-label">' +
|
|
1462
1608
|
esc(lab) +
|
|
1463
1609
|
'</span>' +
|
|
@@ -1466,7 +1612,9 @@ function renderHistoryTab() {
|
|
|
1466
1612
|
'">' +
|
|
1467
1613
|
esc(val) +
|
|
1468
1614
|
'</span>' +
|
|
1469
|
-
'<button type="button" class="state-remove" title="Remove
|
|
1615
|
+
'<button type="button" class="state-remove" title="Remove this saved row (#' +
|
|
1616
|
+
item.idx +
|
|
1617
|
+
')" onclick="removeHistoryChangeset(' +
|
|
1470
1618
|
item.idx +
|
|
1471
1619
|
')">✕</button>' +
|
|
1472
1620
|
'</div>';
|
|
@@ -1476,6 +1624,16 @@ function renderHistoryTab() {
|
|
|
1476
1624
|
container.innerHTML = html;
|
|
1477
1625
|
}
|
|
1478
1626
|
|
|
1627
|
+
function changesetListHasStructural(arr) {
|
|
1628
|
+
if (!arr || !arr.length) return false;
|
|
1629
|
+
for (var i = 0; i < arr.length; i++) {
|
|
1630
|
+
var e = arr[i];
|
|
1631
|
+
var t = normalizeChangesetType(e);
|
|
1632
|
+
if (e && (t === 'insert' || t === 'reorder')) return true;
|
|
1633
|
+
}
|
|
1634
|
+
return false;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1479
1637
|
function removeHistoryChangeset(idx) {
|
|
1480
1638
|
var v = getActiveVariationForHistory();
|
|
1481
1639
|
if (!v) return;
|
|
@@ -1488,18 +1646,31 @@ function removeHistoryChangeset(idx) {
|
|
|
1488
1646
|
try {
|
|
1489
1647
|
delete varHtmlCache[activeVarId];
|
|
1490
1648
|
} catch(_) {}
|
|
1491
|
-
|
|
1649
|
+
// Re-applying remaining rows on top of current DOM duplicates insert/reorder nodes; reload when any
|
|
1650
|
+
// structural row remains or was removed (revert may already have started a reload for insert/body).
|
|
1651
|
+
var removedType = normalizeChangesetType(removed);
|
|
1652
|
+
var needsStructuralReload =
|
|
1653
|
+
!didReload &&
|
|
1654
|
+
(removedType === 'insert' ||
|
|
1655
|
+
removedType === 'reorder' ||
|
|
1656
|
+
changesetListHasStructural(arr));
|
|
1657
|
+
if (didReload) {
|
|
1658
|
+
/* revertChangesetEntryOnDom already kicked off iframe reload */
|
|
1659
|
+
} else if (needsStructuralReload) {
|
|
1660
|
+
softReloadEditorIframe();
|
|
1661
|
+
} else {
|
|
1492
1662
|
try {
|
|
1663
|
+
appliedStructuralChangesetKeys = {};
|
|
1493
1664
|
applyActiveVariationHtml();
|
|
1494
1665
|
registerPendingGranularChangesets(
|
|
1495
1666
|
arr,
|
|
1496
1667
|
document.getElementById('iframeId').contentDocument,
|
|
1497
1668
|
);
|
|
1669
|
+
saveCurrentVariationHtml();
|
|
1498
1670
|
} catch(_) {}
|
|
1499
|
-
saveCurrentVariationHtml();
|
|
1500
1671
|
}
|
|
1501
1672
|
if (currentMainTab === 'history') renderHistoryTab();
|
|
1502
|
-
|
|
1673
|
+
recomputeEditorDirty();
|
|
1503
1674
|
scheduleDomTreeRefresh();
|
|
1504
1675
|
}
|
|
1505
1676
|
|
|
@@ -1509,15 +1680,82 @@ function clearAllHistoryChangesets() {
|
|
|
1509
1680
|
if (!parseVariationChangesets(v).length) return;
|
|
1510
1681
|
persistActiveVariationChangesets([]);
|
|
1511
1682
|
appliedChangesetSnapshots = {};
|
|
1683
|
+
appliedStructuralChangesetKeys = {};
|
|
1512
1684
|
try {
|
|
1513
1685
|
delete varHtmlCache[activeVarId];
|
|
1514
1686
|
} catch(_) {}
|
|
1515
1687
|
if (currentMainTab === 'history') renderHistoryTab();
|
|
1516
|
-
|
|
1688
|
+
recomputeEditorDirty();
|
|
1517
1689
|
scheduleDomTreeRefresh();
|
|
1518
1690
|
softReloadEditorIframe();
|
|
1519
1691
|
}
|
|
1520
1692
|
|
|
1693
|
+
// \u2500\u2500 Persisted active variation (survives iframe / full page reload) \u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1694
|
+
/** All Visual Editor iframe keys in localStorage use this prefix \u2014 cleared on close. */
|
|
1695
|
+
var VVE_LOCAL_STORAGE_PREFIX = 'vve:';
|
|
1696
|
+
|
|
1697
|
+
function clearVisualEditorLocalStorage() {
|
|
1698
|
+
try {
|
|
1699
|
+
for (var i = localStorage.length - 1; i >= 0; i--) {
|
|
1700
|
+
var k = localStorage.key(i);
|
|
1701
|
+
if (k && k.indexOf(VVE_LOCAL_STORAGE_PREFIX) === 0) {
|
|
1702
|
+
localStorage.removeItem(k);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
} catch(_) {}
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
function activeVariationStorageKeyFromPayload(data) {
|
|
1709
|
+
return (
|
|
1710
|
+
VVE_LOCAL_STORAGE_PREFIX +
|
|
1711
|
+
'activeVar:' +
|
|
1712
|
+
String((data && data.experimentId) || '') +
|
|
1713
|
+
':' +
|
|
1714
|
+
String((data && data.pageUrl) || '')
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
function readPersistedActiveVariationId(data) {
|
|
1719
|
+
try {
|
|
1720
|
+
var sk = activeVariationStorageKeyFromPayload(data);
|
|
1721
|
+
if (sk === VVE_LOCAL_STORAGE_PREFIX + 'activeVar::') return null;
|
|
1722
|
+
return localStorage.getItem(sk);
|
|
1723
|
+
} catch(_) {
|
|
1724
|
+
return null;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
function writePersistedActiveVariationId(varId) {
|
|
1729
|
+
try {
|
|
1730
|
+
if (!experimentData || !experimentData.experimentId) return;
|
|
1731
|
+
var sk =
|
|
1732
|
+
VVE_LOCAL_STORAGE_PREFIX +
|
|
1733
|
+
'activeVar:' +
|
|
1734
|
+
String(experimentData.experimentId || '') +
|
|
1735
|
+
':' +
|
|
1736
|
+
String(experimentData.pageUrl || '');
|
|
1737
|
+
if (sk === VVE_LOCAL_STORAGE_PREFIX + 'activeVar::') return;
|
|
1738
|
+
if (varId) localStorage.setItem(sk, String(varId));
|
|
1739
|
+
} catch(_) {}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
/**
|
|
1743
|
+
* @param allowPrevMemory when true, keep in-session activeVarId if still valid (skip-reload path).
|
|
1744
|
+
*/
|
|
1745
|
+
function pickActiveVariationIdForLoad(data, variationsArr, prevMemoryId, allowPrevMemory) {
|
|
1746
|
+
var baseline = variationsArr.find(function(v) { return v.baseline; });
|
|
1747
|
+
var fallback = (baseline || variationsArr[0] || {})._id || null;
|
|
1748
|
+
if (!variationsArr.length) return null;
|
|
1749
|
+
if (allowPrevMemory && prevMemoryId && variationsArr.some(function(v) { return v._id === prevMemoryId; })) {
|
|
1750
|
+
return prevMemoryId;
|
|
1751
|
+
}
|
|
1752
|
+
var stored = readPersistedActiveVariationId(data);
|
|
1753
|
+
if (stored && variationsArr.some(function(v) { return v._id === stored; })) {
|
|
1754
|
+
return stored;
|
|
1755
|
+
}
|
|
1756
|
+
return fallback;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1521
1759
|
// \u2500\u2500 Experiment loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1522
1760
|
function handleLoadExperiment(data) {
|
|
1523
1761
|
clearPendingGranularChangesets();
|
|
@@ -1545,10 +1783,8 @@ function handleLoadExperiment(data) {
|
|
|
1545
1783
|
experimentData = data;
|
|
1546
1784
|
variations = Array.isArray(data.variations) ? data.variations : [];
|
|
1547
1785
|
var prevActive = activeVarId;
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
activeVarId =
|
|
1551
|
-
prevActive && variations.some(function(v) { return v._id === prevActive; }) ? prevActive : fallback;
|
|
1786
|
+
activeVarId = pickActiveVariationIdForLoad(data, variations, prevActive, true);
|
|
1787
|
+
writePersistedActiveVariationId(activeVarId);
|
|
1552
1788
|
renderVariationTabs();
|
|
1553
1789
|
var urlBarSkip = document.getElementById('url-bar');
|
|
1554
1790
|
urlBarSkip.textContent = pageUrl;
|
|
@@ -1562,24 +1798,30 @@ function handleLoadExperiment(data) {
|
|
|
1562
1798
|
if (ifrSkip && ifrSkip.contentDocument) attachIframeLoadingUntilComplete(ifrSkip);
|
|
1563
1799
|
} catch(_) {}
|
|
1564
1800
|
if (currentMainTab === 'history') renderHistoryTab();
|
|
1801
|
+
captureBaselineFromVariations(variations);
|
|
1802
|
+
recomputeEditorDirty();
|
|
1565
1803
|
return;
|
|
1566
1804
|
}
|
|
1567
1805
|
|
|
1568
1806
|
if (!experimentData || prevKey !== nextKey) {
|
|
1569
1807
|
varHtmlCache = {};
|
|
1808
|
+
sessionStructuralChainRowsByVarId = {};
|
|
1570
1809
|
appliedChangesetSnapshots = {};
|
|
1810
|
+
appliedStructuralChangesetKeys = {};
|
|
1571
1811
|
}
|
|
1572
1812
|
experimentData = data;
|
|
1573
1813
|
variations = Array.isArray(data.variations) ? data.variations : [];
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
activeVarId
|
|
1814
|
+
var sameExpPage = prevKey === nextKey;
|
|
1815
|
+
activeVarId = pickActiveVariationIdForLoad(data, variations, activeVarId, sameExpPage);
|
|
1816
|
+
writePersistedActiveVariationId(activeVarId);
|
|
1577
1817
|
renderVariationTabs();
|
|
1578
1818
|
|
|
1579
1819
|
var urlBar = document.getElementById('url-bar');
|
|
1580
1820
|
urlBar.textContent = pageUrl;
|
|
1581
1821
|
urlBar.title = pageUrl;
|
|
1582
1822
|
if (currentMainTab === 'history') renderHistoryTab();
|
|
1823
|
+
captureBaselineFromVariations(variations);
|
|
1824
|
+
recomputeEditorDirty();
|
|
1583
1825
|
loadPage(proxyUrl);
|
|
1584
1826
|
}
|
|
1585
1827
|
|
|
@@ -1679,9 +1921,7 @@ function granularAnySelectorMatches(doc, cs) {
|
|
|
1679
1921
|
if (!doc || !cs || !cs.length) return false;
|
|
1680
1922
|
var g = filterGranularChangesetEntries(cs);
|
|
1681
1923
|
for (var i = 0; i < g.length; i++) {
|
|
1682
|
-
|
|
1683
|
-
if (doc.querySelector(g[i].selector)) return true;
|
|
1684
|
-
} catch(_) {}
|
|
1924
|
+
if (querySelectorResolved(doc, g[i].selector)) return true;
|
|
1685
1925
|
}
|
|
1686
1926
|
return false;
|
|
1687
1927
|
}
|
|
@@ -1702,7 +1942,7 @@ function appendIframeReloadBust(url) {
|
|
|
1702
1942
|
// applying variation changesets there is then wiped when the real navigation commits.
|
|
1703
1943
|
function iframeDocMatchesNavigatedSrc(iframe, doc) {
|
|
1704
1944
|
if (!iframe || !doc) return false;
|
|
1705
|
-
var src = iframe.src || iframe.
|
|
1945
|
+
var src = iframe.getAttribute('src') || iframe.src || '';
|
|
1706
1946
|
if (!src || src === 'about:blank') return false;
|
|
1707
1947
|
var loc = '';
|
|
1708
1948
|
try {
|
|
@@ -1711,12 +1951,28 @@ function iframeDocMatchesNavigatedSrc(iframe, doc) {
|
|
|
1711
1951
|
return false;
|
|
1712
1952
|
}
|
|
1713
1953
|
if (!loc || loc === 'about:blank') return false;
|
|
1714
|
-
var rmSrc = src.match(/__ve_reload=([0-9]+)/);
|
|
1715
|
-
if (rmSrc) return loc.indexOf('__ve_reload=' + rmSrc[1]) !== -1;
|
|
1716
1954
|
try {
|
|
1717
1955
|
var base = window.location.href;
|
|
1718
1956
|
var su = new URL(src, base);
|
|
1957
|
+
if (su.searchParams && su.searchParams.has('__ve_reload')) {
|
|
1958
|
+
su.searchParams.delete('__ve_reload');
|
|
1959
|
+
}
|
|
1719
1960
|
var du = new URL(loc, base);
|
|
1961
|
+
if (du.searchParams && du.searchParams.has('__ve_reload')) {
|
|
1962
|
+
du.searchParams.delete('__ve_reload');
|
|
1963
|
+
}
|
|
1964
|
+
// Same-origin proxy that keeps document address aligned with iframe src
|
|
1965
|
+
if (su.origin === du.origin && su.pathname + su.search === du.pathname + du.search) {
|
|
1966
|
+
return true;
|
|
1967
|
+
}
|
|
1968
|
+
// conversion-proxy: iframe src stays on our app, but doc.URL is usually the target site after redirects
|
|
1969
|
+
var p = su.pathname || '';
|
|
1970
|
+
var isRootProxyPath = p === '/api/conversion-proxy' || p.indexOf('/api/conversion-proxy/') === 0;
|
|
1971
|
+
var isNestedMalformedProxy = !isRootProxyPath && p.indexOf('api/conversion-proxy') !== -1;
|
|
1972
|
+
if (isNestedMalformedProxy) return false;
|
|
1973
|
+
if (isRootProxyPath || String(su.href).indexOf('conversion-proxy') !== -1) {
|
|
1974
|
+
return doc === iframe.contentDocument;
|
|
1975
|
+
}
|
|
1720
1976
|
return su.pathname + su.search === du.pathname + du.search;
|
|
1721
1977
|
} catch(_) {
|
|
1722
1978
|
return false;
|
|
@@ -1745,10 +2001,73 @@ function clearPendingGranularChangesets() {
|
|
|
1745
2001
|
}
|
|
1746
2002
|
}
|
|
1747
2003
|
|
|
2004
|
+
function stopConsistencyWatchdog() {
|
|
2005
|
+
if (consistencyWatchTimer) {
|
|
2006
|
+
clearInterval(consistencyWatchTimer);
|
|
2007
|
+
consistencyWatchTimer = null;
|
|
2008
|
+
}
|
|
2009
|
+
consistencyWatchDoc = null;
|
|
2010
|
+
consistencyWatchTicks = 0;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
function runConsistencyReconcile() {
|
|
2014
|
+
if (!activeVarId) return;
|
|
2015
|
+
var iframe = document.getElementById('iframeId');
|
|
2016
|
+
var doc = iframe && iframe.contentDocument;
|
|
2017
|
+
if (!doc || !doc.body) return;
|
|
2018
|
+
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2019
|
+
var cs = parseVariationChangesets(variation);
|
|
2020
|
+
if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
|
|
2021
|
+
var granular = filterGranularChangesetEntries(cs);
|
|
2022
|
+
var unresolved = countUnresolvedGranularSelectors(doc, granular);
|
|
2023
|
+
if (unresolved > 0 || changesetListHasStructural(cs)) {
|
|
2024
|
+
reapplyActiveVariationGranular(doc);
|
|
2025
|
+
registerPendingGranularChangesets(cs, doc);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
function scheduleConsistencyReconcile() {
|
|
2030
|
+
if (consistencyReconcileTimer) return;
|
|
2031
|
+
consistencyReconcileTimer = setTimeout(function() {
|
|
2032
|
+
consistencyReconcileTimer = null;
|
|
2033
|
+
runConsistencyReconcile();
|
|
2034
|
+
}, 80);
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
function startConsistencyWatchdog(doc) {
|
|
2038
|
+
if (!doc || !doc.body) return;
|
|
2039
|
+
if (consistencyWatchDoc === doc && consistencyWatchTimer) return;
|
|
2040
|
+
stopConsistencyWatchdog();
|
|
2041
|
+
consistencyWatchDoc = doc;
|
|
2042
|
+
consistencyWatchTicks = 0;
|
|
2043
|
+
consistencyWatchTimer = setInterval(function() {
|
|
2044
|
+
if (consistencyWatchDoc !== doc) {
|
|
2045
|
+
stopConsistencyWatchdog();
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
var iframe = document.getElementById('iframeId');
|
|
2049
|
+
if (!iframe || iframe.contentDocument !== doc || !doc.body) {
|
|
2050
|
+
stopConsistencyWatchdog();
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
consistencyWatchTicks += 1;
|
|
2054
|
+
scheduleConsistencyReconcile();
|
|
2055
|
+
if (consistencyWatchTicks >= CONSISTENCY_WATCH_MAX_TICKS) {
|
|
2056
|
+
stopConsistencyWatchdog();
|
|
2057
|
+
}
|
|
2058
|
+
}, 400);
|
|
2059
|
+
}
|
|
2060
|
+
|
|
1748
2061
|
function resetIframeBindings() {
|
|
1749
2062
|
detachIframeLoadingListeners();
|
|
1750
2063
|
stopIframeContentApplyWatcher();
|
|
2064
|
+
stopConsistencyWatchdog();
|
|
2065
|
+
if (consistencyReconcileTimer) {
|
|
2066
|
+
clearTimeout(consistencyReconcileTimer);
|
|
2067
|
+
consistencyReconcileTimer = null;
|
|
2068
|
+
}
|
|
1751
2069
|
appliedChangesetSnapshots = {};
|
|
2070
|
+
appliedStructuralChangesetKeys = {};
|
|
1752
2071
|
clickAttachDoc = null;
|
|
1753
2072
|
dragAttachDoc = null;
|
|
1754
2073
|
changeObserverDoc = null;
|
|
@@ -1819,13 +2138,19 @@ function switchVariation(varId) {
|
|
|
1819
2138
|
saveCurrentVariationHtml();
|
|
1820
2139
|
clearPendingGranularChangesets();
|
|
1821
2140
|
activeVarId = varId;
|
|
2141
|
+
writePersistedActiveVariationId(varId);
|
|
1822
2142
|
renderVariationTabs();
|
|
1823
2143
|
deselectElement();
|
|
1824
2144
|
try {
|
|
1825
2145
|
var iframe = document.getElementById('iframeId');
|
|
1826
2146
|
var saved = varHtmlCache[varId];
|
|
1827
2147
|
if (saved) {
|
|
1828
|
-
|
|
2148
|
+
beginSuppressIframeMutationDirty();
|
|
2149
|
+
try {
|
|
2150
|
+
iframe.contentDocument.body.innerHTML = saved;
|
|
2151
|
+
} finally {
|
|
2152
|
+
endSuppressIframeMutationDirty();
|
|
2153
|
+
}
|
|
1829
2154
|
detachIframeLoadingListeners();
|
|
1830
2155
|
setIframePageLoadingUi(false);
|
|
1831
2156
|
syncIframeInteractions('switch-variation-cache');
|
|
@@ -1848,6 +2173,7 @@ function switchVariation(varId) {
|
|
|
1848
2173
|
}
|
|
1849
2174
|
} catch(_) {}
|
|
1850
2175
|
if (currentMainTab === 'history') renderHistoryTab();
|
|
2176
|
+
recomputeEditorDirty();
|
|
1851
2177
|
}
|
|
1852
2178
|
|
|
1853
2179
|
function saveCurrentVariationHtml() {
|
|
@@ -1886,31 +2212,123 @@ function parseVariationChangesets(variation) {
|
|
|
1886
2212
|
}
|
|
1887
2213
|
}
|
|
1888
2214
|
|
|
2215
|
+
/** Lowercase entry.type so persisted / API rows match switches (e.g. Insert vs insert). */
|
|
2216
|
+
function normalizeChangesetType(entry) {
|
|
2217
|
+
return String(entry && entry.type != null ? entry.type : '').toLowerCase();
|
|
2218
|
+
}
|
|
2219
|
+
|
|
1889
2220
|
function filterGranularChangesetEntries(cs) {
|
|
1890
2221
|
if (!cs || !cs.length) return [];
|
|
1891
2222
|
var out = [];
|
|
1892
2223
|
for (var i = 0; i < cs.length; i++) {
|
|
1893
2224
|
var e = cs[i];
|
|
1894
|
-
if (e
|
|
2225
|
+
if (!e || !e.selector || e.selector === '__vvveb_body__') continue;
|
|
2226
|
+
out.push(e);
|
|
2227
|
+
}
|
|
2228
|
+
return out;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
/** Dedup key for merging chain-set rows (overlay wins over base). */
|
|
2232
|
+
function chainSetDedupKey(entry) {
|
|
2233
|
+
if (!entry || !entry.selector) return '';
|
|
2234
|
+
var t = normalizeChangesetType(entry);
|
|
2235
|
+
if (t === 'style') return entry.selector + '|s|' + String(entry.property || '');
|
|
2236
|
+
if (t === 'content') return entry.selector + '|c|' + (entry.html != null ? 'h' : 't');
|
|
2237
|
+
if (t === 'attribute') return entry.selector + '|a|' + String(entry.attribute || '');
|
|
2238
|
+
if (t === 'insert') return entry.selector + '|i|' + String(entry.action || '') + '|' + String(entry.html || '').slice(0, 120);
|
|
2239
|
+
if (t === 'remove') return entry.selector + '|r|';
|
|
2240
|
+
if (t === 'reorder') {
|
|
2241
|
+
return entry.selector + '|ro|' + String(entry.targetSelector || '') + '|' + String(entry.action || '');
|
|
2242
|
+
}
|
|
2243
|
+
try {
|
|
2244
|
+
return entry.selector + '|' + t + '|' + JSON.stringify(entry);
|
|
2245
|
+
} catch(_) {
|
|
2246
|
+
return entry.selector + '|' + t;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
function mergeGranularChainSets(baseList, overlayList) {
|
|
2251
|
+
var map = {};
|
|
2252
|
+
var order = [];
|
|
2253
|
+
function ingest(arr) {
|
|
2254
|
+
if (!arr || !arr.length) return;
|
|
2255
|
+
for (var i = 0; i < arr.length; i++) {
|
|
2256
|
+
var e = arr[i];
|
|
2257
|
+
if (!e || !e.selector) continue;
|
|
2258
|
+
var k = chainSetDedupKey(e);
|
|
2259
|
+
if (!map[k]) order.push(k);
|
|
2260
|
+
map[k] = e;
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
ingest(baseList);
|
|
2264
|
+
ingest(overlayList);
|
|
2265
|
+
var out = [];
|
|
2266
|
+
for (var j = 0; j < order.length; j++) {
|
|
2267
|
+
out.push(map[order[j]]);
|
|
1895
2268
|
}
|
|
1896
2269
|
return out;
|
|
1897
2270
|
}
|
|
1898
2271
|
|
|
2272
|
+
function appendSessionStructuralChainRow(varId, row) {
|
|
2273
|
+
if (!varId || !row) return;
|
|
2274
|
+
if (!sessionStructuralChainRowsByVarId[varId]) sessionStructuralChainRowsByVarId[varId] = [];
|
|
2275
|
+
sessionStructuralChainRowsByVarId[varId].push(row);
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
/** One States-tab row -> Conversion.io chain-set shape (matches applyChangesetEntry). */
|
|
2279
|
+
function stateChangeToChainSet(c) {
|
|
2280
|
+
if (!c || !c.selector) return null;
|
|
2281
|
+
if (c.cssProp) {
|
|
2282
|
+
return { selector: c.selector, type: 'style', property: c.cssProp, value: c.value };
|
|
2283
|
+
}
|
|
2284
|
+
switch (c.inputId) {
|
|
2285
|
+
case 'pp-text':
|
|
2286
|
+
return { selector: c.selector, type: 'content', value: c.value };
|
|
2287
|
+
case 'pp-html':
|
|
2288
|
+
return { selector: c.selector, type: 'content', html: c.value };
|
|
2289
|
+
case 'pp-cls':
|
|
2290
|
+
return { selector: c.selector, type: 'attribute', attribute: 'class', value: c.value };
|
|
2291
|
+
case 'pp-id':
|
|
2292
|
+
return { selector: c.selector, type: 'attribute', attribute: 'id', value: c.value };
|
|
2293
|
+
case 'pp-href':
|
|
2294
|
+
return { selector: c.selector, type: 'attribute', attribute: 'href', value: c.value };
|
|
2295
|
+
case 'pp-target':
|
|
2296
|
+
return { selector: c.selector, type: 'attribute', attribute: 'target', value: c.value };
|
|
2297
|
+
case 'pp-src':
|
|
2298
|
+
return { selector: c.selector, type: 'attribute', attribute: 'src', value: c.value };
|
|
2299
|
+
case 'pp-alt':
|
|
2300
|
+
return { selector: c.selector, type: 'attribute', attribute: 'alt', value: c.value };
|
|
2301
|
+
case 'pp-ph':
|
|
2302
|
+
return { selector: c.selector, type: 'attribute', attribute: 'placeholder', value: c.value };
|
|
2303
|
+
case 'pp-css':
|
|
2304
|
+
return { selector: c.selector, type: 'attribute', attribute: 'style', value: c.value };
|
|
2305
|
+
case 'pp-mob-css':
|
|
2306
|
+
return { selector: c.selector, type: 'attribute', attribute: 'data-mobile-css', value: c.value };
|
|
2307
|
+
case 'pp-tab-css':
|
|
2308
|
+
return { selector: c.selector, type: 'attribute', attribute: 'data-tablet-css', value: c.value };
|
|
2309
|
+
default:
|
|
2310
|
+
return null;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
|
|
1899
2314
|
function countUnresolvedGranularSelectors(iframeDoc, entries) {
|
|
1900
2315
|
if (!iframeDoc || !entries || !entries.length) return 0;
|
|
1901
2316
|
var n = 0;
|
|
1902
2317
|
for (var i = 0; i < entries.length; i++) {
|
|
1903
|
-
|
|
1904
|
-
try { el = iframeDoc.querySelector(entries[i].selector); } catch(_) {}
|
|
1905
|
-
if (!el) n++;
|
|
2318
|
+
if (!querySelectorResolved(iframeDoc, entries[i].selector)) n++;
|
|
1906
2319
|
}
|
|
1907
2320
|
return n;
|
|
1908
2321
|
}
|
|
1909
2322
|
|
|
1910
2323
|
function applyGranularChangesetEntries(iframeDoc, entries) {
|
|
1911
2324
|
if (!iframeDoc || !entries || !entries.length) return;
|
|
1912
|
-
|
|
1913
|
-
|
|
2325
|
+
beginSuppressIframeMutationDirty();
|
|
2326
|
+
try {
|
|
2327
|
+
for (var i = 0; i < entries.length; i++) {
|
|
2328
|
+
applyChangesetEntry(entries[i], iframeDoc);
|
|
2329
|
+
}
|
|
2330
|
+
} finally {
|
|
2331
|
+
endSuppressIframeMutationDirty();
|
|
1914
2332
|
}
|
|
1915
2333
|
}
|
|
1916
2334
|
|
|
@@ -1965,6 +2383,30 @@ function flushPendingGranularChangesets() {
|
|
|
1965
2383
|
scheduleGranularChangesetReapply();
|
|
1966
2384
|
}
|
|
1967
2385
|
|
|
2386
|
+
/** Stable key for structural changesets (insert/reorder) to avoid double-apply across early + full paint. */
|
|
2387
|
+
function structuralChangesetDedupKey(entry) {
|
|
2388
|
+
var nt = normalizeChangesetType(entry);
|
|
2389
|
+
if (!entry || (nt !== 'insert' && nt !== 'reorder')) return '';
|
|
2390
|
+
var vid = activeVarId || '';
|
|
2391
|
+
try {
|
|
2392
|
+
return (
|
|
2393
|
+
vid +
|
|
2394
|
+
'\0' +
|
|
2395
|
+
nt +
|
|
2396
|
+
'\0' +
|
|
2397
|
+
entry.selector +
|
|
2398
|
+
'\0' +
|
|
2399
|
+
String(entry.action || '') +
|
|
2400
|
+
'\0' +
|
|
2401
|
+
String(entry.html != null ? entry.html : '').slice(0, 240) +
|
|
2402
|
+
'\0' +
|
|
2403
|
+
String(entry.targetSelector || '')
|
|
2404
|
+
);
|
|
2405
|
+
} catch(_) {
|
|
2406
|
+
return vid + '\0' + nt + '\0' + entry.selector;
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
|
|
1968
2410
|
/**
|
|
1969
2411
|
* Apply a single changeset entry inside the editor iframe.
|
|
1970
2412
|
* Backend format: { selector, type: 'content'|'style'|'attribute'|'insert'|'remove', ... }
|
|
@@ -1984,21 +2426,34 @@ function applyChangesetEntry(entry, iframeDoc) {
|
|
|
1984
2426
|
return;
|
|
1985
2427
|
}
|
|
1986
2428
|
|
|
2429
|
+
var structuralDedupeKey = structuralChangesetDedupKey(entry);
|
|
2430
|
+
if (structuralDedupeKey && appliedStructuralChangesetKeys[structuralDedupeKey]) return;
|
|
2431
|
+
|
|
1987
2432
|
// \u2500\u2500 Standard granular changeset \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1988
|
-
var el =
|
|
1989
|
-
try { el = iframeDoc.querySelector(entry.selector); } catch(_) {}
|
|
2433
|
+
var el = querySelectorResolved(iframeDoc, entry.selector);
|
|
1990
2434
|
if (!el) return;
|
|
1991
2435
|
|
|
1992
2436
|
captureChangesetSnapshotBeforeApply(entry, el, iframeDoc);
|
|
1993
2437
|
|
|
1994
|
-
switch (entry
|
|
2438
|
+
switch (normalizeChangesetType(entry)) {
|
|
1995
2439
|
case 'content':
|
|
1996
2440
|
if (entry.html != null) el.innerHTML = entry.html;
|
|
1997
2441
|
else if (entry.value != null) el.textContent = entry.value;
|
|
1998
2442
|
break;
|
|
1999
2443
|
case 'style':
|
|
2000
|
-
if (entry.property
|
|
2001
|
-
|
|
2444
|
+
if (entry.property) {
|
|
2445
|
+
var propKebab = entry.property;
|
|
2446
|
+
var cam = camelize(propKebab);
|
|
2447
|
+
if (entry.value == null || entry.value === '') {
|
|
2448
|
+
try { el.style.removeProperty(propKebab); } catch(_) {}
|
|
2449
|
+
try { if (cam in el.style) el.style[cam] = ''; } catch(__) {}
|
|
2450
|
+
} else {
|
|
2451
|
+
try {
|
|
2452
|
+
el.style.setProperty(propKebab, entry.value, 'important');
|
|
2453
|
+
} catch(_) {
|
|
2454
|
+
el.style[cam] = entry.value;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2002
2457
|
}
|
|
2003
2458
|
break;
|
|
2004
2459
|
case 'attribute':
|
|
@@ -2009,11 +2464,23 @@ function applyChangesetEntry(entry, iframeDoc) {
|
|
|
2009
2464
|
case 'insert': {
|
|
2010
2465
|
var pos = entry.action === 'before' ? 'beforebegin' : 'afterend';
|
|
2011
2466
|
if (entry.html) el.insertAdjacentHTML(pos, entry.html);
|
|
2467
|
+
if (structuralDedupeKey) appliedStructuralChangesetKeys[structuralDedupeKey] = true;
|
|
2012
2468
|
break;
|
|
2013
2469
|
}
|
|
2014
2470
|
case 'remove':
|
|
2015
2471
|
el.style.display = 'none';
|
|
2016
2472
|
break;
|
|
2473
|
+
case 'reorder': {
|
|
2474
|
+
var target = entry.targetSelector ? querySelectorResolved(iframeDoc, entry.targetSelector) : null;
|
|
2475
|
+
if (!target || !el.parentNode || !target.parentNode) break;
|
|
2476
|
+
if (entry.action === 'before') {
|
|
2477
|
+
target.parentNode.insertBefore(el, target);
|
|
2478
|
+
} else {
|
|
2479
|
+
target.parentNode.insertBefore(el, target.nextSibling);
|
|
2480
|
+
}
|
|
2481
|
+
if (structuralDedupeKey) appliedStructuralChangesetKeys[structuralDedupeKey] = true;
|
|
2482
|
+
break;
|
|
2483
|
+
}
|
|
2017
2484
|
default: break;
|
|
2018
2485
|
}
|
|
2019
2486
|
}
|
|
@@ -2028,19 +2495,24 @@ function applyActiveVariationHtml() {
|
|
|
2028
2495
|
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2029
2496
|
var cs = parseVariationChangesets(variation);
|
|
2030
2497
|
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2498
|
+
beginSuppressIframeMutationDirty();
|
|
2499
|
+
try {
|
|
2500
|
+
// If we have an in-session HTML snapshot, use it (user edited in this session)
|
|
2501
|
+
var saved = varHtmlCache[activeVarId];
|
|
2502
|
+
if (saved) {
|
|
2503
|
+
try { iframeDoc.body.innerHTML = saved; } catch(_) {}
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2037
2506
|
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2507
|
+
if (!cs.length) return;
|
|
2508
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2509
|
+
applyChangesetEntry(cs[i], iframeDoc);
|
|
2510
|
+
}
|
|
2511
|
+
// Selectors often miss on first paint (client-rendered sections). Retry via timer + mutations.
|
|
2512
|
+
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2513
|
+
} finally {
|
|
2514
|
+
endSuppressIframeMutationDirty();
|
|
2041
2515
|
}
|
|
2042
|
-
// Selectors often miss on first paint (client-rendered sections). Retry via timer + mutations.
|
|
2043
|
-
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2044
2516
|
}
|
|
2045
2517
|
|
|
2046
2518
|
function changesetsHaveBodySnapshot(cs) {
|
|
@@ -2051,6 +2523,23 @@ function changesetsHaveBodySnapshot(cs) {
|
|
|
2051
2523
|
return false;
|
|
2052
2524
|
}
|
|
2053
2525
|
|
|
2526
|
+
/** Rows to persist for this variation on Finalize (same chain-set model as EditorShell \u2014 never __vvveb_body__). */
|
|
2527
|
+
function buildPersistedChainSetsForVariation(v) {
|
|
2528
|
+
if (!v || !v._id) return [];
|
|
2529
|
+
var parsed = parseVariationChangesets(v);
|
|
2530
|
+
var base = filterGranularChangesetEntries(parsed);
|
|
2531
|
+
var sessionExtra = sessionStructuralChainRowsByVarId[v._id] || [];
|
|
2532
|
+
if (v._id !== activeVarId) {
|
|
2533
|
+
return mergeGranularChainSets(base, sessionExtra);
|
|
2534
|
+
}
|
|
2535
|
+
var overlay = [];
|
|
2536
|
+
for (var si = 0; si < stateChanges.length; si++) {
|
|
2537
|
+
var row = stateChangeToChainSet(stateChanges[si]);
|
|
2538
|
+
if (row) overlay.push(row);
|
|
2539
|
+
}
|
|
2540
|
+
return mergeGranularChainSets(mergeGranularChainSets(base, sessionExtra), overlay);
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2054
2543
|
/**
|
|
2055
2544
|
* While document.readyState === 'loading', apply only granular changesets (no __vvveb_body__
|
|
2056
2545
|
* replacement) so the first painted nodes can receive edits before iframe/window load completes.
|
|
@@ -2061,10 +2550,15 @@ function applyVariationGranularOnly(iframeDoc) {
|
|
|
2061
2550
|
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2062
2551
|
var cs = parseVariationChangesets(variation);
|
|
2063
2552
|
if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
|
|
2064
|
-
|
|
2065
|
-
|
|
2553
|
+
beginSuppressIframeMutationDirty();
|
|
2554
|
+
try {
|
|
2555
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2556
|
+
applyChangesetEntry(cs[i], iframeDoc);
|
|
2557
|
+
}
|
|
2558
|
+
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2559
|
+
} finally {
|
|
2560
|
+
endSuppressIframeMutationDirty();
|
|
2066
2561
|
}
|
|
2067
|
-
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2068
2562
|
}
|
|
2069
2563
|
|
|
2070
2564
|
/** Re-try granular entries without resetting pending registration (use between poll ticks). */
|
|
@@ -2074,8 +2568,13 @@ function reapplyActiveVariationGranular(iframeDoc) {
|
|
|
2074
2568
|
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2075
2569
|
var cs = parseVariationChangesets(variation);
|
|
2076
2570
|
if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
|
|
2077
|
-
|
|
2078
|
-
|
|
2571
|
+
beginSuppressIframeMutationDirty();
|
|
2572
|
+
try {
|
|
2573
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2574
|
+
applyChangesetEntry(cs[i], iframeDoc);
|
|
2575
|
+
}
|
|
2576
|
+
} finally {
|
|
2577
|
+
endSuppressIframeMutationDirty();
|
|
2079
2578
|
}
|
|
2080
2579
|
}
|
|
2081
2580
|
|
|
@@ -2137,9 +2636,14 @@ function startIframeContentApplyWatcher(navGen) {
|
|
|
2137
2636
|
|
|
2138
2637
|
// \u2500\u2500 Element selection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2139
2638
|
function selectElement(el) {
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2639
|
+
beginSuppressIframeMutationDirty();
|
|
2640
|
+
try {
|
|
2641
|
+
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} }
|
|
2642
|
+
selectedEl = el;
|
|
2643
|
+
try { el.classList.add('vve-selected'); } catch(_) {}
|
|
2644
|
+
} finally {
|
|
2645
|
+
endSuppressIframeMutationDirty();
|
|
2646
|
+
}
|
|
2143
2647
|
document.getElementById('bc-path').textContent = buildSelector(el);
|
|
2144
2648
|
document.getElementById('bc-path').style.color = 'var(--accent-txt)';
|
|
2145
2649
|
document.getElementById('no-sel').style.display = 'none';
|
|
@@ -2154,7 +2658,12 @@ function selectElement(el) {
|
|
|
2154
2658
|
|
|
2155
2659
|
function deselectElement() {
|
|
2156
2660
|
setDragHandleActive(false);
|
|
2157
|
-
|
|
2661
|
+
beginSuppressIframeMutationDirty();
|
|
2662
|
+
try {
|
|
2663
|
+
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} selectedEl = null; }
|
|
2664
|
+
} finally {
|
|
2665
|
+
endSuppressIframeMutationDirty();
|
|
2666
|
+
}
|
|
2158
2667
|
document.getElementById('no-sel').style.display = '';
|
|
2159
2668
|
document.getElementById('el-info').style.display = 'none';
|
|
2160
2669
|
document.getElementById('rp-accordion').style.display = 'none';
|
|
@@ -2178,18 +2687,27 @@ function injectIframeSelectionStyles(doc) {
|
|
|
2178
2687
|
'html.vve-drag-armed .vve-selected{cursor:grab!important;}' +
|
|
2179
2688
|
'.vve-dragging{opacity:0.92!important;outline:2px dashed #f59e0b!important;' +
|
|
2180
2689
|
'outline-offset:2px!important;cursor:grabbing!important;box-shadow:none!important;}';
|
|
2181
|
-
|
|
2690
|
+
beginSuppressIframeMutationDirty();
|
|
2691
|
+
try {
|
|
2692
|
+
doc.head.appendChild(st);
|
|
2693
|
+
} finally {
|
|
2694
|
+
endSuppressIframeMutationDirty();
|
|
2695
|
+
}
|
|
2182
2696
|
}
|
|
2183
2697
|
|
|
2184
2698
|
function setDragHandleActive(on) {
|
|
2185
2699
|
dragHandleActive = !!on;
|
|
2186
2700
|
var b = document.getElementById('sf-drag');
|
|
2187
2701
|
if (b) b.classList.toggle('active', dragHandleActive);
|
|
2702
|
+
beginSuppressIframeMutationDirty();
|
|
2188
2703
|
try {
|
|
2189
2704
|
var iframe = document.getElementById('iframeId');
|
|
2190
2705
|
var d = iframe && iframe.contentDocument && iframe.contentDocument.documentElement;
|
|
2191
2706
|
if (d) d.classList.toggle('vve-drag-armed', dragHandleActive);
|
|
2192
|
-
} catch(_) {
|
|
2707
|
+
} catch(_) {
|
|
2708
|
+
} finally {
|
|
2709
|
+
endSuppressIframeMutationDirty();
|
|
2710
|
+
}
|
|
2193
2711
|
}
|
|
2194
2712
|
|
|
2195
2713
|
function positionSelectionToolbar() {
|
|
@@ -2259,34 +2777,72 @@ function selectElementFromTree(el) {
|
|
|
2259
2777
|
|
|
2260
2778
|
function duplicateSelectedEl() {
|
|
2261
2779
|
if (!selectedEl || !selectedEl.parentNode) return;
|
|
2780
|
+
var anchorSel = buildSelector(selectedEl);
|
|
2262
2781
|
var clone = selectedEl.cloneNode(true);
|
|
2263
2782
|
clone.classList.remove('vve-selected');
|
|
2264
2783
|
var all = clone.querySelectorAll ? clone.querySelectorAll('.vve-selected') : [];
|
|
2265
2784
|
for (var i = 0; i < all.length; i++) all[i].classList.remove('vve-selected');
|
|
2266
2785
|
clone.style.visibility = '';
|
|
2267
2786
|
if (clone.removeAttribute) clone.removeAttribute('data-vve-hidden');
|
|
2787
|
+
stripDataVveInstanceSubtree(clone);
|
|
2788
|
+
try {
|
|
2789
|
+
if (clone.id) clone.removeAttribute('id');
|
|
2790
|
+
} catch(_) {}
|
|
2791
|
+
try {
|
|
2792
|
+
clone.setAttribute('data-vve-instance', generateVveInstanceId());
|
|
2793
|
+
} catch(_) {}
|
|
2794
|
+
if (activeVarId) {
|
|
2795
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
2796
|
+
selector: anchorSel,
|
|
2797
|
+
type: 'insert',
|
|
2798
|
+
action: 'after',
|
|
2799
|
+
html: clone.outerHTML,
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2268
2802
|
selectedEl.parentNode.insertBefore(clone, selectedEl.nextSibling);
|
|
2269
|
-
|
|
2803
|
+
saveCurrentVariationHtml();
|
|
2804
|
+
recomputeEditorDirty();
|
|
2270
2805
|
scheduleDomTreeRefresh();
|
|
2271
2806
|
selectElement(clone);
|
|
2272
2807
|
}
|
|
2273
2808
|
|
|
2274
2809
|
function toggleHideSelectedEl() {
|
|
2275
2810
|
if (!selectedEl) return;
|
|
2811
|
+
var hidSel = buildSelector(selectedEl);
|
|
2276
2812
|
if (selectedEl.getAttribute('data-vve-hidden') === '1') {
|
|
2277
2813
|
selectedEl.style.visibility = '';
|
|
2278
2814
|
selectedEl.removeAttribute('data-vve-hidden');
|
|
2815
|
+
if (activeVarId) {
|
|
2816
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
2817
|
+
selector: hidSel,
|
|
2818
|
+
type: 'style',
|
|
2819
|
+
property: 'visibility',
|
|
2820
|
+
value: '',
|
|
2821
|
+
});
|
|
2822
|
+
}
|
|
2279
2823
|
} else {
|
|
2280
2824
|
selectedEl.style.visibility = 'hidden';
|
|
2281
2825
|
selectedEl.setAttribute('data-vve-hidden', '1');
|
|
2826
|
+
if (activeVarId) {
|
|
2827
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
2828
|
+
selector: hidSel,
|
|
2829
|
+
type: 'style',
|
|
2830
|
+
property: 'visibility',
|
|
2831
|
+
value: 'hidden',
|
|
2832
|
+
});
|
|
2833
|
+
}
|
|
2282
2834
|
}
|
|
2283
|
-
|
|
2835
|
+
saveCurrentVariationHtml();
|
|
2836
|
+
recomputeEditorDirty();
|
|
2284
2837
|
}
|
|
2285
2838
|
|
|
2286
2839
|
function deleteSelectedEl() {
|
|
2287
2840
|
if (!selectedEl || !selectedEl.parentNode) return;
|
|
2841
|
+
var delSel = buildSelector(selectedEl);
|
|
2288
2842
|
selectedEl.remove();
|
|
2289
|
-
|
|
2843
|
+
if (activeVarId) appendSessionStructuralChainRow(activeVarId, { selector: delSel, type: 'remove' });
|
|
2844
|
+
saveCurrentVariationHtml();
|
|
2845
|
+
recomputeEditorDirty();
|
|
2290
2846
|
deselectElement();
|
|
2291
2847
|
scheduleDomTreeRefresh();
|
|
2292
2848
|
}
|
|
@@ -2578,14 +3134,12 @@ function renderImageSection(el) {
|
|
|
2578
3134
|
var prev = document.querySelector('.img-preview');
|
|
2579
3135
|
if (prev) prev.src = srcInp.value;
|
|
2580
3136
|
logChange(sel, 'pp-src', srcInp.value, el, orig);
|
|
2581
|
-
markDirty();
|
|
2582
3137
|
});
|
|
2583
3138
|
var altInp = document.getElementById('pp-img-alt');
|
|
2584
3139
|
if (altInp) altInp.addEventListener('input', function() {
|
|
2585
3140
|
var orig = getOriginalValue('pp-alt', el);
|
|
2586
3141
|
el.setAttribute('alt', altInp.value);
|
|
2587
3142
|
logChange(sel, 'pp-alt', altInp.value, el, orig);
|
|
2588
|
-
markDirty();
|
|
2589
3143
|
});
|
|
2590
3144
|
|
|
2591
3145
|
// Wire srcset entry inputs
|
|
@@ -2594,7 +3148,8 @@ function renderImageSection(el) {
|
|
|
2594
3148
|
var dInp = document.getElementById('pp-se-desc-'+i);
|
|
2595
3149
|
function flushSrcset() {
|
|
2596
3150
|
el.setAttribute('srcset', buildSrcset(_srcsetEntries));
|
|
2597
|
-
|
|
3151
|
+
saveCurrentVariationHtml();
|
|
3152
|
+
recomputeEditorDirty();
|
|
2598
3153
|
}
|
|
2599
3154
|
if (uInp) uInp.addEventListener('input', function(){ _srcsetEntries[i].url = uInp.value; flushSrcset(); });
|
|
2600
3155
|
if (dInp) dInp.addEventListener('input', function(){ _srcsetEntries[i].descriptor = dInp.value; flushSrcset(); });
|
|
@@ -2612,7 +3167,8 @@ function renderImageSection(el) {
|
|
|
2612
3167
|
}
|
|
2613
3168
|
function flushSizes() {
|
|
2614
3169
|
el.setAttribute('sizes', buildSizes(_sizesEntries));
|
|
2615
|
-
|
|
3170
|
+
saveCurrentVariationHtml();
|
|
3171
|
+
recomputeEditorDirty();
|
|
2616
3172
|
}
|
|
2617
3173
|
if (opInp) opInp.addEventListener('change', function(){ buildCondition(); flushSizes(); });
|
|
2618
3174
|
if (valInp) valInp.addEventListener('input', function(){ buildCondition(); flushSizes(); });
|
|
@@ -2626,7 +3182,12 @@ function addSrcsetEntry() {
|
|
|
2626
3182
|
}
|
|
2627
3183
|
function removeSrcsetEntry(i) {
|
|
2628
3184
|
_srcsetEntries.splice(i, 1);
|
|
2629
|
-
if (_imageEl) {
|
|
3185
|
+
if (_imageEl) {
|
|
3186
|
+
_imageEl.setAttribute('srcset', buildSrcset(_srcsetEntries));
|
|
3187
|
+
renderImageSection(_imageEl);
|
|
3188
|
+
saveCurrentVariationHtml();
|
|
3189
|
+
recomputeEditorDirty();
|
|
3190
|
+
}
|
|
2630
3191
|
}
|
|
2631
3192
|
function addSizesEntry() {
|
|
2632
3193
|
_sizesEntries.push({condition:'max-width: 760px', value:'760px'});
|
|
@@ -2634,7 +3195,12 @@ function addSizesEntry() {
|
|
|
2634
3195
|
}
|
|
2635
3196
|
function removeSizesEntry(i) {
|
|
2636
3197
|
_sizesEntries.splice(i, 1);
|
|
2637
|
-
if (_imageEl) {
|
|
3198
|
+
if (_imageEl) {
|
|
3199
|
+
_imageEl.setAttribute('sizes', buildSizes(_sizesEntries));
|
|
3200
|
+
renderImageSection(_imageEl);
|
|
3201
|
+
saveCurrentVariationHtml();
|
|
3202
|
+
recomputeEditorDirty();
|
|
3203
|
+
}
|
|
2638
3204
|
}
|
|
2639
3205
|
|
|
2640
3206
|
// \u2500\u2500 Right panel rendering (accordion) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -2839,20 +3405,51 @@ function renderRightPanel(el) {
|
|
|
2839
3405
|
var orig = getOriginalValue(b[0], el);
|
|
2840
3406
|
b[1](inp.value);
|
|
2841
3407
|
logChange(sel, b[0], inp.value, el, orig);
|
|
2842
|
-
markDirty();
|
|
2843
3408
|
});
|
|
2844
3409
|
});
|
|
2845
3410
|
}
|
|
2846
3411
|
|
|
2847
3412
|
// \u2500\u2500 Selector helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3413
|
+
function generateVveInstanceId() {
|
|
3414
|
+
return 'v' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10);
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
/** Editor-assigned clone marker so duplicated subtrees do not share the same CSS path as the original. */
|
|
3418
|
+
function stripDataVveInstanceSubtree(root) {
|
|
3419
|
+
if (!root || root.nodeType !== 1) return;
|
|
3420
|
+
try {
|
|
3421
|
+
root.removeAttribute('data-vve-instance');
|
|
3422
|
+
} catch(_) {}
|
|
3423
|
+
var sub = root.querySelectorAll ? root.querySelectorAll('[data-vve-instance]') : [];
|
|
3424
|
+
for (var i = 0; i < sub.length; i++) {
|
|
3425
|
+
try {
|
|
3426
|
+
sub[i].removeAttribute('data-vve-instance');
|
|
3427
|
+
} catch(_) {}
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
|
|
2848
3431
|
function buildSelector(el) {
|
|
2849
3432
|
if (!el) return '';
|
|
3433
|
+
var doc = el.ownerDocument || document;
|
|
3434
|
+
var inst = el.getAttribute && el.getAttribute('data-vve-instance');
|
|
3435
|
+
if (inst && String(inst).trim()) {
|
|
3436
|
+
var safe = String(inst).split('"').join('\\"');
|
|
3437
|
+
var attrSel = '[data-vve-instance="' + safe + '"]';
|
|
3438
|
+
try {
|
|
3439
|
+
if (doc.querySelectorAll(attrSel).length === 1) return attrSel;
|
|
3440
|
+
} catch(_) {}
|
|
3441
|
+
}
|
|
2850
3442
|
if (el.id) return '#' + el.id;
|
|
2851
3443
|
var parts = [], node = el, depth = 0;
|
|
2852
3444
|
while (node && node.nodeType === 1 && depth < 5) {
|
|
2853
3445
|
if (node.id) { parts.unshift('#' + node.id); break; }
|
|
2854
3446
|
var p = node.tagName.toLowerCase();
|
|
2855
|
-
if (node.classList && node.classList.length)
|
|
3447
|
+
if (node.classList && node.classList.length) {
|
|
3448
|
+
var clsArr = Array.from(node.classList).filter(function(c) {
|
|
3449
|
+
return c.indexOf('vve-') !== 0;
|
|
3450
|
+
});
|
|
3451
|
+
if (clsArr.length) p += '.' + clsArr.slice(0, 2).join('.');
|
|
3452
|
+
}
|
|
2856
3453
|
var idx = 1, sib = node.previousElementSibling;
|
|
2857
3454
|
while (sib) { if (sib.tagName === node.tagName) idx++; sib = sib.previousElementSibling; }
|
|
2858
3455
|
if (idx > 1) p += ':nth-of-type(' + idx + ')';
|
|
@@ -2863,6 +3460,71 @@ function buildSelector(el) {
|
|
|
2863
3460
|
return parts.join(' > ');
|
|
2864
3461
|
}
|
|
2865
3462
|
|
|
3463
|
+
/**
|
|
3464
|
+
* Strip editor-only .vve-* class tokens from a selector string (fixes DB rows saved while an element was selected).
|
|
3465
|
+
*/
|
|
3466
|
+
function sanitizeSelectorForMatch(sel) {
|
|
3467
|
+
if (!sel || typeof sel !== 'string') return '';
|
|
3468
|
+
var s0 = sel.replace(/.vve-[a-zA-Z0-9_-]+/gi, '');
|
|
3469
|
+
var parts = s0.split(/s*>s*/).map(function(seg) {
|
|
3470
|
+
var t = seg.replace(/.+/g, '.').replace(/.$/, '');
|
|
3471
|
+
return t.trim();
|
|
3472
|
+
});
|
|
3473
|
+
return parts.filter(Boolean).join(' > ');
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
/** Drop the rightmost :nth-of-type(n) (hydration / layout often shifts sibling indices). */
|
|
3477
|
+
function stripRightmostNthOfType(sel) {
|
|
3478
|
+
if (!sel || typeof sel !== 'string') return null;
|
|
3479
|
+
var idx = sel.lastIndexOf(':nth-of-type(');
|
|
3480
|
+
if (idx === -1) return null;
|
|
3481
|
+
var j = idx + ':nth-of-type('.length;
|
|
3482
|
+
while (j < sel.length && sel.charCodeAt(j) >= 48 && sel.charCodeAt(j) <= 57) j++;
|
|
3483
|
+
if (j >= sel.length || sel.charAt(j) !== ')') return null;
|
|
3484
|
+
return (sel.slice(0, idx) + sel.slice(j + 1))
|
|
3485
|
+
.replace(/s{2,}/g, ' ')
|
|
3486
|
+
.replace(/s*>s*>/g, ' >')
|
|
3487
|
+
.trim();
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
function querySelectorResolved(iframeDoc, selector) {
|
|
3491
|
+
if (!iframeDoc || !selector) return null;
|
|
3492
|
+
var seen = {};
|
|
3493
|
+
function tryOne(s) {
|
|
3494
|
+
if (!s || seen[s]) return null;
|
|
3495
|
+
seen[s] = true;
|
|
3496
|
+
try {
|
|
3497
|
+
return iframeDoc.querySelector(s) || null;
|
|
3498
|
+
} catch(_) {
|
|
3499
|
+
return null;
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
function walkRelax(base) {
|
|
3503
|
+
if (!base) return null;
|
|
3504
|
+
var el = tryOne(base);
|
|
3505
|
+
if (el) return el;
|
|
3506
|
+
var cur = base;
|
|
3507
|
+
for (var g = 0; g < 28; g++) {
|
|
3508
|
+
var nxt = stripRightmostNthOfType(cur);
|
|
3509
|
+
if (!nxt || nxt === cur) break;
|
|
3510
|
+
cur = nxt;
|
|
3511
|
+
el = tryOne(cur);
|
|
3512
|
+
if (el) return el;
|
|
3513
|
+
}
|
|
3514
|
+
return null;
|
|
3515
|
+
}
|
|
3516
|
+
var alt = sanitizeSelectorForMatch(selector);
|
|
3517
|
+
// Prefer sanitized + nth relax FIRST: raw selectors often still contain .vve-* from
|
|
3518
|
+
// save-time selection; those only match after clicking (we re-add vve-selected).
|
|
3519
|
+
var el = walkRelax(alt || selector);
|
|
3520
|
+
if (el) return el;
|
|
3521
|
+
if (alt !== selector) {
|
|
3522
|
+
el = walkRelax(selector);
|
|
3523
|
+
if (el) return el;
|
|
3524
|
+
}
|
|
3525
|
+
return null;
|
|
3526
|
+
}
|
|
3527
|
+
|
|
2866
3528
|
// \u2500\u2500 Iframe interaction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2867
3529
|
function repositionDragSibling(dragEl, clientY) {
|
|
2868
3530
|
var p = dragEl.parentElement;
|
|
@@ -2888,6 +3550,27 @@ function repositionDragSibling(dragEl, clientY) {
|
|
|
2888
3550
|
}
|
|
2889
3551
|
}
|
|
2890
3552
|
|
|
3553
|
+
function recordReorderAfterDrag(movedEl) {
|
|
3554
|
+
if (!activeVarId || !movedEl || !movedEl.parentElement) return;
|
|
3555
|
+
var prev = movedEl.previousElementSibling;
|
|
3556
|
+
var next = movedEl.nextElementSibling;
|
|
3557
|
+
if (prev) {
|
|
3558
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
3559
|
+
selector: buildSelector(movedEl),
|
|
3560
|
+
type: 'reorder',
|
|
3561
|
+
targetSelector: buildSelector(prev),
|
|
3562
|
+
action: 'after',
|
|
3563
|
+
});
|
|
3564
|
+
} else if (next) {
|
|
3565
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
3566
|
+
selector: buildSelector(movedEl),
|
|
3567
|
+
type: 'reorder',
|
|
3568
|
+
targetSelector: buildSelector(next),
|
|
3569
|
+
action: 'before',
|
|
3570
|
+
});
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
|
|
2891
3574
|
function attachDragReposition() {
|
|
2892
3575
|
try {
|
|
2893
3576
|
var iframe = document.getElementById('iframeId');
|
|
@@ -2941,7 +3624,9 @@ function attachDragReposition() {
|
|
|
2941
3624
|
} catch(_) {}
|
|
2942
3625
|
suppressClickUntil = Date.now() + 200;
|
|
2943
3626
|
setDragHandleActive(false);
|
|
2944
|
-
|
|
3627
|
+
if (activeVarId) recordReorderAfterDrag(selectedEl);
|
|
3628
|
+
saveCurrentVariationHtml();
|
|
3629
|
+
recomputeEditorDirty();
|
|
2945
3630
|
updateSelectionToolbar();
|
|
2946
3631
|
scheduleDomTreeRefresh();
|
|
2947
3632
|
}
|
|
@@ -2994,11 +3679,31 @@ function attachChangeObserver() {
|
|
|
2994
3679
|
changeObserver = null;
|
|
2995
3680
|
changeObserverDoc = null;
|
|
2996
3681
|
}
|
|
2997
|
-
changeObserver = new MutationObserver(function() {
|
|
2998
|
-
|
|
2999
|
-
|
|
3682
|
+
changeObserver = new MutationObserver(function(mutations) {
|
|
3683
|
+
// Dirty state is derived from changesets baseline + stateChanges (not raw DOM mutations).
|
|
3684
|
+
var bodyReplaced = false;
|
|
3685
|
+
for (var mi = 0; mi < mutations.length; mi++) {
|
|
3686
|
+
var m = mutations[mi];
|
|
3687
|
+
if (
|
|
3688
|
+
m &&
|
|
3689
|
+
m.type === 'childList' &&
|
|
3690
|
+
m.target === doc.body &&
|
|
3691
|
+
m.addedNodes &&
|
|
3692
|
+
m.removedNodes &&
|
|
3693
|
+
m.addedNodes.length > 0 &&
|
|
3694
|
+
m.removedNodes.length > 0
|
|
3695
|
+
) {
|
|
3696
|
+
bodyReplaced = true;
|
|
3697
|
+
break;
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
if (bodyReplaced) {
|
|
3701
|
+
// Page JS replaced body children; allow structural rows (insert/reorder) to apply again.
|
|
3702
|
+
appliedStructuralChangesetKeys = {};
|
|
3703
|
+
}
|
|
3000
3704
|
scheduleDomTreeRefresh();
|
|
3001
3705
|
scheduleGranularChangesetReapply();
|
|
3706
|
+
scheduleConsistencyReconcile();
|
|
3002
3707
|
});
|
|
3003
3708
|
changeObserver.observe(doc.body, {
|
|
3004
3709
|
childList: true, subtree: true, attributes: true, characterData: true
|
|
@@ -3028,10 +3733,13 @@ function syncIframeInteractions(reason) {
|
|
|
3028
3733
|
attachClickHandler();
|
|
3029
3734
|
attachDragReposition();
|
|
3030
3735
|
attachChangeObserver();
|
|
3736
|
+
startConsistencyWatchdog(doc);
|
|
3737
|
+
scheduleConsistencyReconcile();
|
|
3031
3738
|
bindSelectionToolbarScroll();
|
|
3032
3739
|
var inp = document.getElementById('comp-search');
|
|
3033
3740
|
renderDomTree(inp ? inp.value : '');
|
|
3034
3741
|
updateSelectionToolbar();
|
|
3742
|
+
recomputeEditorDirty();
|
|
3035
3743
|
} catch(_) {}
|
|
3036
3744
|
}
|
|
3037
3745
|
|
|
@@ -3083,8 +3791,11 @@ function insertHtml(html) {
|
|
|
3083
3791
|
console.warn('[V2] insertHtml: iframe document not ready');
|
|
3084
3792
|
return;
|
|
3085
3793
|
}
|
|
3794
|
+
var htmlStr = String(html).trim();
|
|
3795
|
+
var anchorSel =
|
|
3796
|
+
selectedEl && selectedEl !== doc.body && selectedEl.parentNode ? buildSelector(selectedEl) : 'body';
|
|
3086
3797
|
var t = doc.createElement('template');
|
|
3087
|
-
t.innerHTML =
|
|
3798
|
+
t.innerHTML = htmlStr;
|
|
3088
3799
|
var frag = doc.createDocumentFragment();
|
|
3089
3800
|
var firstEl = null;
|
|
3090
3801
|
while (t.content.firstChild) {
|
|
@@ -3100,7 +3811,16 @@ function insertHtml(html) {
|
|
|
3100
3811
|
doc.body.appendChild(frag);
|
|
3101
3812
|
}
|
|
3102
3813
|
if (firstEl) selectElement(firstEl);
|
|
3103
|
-
|
|
3814
|
+
if (activeVarId) {
|
|
3815
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
3816
|
+
selector: anchorSel,
|
|
3817
|
+
type: 'insert',
|
|
3818
|
+
action: 'after',
|
|
3819
|
+
html: htmlStr,
|
|
3820
|
+
});
|
|
3821
|
+
}
|
|
3822
|
+
saveCurrentVariationHtml();
|
|
3823
|
+
recomputeEditorDirty();
|
|
3104
3824
|
scheduleDomTreeRefresh();
|
|
3105
3825
|
} catch(err) { console.warn('[V2] insertHtml:', err); }
|
|
3106
3826
|
}
|
|
@@ -3192,15 +3912,37 @@ document.getElementById('btn-close').addEventListener('click', handleClose);
|
|
|
3192
3912
|
function handleSave() {
|
|
3193
3913
|
saveCurrentVariationHtml();
|
|
3194
3914
|
var updatedVariations = variations.map(function(v) {
|
|
3195
|
-
var
|
|
3196
|
-
|
|
3197
|
-
|
|
3915
|
+
var prevParsed = parseVariationChangesets(v);
|
|
3916
|
+
var granularPrev = filterGranularChangesetEntries(prevParsed);
|
|
3917
|
+
var bodyOnlyLegacy = changesetsHaveBodySnapshot(prevParsed) && granularPrev.length === 0;
|
|
3918
|
+
|
|
3919
|
+
var rows = buildPersistedChainSetsForVariation(v);
|
|
3920
|
+
if (bodyOnlyLegacy && rows.length === 0) {
|
|
3921
|
+
return Object.assign({}, v);
|
|
3922
|
+
}
|
|
3923
|
+
var json = '[]';
|
|
3924
|
+
try {
|
|
3925
|
+
json = JSON.stringify(rows || []);
|
|
3926
|
+
} catch(_) {
|
|
3927
|
+
json = '[]';
|
|
3928
|
+
}
|
|
3929
|
+
return Object.assign({}, v, { changesets: json });
|
|
3198
3930
|
});
|
|
3931
|
+
variations = updatedVariations;
|
|
3932
|
+
varHtmlCache = {};
|
|
3933
|
+
sessionStructuralChainRowsByVarId = {};
|
|
3934
|
+
stateChanges = [];
|
|
3935
|
+
if (currentMainTab === 'states') renderStatesTab();
|
|
3936
|
+
captureBaselineFromVariations(variations);
|
|
3937
|
+
if (experimentData && Array.isArray(experimentData.variations)) {
|
|
3938
|
+
experimentData.variations = updatedVariations;
|
|
3939
|
+
}
|
|
3199
3940
|
send('save-experiment', { experimentId: experimentData ? experimentData.experimentId : null, variations: updatedVariations });
|
|
3200
|
-
|
|
3941
|
+
setEditorDirty(false);
|
|
3201
3942
|
}
|
|
3202
3943
|
|
|
3203
3944
|
function handleClose() {
|
|
3945
|
+
clearVisualEditorLocalStorage();
|
|
3204
3946
|
// Unsaved-changes UX lives in the parent (PlatformVisualEditorV2); avoid double confirm here.
|
|
3205
3947
|
send('close-editor', {});
|
|
3206
3948
|
}
|
|
@@ -3208,8 +3950,22 @@ function handleClose() {
|
|
|
3208
3950
|
// \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
|
|
3209
3951
|
document.addEventListener('keydown', function(e) {
|
|
3210
3952
|
var meta = e.metaKey || e.ctrlKey;
|
|
3211
|
-
if (meta && !e.shiftKey && e.key === 'z') {
|
|
3212
|
-
|
|
3953
|
+
if (meta && !e.shiftKey && e.key === 'z') {
|
|
3954
|
+
e.preventDefault();
|
|
3955
|
+
if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
|
|
3956
|
+
Vvveb.Undo.undo();
|
|
3957
|
+
saveCurrentVariationHtml();
|
|
3958
|
+
recomputeEditorDirty();
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
if (meta && e.shiftKey && e.key === 'z') {
|
|
3962
|
+
e.preventDefault();
|
|
3963
|
+
if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
|
|
3964
|
+
Vvveb.Undo.redo();
|
|
3965
|
+
saveCurrentVariationHtml();
|
|
3966
|
+
recomputeEditorDirty();
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3213
3969
|
if (meta && e.key === 's') { e.preventDefault(); handleSave(); }
|
|
3214
3970
|
if (e.key === 'Escape') {
|
|
3215
3971
|
var openTips = document.querySelectorAll('.ve-pl-tip.is-tip-open');
|