@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.js
CHANGED
|
@@ -619,7 +619,7 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
619
619
|
</div>
|
|
620
620
|
<!-- btn-close: hidden visually, kept for JS event listener -->
|
|
621
621
|
<button id="btn-close" style="display:none" title="Close editor"></button>
|
|
622
|
-
<button class="tb-sim-btn" id="btn-simulate"><i class="bi bi-lightning-charge-fill"></i> Simulate</button>
|
|
622
|
+
<button class="tb-sim-btn" id="btn-simulate" onclick="simulateExperiment()"><i class="bi bi-lightning-charge-fill"></i> Simulate</button>
|
|
623
623
|
<button class="tb-fin-btn" id="btn-save">Finalize</button>
|
|
624
624
|
</div>
|
|
625
625
|
|
|
@@ -842,6 +842,7 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
842
842
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/inputs.js"></script>
|
|
843
843
|
<!-- components.js defines shared colour-class arrays used by bootstrap5/widgets components -->
|
|
844
844
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/components.js"></script>
|
|
845
|
+
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/components-html.js"></script>
|
|
845
846
|
<script>
|
|
846
847
|
/* Safety stub: if components.js didn't define these, create empty arrays so
|
|
847
848
|
components-bootstrap5.js doesn't throw ReferenceError on load. */
|
|
@@ -855,6 +856,18 @@ if (typeof colorSelectOptions === 'undefined') window.colorSelectOptions =
|
|
|
855
856
|
if (typeof textColorSelectOptions=== 'undefined') window.textColorSelectOptions= [];
|
|
856
857
|
if (typeof borderSelectOptions === 'undefined') window.borderSelectOptions = [];
|
|
857
858
|
if (typeof sizeSelectOptions === 'undefined') window.sizeSelectOptions = [];
|
|
859
|
+
if (window.Vvveb && window.Vvveb.Components) {
|
|
860
|
+
if (!window.Vvveb.ComponentsGroup) window.Vvveb.ComponentsGroup = {};
|
|
861
|
+
if (!window.Vvveb.ComponentsGroup['Bootstrap 5']) window.Vvveb.ComponentsGroup['Bootstrap 5'] = [];
|
|
862
|
+
try {
|
|
863
|
+
var baseExists =
|
|
864
|
+
window.Vvveb.Components._components &&
|
|
865
|
+
Object.prototype.hasOwnProperty.call(window.Vvveb.Components._components, '_base');
|
|
866
|
+
if (!baseExists && typeof window.Vvveb.Components.add === 'function') {
|
|
867
|
+
window.Vvveb.Components.add('_base', { name: 'Base', properties: [] });
|
|
868
|
+
}
|
|
869
|
+
} catch(_) {}
|
|
870
|
+
}
|
|
858
871
|
</script>
|
|
859
872
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/components-bootstrap5.js"></script>
|
|
860
873
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/components-widgets.js"></script>
|
|
@@ -893,6 +906,52 @@ function send(type, payload) {
|
|
|
893
906
|
window.parent.postMessage({ channel: CHANNEL, type: type, payload: payload || {} }, '*');
|
|
894
907
|
}
|
|
895
908
|
|
|
909
|
+
function generatePreviewUrlString(args) {
|
|
910
|
+
var baseUrl = (args && args.url) || '';
|
|
911
|
+
var test = (args && args.test) || {};
|
|
912
|
+
var variation = (args && args.variation) || {};
|
|
913
|
+
if (!baseUrl) return '';
|
|
914
|
+
var testId = test.iid || test.experimentId || test._id || '';
|
|
915
|
+
var variationId = variation.iid || variation._id || '';
|
|
916
|
+
var cId = String(testId || '') + '_' + String(variationId || '');
|
|
917
|
+
var hasQueryParams = String(baseUrl).indexOf('?') >= 0;
|
|
918
|
+
return (
|
|
919
|
+
baseUrl +
|
|
920
|
+
(hasQueryParams ? '&' : '?') +
|
|
921
|
+
'codebase_debug=true&cId=' +
|
|
922
|
+
encodeURIComponent(cId)
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function simulateExperiment() {
|
|
927
|
+
var test = experimentData || {};
|
|
928
|
+
var activeVariation = null;
|
|
929
|
+
if (Array.isArray(variations) && variations.length) {
|
|
930
|
+
activeVariation =
|
|
931
|
+
variations.find(function(v) { return v && v._id === activeVarId; }) ||
|
|
932
|
+
variations[0];
|
|
933
|
+
}
|
|
934
|
+
var targetUrl =
|
|
935
|
+
(Array.isArray(test.urltargeting) && test.urltargeting[0]) ||
|
|
936
|
+
test.pageUrl ||
|
|
937
|
+
(test.metadata_1 && test.metadata_1.editor_url) ||
|
|
938
|
+
'';
|
|
939
|
+
var url = generatePreviewUrlString({
|
|
940
|
+
url: targetUrl,
|
|
941
|
+
test: test,
|
|
942
|
+
variation: activeVariation || {},
|
|
943
|
+
});
|
|
944
|
+
if (!url) {
|
|
945
|
+
console.warn('[V2] simulateExperiment: missing target URL');
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
try {
|
|
949
|
+
window.open(url, '_blank');
|
|
950
|
+
} catch(err) {
|
|
951
|
+
console.warn('[V2] simulateExperiment:', err);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
896
955
|
window.addEventListener('message', function(e) {
|
|
897
956
|
if (!e.data || e.data.channel !== CHANNEL) return;
|
|
898
957
|
switch (e.data.type) {
|
|
@@ -906,6 +965,8 @@ var experimentData = null;
|
|
|
906
965
|
var variations = [];
|
|
907
966
|
var activeVarId = null;
|
|
908
967
|
var varHtmlCache = {};
|
|
968
|
+
/** Per-variation chain rows from structural actions (insert/duplicate/delete/reorder/hide), merged on Finalize. */
|
|
969
|
+
var sessionStructuralChainRowsByVarId = {};
|
|
909
970
|
/** Last iframe proxy URL we navigated to \u2014 used to skip redundant reloads when parent re-sends load-experiment */
|
|
910
971
|
var lastLoadedProxyUrl = '';
|
|
911
972
|
/** API changeset rows (excluding __vvveb_body__) reapplied until selectors match late-hydrated DOM */
|
|
@@ -919,6 +980,8 @@ var iframeContentNavGen = 0;
|
|
|
919
980
|
var iframeContentApplyTimer = null;
|
|
920
981
|
var iframeEarlyGranularPrimedForGen = null;
|
|
921
982
|
var iframeEarlySyncPrimedForGen = null;
|
|
983
|
+
/** insert/reorder entries are applied from early granular + full apply \u2014 skip exact duplicates per iframe nav */
|
|
984
|
+
var appliedStructuralChangesetKeys = {};
|
|
922
985
|
var isDirty = false;
|
|
923
986
|
var vvvebReady = false;
|
|
924
987
|
var currentMode = 'editor';
|
|
@@ -938,6 +1001,15 @@ var selectionResizeBound = false;
|
|
|
938
1001
|
var clickAttachDoc = null;
|
|
939
1002
|
var changeObserver = null;
|
|
940
1003
|
var changeObserverDoc = null;
|
|
1004
|
+
/** Incremented while applying changesets / selection chrome so MutationObserver does not mark dirty */
|
|
1005
|
+
var suppressIframeMutationDirty = 0;
|
|
1006
|
+
/** Throttled reconcile timer to keep DOM aligned with saved changesets. */
|
|
1007
|
+
var consistencyReconcileTimer = null;
|
|
1008
|
+
/** Background watchdog for late client-side re-renders that wipe applied changesets. */
|
|
1009
|
+
var consistencyWatchTimer = null;
|
|
1010
|
+
var consistencyWatchDoc = null;
|
|
1011
|
+
var consistencyWatchTicks = 0;
|
|
1012
|
+
var CONSISTENCY_WATCH_MAX_TICKS = 160;
|
|
941
1013
|
/** { doc, onRS } \u2014 iframe document readystate until complete */
|
|
942
1014
|
var iframeDocLoadingListeners = null;
|
|
943
1015
|
// Each entry: {selector, label, cssProp, value, targetEl}
|
|
@@ -945,12 +1017,89 @@ var iframeDocLoadingListeners = null;
|
|
|
945
1017
|
var stateChanges = [];
|
|
946
1018
|
/** Pre-apply DOM snapshots for granular + body changesets (used when removing History rows) */
|
|
947
1019
|
var appliedChangesetSnapshots = {};
|
|
1020
|
+
/** Canonical JSON fingerprints of persisted changesets per variation (last load / finalize) */
|
|
1021
|
+
var baselineChangesetsByVarId = {};
|
|
1022
|
+
|
|
1023
|
+
// \u2500\u2500 Dirty tracking (compare DB baseline + session stateChanges vs current export) \u2500\u2500
|
|
1024
|
+
function beginSuppressIframeMutationDirty() {
|
|
1025
|
+
suppressIframeMutationDirty += 1;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function endSuppressIframeMutationDirty() {
|
|
1029
|
+
suppressIframeMutationDirty = Math.max(0, suppressIframeMutationDirty - 1);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/** Stable stringify of a variation's changesets field (string or array from API). */
|
|
1033
|
+
function fingerprintChangesetsField(raw) {
|
|
1034
|
+
if (raw == null) return '[]';
|
|
1035
|
+
if (Array.isArray(raw)) {
|
|
1036
|
+
try {
|
|
1037
|
+
return JSON.stringify(raw);
|
|
1038
|
+
} catch(_) {
|
|
1039
|
+
return '[]';
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
if (typeof raw !== 'string') return '[]';
|
|
1043
|
+
var s = raw.trim();
|
|
1044
|
+
if (!s) return '[]';
|
|
1045
|
+
try {
|
|
1046
|
+
var p = JSON.parse(s);
|
|
1047
|
+
return JSON.stringify(Array.isArray(p) ? p : []);
|
|
1048
|
+
} catch(_) {
|
|
1049
|
+
return '[]';
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function captureBaselineFromVariations(list) {
|
|
1054
|
+
baselineChangesetsByVarId = {};
|
|
1055
|
+
if (!list || !list.length) return;
|
|
1056
|
+
for (var i = 0; i < list.length; i++) {
|
|
1057
|
+
var v = list[i];
|
|
1058
|
+
if (!v || !v._id) continue;
|
|
1059
|
+
baselineChangesetsByVarId[v._id] = fingerprintChangesetsField(v.changesets);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/** Fingerprint of what Finalize would send for this variation (matches buildPersistedChainSetsForVariation). */
|
|
1064
|
+
function persistedExportFingerprintForVariation(v) {
|
|
1065
|
+
if (!v || !v._id) return '[]';
|
|
1066
|
+
try {
|
|
1067
|
+
var rows = buildPersistedChainSetsForVariation(v);
|
|
1068
|
+
return JSON.stringify(rows || []);
|
|
1069
|
+
} catch(_) {
|
|
1070
|
+
return '[]';
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
function setEditorDirty(dirty) {
|
|
1075
|
+
var was = isDirty;
|
|
1076
|
+
isDirty = !!dirty;
|
|
1077
|
+
var dot = document.getElementById('dirty-dot');
|
|
1078
|
+
if (dot) dot.classList.toggle('on', isDirty);
|
|
1079
|
+
if (isDirty && !was) send('mutations-changed', {});
|
|
1080
|
+
if (!isDirty && was) send('editor-dirty', { dirty: false });
|
|
1081
|
+
if (!isDirty) {
|
|
1082
|
+
savedAt = Date.now();
|
|
1083
|
+
updateSaveTime();
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
948
1086
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1087
|
+
function recomputeEditorDirty() {
|
|
1088
|
+
var d = stateChanges.length > 0;
|
|
1089
|
+
if (!d && variations && variations.length) {
|
|
1090
|
+
for (var i = 0; i < variations.length; i++) {
|
|
1091
|
+
var v = variations[i];
|
|
1092
|
+
var vid = v._id;
|
|
1093
|
+
var cur = persistedExportFingerprintForVariation(v);
|
|
1094
|
+
var base = baselineChangesetsByVarId[vid];
|
|
1095
|
+
if (base == null) base = '[]';
|
|
1096
|
+
if (cur !== base) {
|
|
1097
|
+
d = true;
|
|
1098
|
+
break;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
setEditorDirty(d);
|
|
954
1103
|
}
|
|
955
1104
|
var savedAt = null;
|
|
956
1105
|
function updateSaveTime() {
|
|
@@ -960,12 +1109,6 @@ function updateSaveTime() {
|
|
|
960
1109
|
el.textContent = s < 60 ? s + 's ago' : Math.floor(s / 60) + 'm ago';
|
|
961
1110
|
}
|
|
962
1111
|
setInterval(updateSaveTime, 10000);
|
|
963
|
-
function markClean() {
|
|
964
|
-
isDirty = false;
|
|
965
|
-
savedAt = Date.now();
|
|
966
|
-
document.getElementById('dirty-dot').classList.remove('on');
|
|
967
|
-
updateSaveTime();
|
|
968
|
-
}
|
|
969
1112
|
|
|
970
1113
|
// \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
|
|
971
1114
|
function setMode(mode) {
|
|
@@ -1142,6 +1285,7 @@ function logChange(selector, inputId, value, targetEl, originalValue) {
|
|
|
1142
1285
|
if (idx >= 0) { stateChanges[idx] = entry; } else { stateChanges.push(entry); }
|
|
1143
1286
|
}
|
|
1144
1287
|
if (currentMainTab === 'states') renderStatesTab();
|
|
1288
|
+
recomputeEditorDirty();
|
|
1145
1289
|
}
|
|
1146
1290
|
|
|
1147
1291
|
function renderStatesTab() {
|
|
@@ -1175,7 +1319,7 @@ function renderStatesTab() {
|
|
|
1175
1319
|
|
|
1176
1320
|
// Resolve a live DOM element for a state-change entry.
|
|
1177
1321
|
// Tries the stored direct reference first; if it's detached or missing,
|
|
1178
|
-
// falls back to querySelector(
|
|
1322
|
+
// falls back to querySelector (with .vve-* class stripped) inside the iframe document.
|
|
1179
1323
|
function resolveChangeEl(change) {
|
|
1180
1324
|
try {
|
|
1181
1325
|
var iframeDoc = document.getElementById('iframeId').contentDocument;
|
|
@@ -1185,7 +1329,7 @@ function resolveChangeEl(change) {
|
|
|
1185
1329
|
return change.targetEl;
|
|
1186
1330
|
}
|
|
1187
1331
|
// Fallback: re-query by the stored CSS selector
|
|
1188
|
-
return iframeDoc
|
|
1332
|
+
return querySelectorResolved(iframeDoc, change.selector);
|
|
1189
1333
|
} catch (e) {
|
|
1190
1334
|
console.warn('[V2] resolveChangeEl:', e);
|
|
1191
1335
|
return null;
|
|
@@ -1248,7 +1392,7 @@ function removeStateChange(idx) {
|
|
|
1248
1392
|
syncDesignInput(change);
|
|
1249
1393
|
stateChanges.splice(idx, 1);
|
|
1250
1394
|
renderStatesTab();
|
|
1251
|
-
|
|
1395
|
+
recomputeEditorDirty();
|
|
1252
1396
|
}
|
|
1253
1397
|
|
|
1254
1398
|
function clearAllStates() {
|
|
@@ -1258,7 +1402,7 @@ function clearAllStates() {
|
|
|
1258
1402
|
});
|
|
1259
1403
|
stateChanges = [];
|
|
1260
1404
|
renderStatesTab();
|
|
1261
|
-
|
|
1405
|
+
recomputeEditorDirty();
|
|
1262
1406
|
}
|
|
1263
1407
|
|
|
1264
1408
|
// \u2500\u2500 History tab (saved changesets from DB for active variation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -1283,10 +1427,11 @@ function persistActiveVariationChangesets(arr) {
|
|
|
1283
1427
|
|
|
1284
1428
|
function entrySnapshotKey(entry) {
|
|
1285
1429
|
if (!entry || !entry.selector) return '';
|
|
1430
|
+
var selKey = sanitizeSelectorForMatch(entry.selector) || entry.selector;
|
|
1286
1431
|
return (
|
|
1287
|
-
|
|
1432
|
+
selKey +
|
|
1288
1433
|
'\0' +
|
|
1289
|
-
(entry
|
|
1434
|
+
normalizeChangesetType(entry) +
|
|
1290
1435
|
'\0' +
|
|
1291
1436
|
String(entry.property || '') +
|
|
1292
1437
|
'\0' +
|
|
@@ -1304,7 +1449,7 @@ function captureChangesetSnapshotBeforeApply(entry, el, iframeDoc) {
|
|
|
1304
1449
|
if (!entry || !el || entry.selector === '__vvveb_body__') return;
|
|
1305
1450
|
var k = entrySnapshotKey(entry);
|
|
1306
1451
|
if (appliedChangesetSnapshots[k]) return;
|
|
1307
|
-
switch (entry
|
|
1452
|
+
switch (normalizeChangesetType(entry)) {
|
|
1308
1453
|
case 'content':
|
|
1309
1454
|
if (entry.html != null) {
|
|
1310
1455
|
appliedChangesetSnapshots[k] = { kind: 'innerHTML', v: el.innerHTML };
|
|
@@ -1372,10 +1517,7 @@ function revertChangesetEntryOnDom(entry) {
|
|
|
1372
1517
|
if (!iframeDoc) return false;
|
|
1373
1518
|
var k = entrySnapshotKey(entry);
|
|
1374
1519
|
var snap = appliedChangesetSnapshots[k];
|
|
1375
|
-
var el =
|
|
1376
|
-
try {
|
|
1377
|
-
el = iframeDoc.querySelector(entry.selector);
|
|
1378
|
-
} catch(_) {}
|
|
1520
|
+
var el = querySelectorResolved(iframeDoc, entry.selector);
|
|
1379
1521
|
if (!snap || !el) {
|
|
1380
1522
|
softReloadEditorIframe();
|
|
1381
1523
|
delete appliedChangesetSnapshots[k];
|
|
@@ -1402,7 +1544,7 @@ function revertChangesetEntryOnDom(entry) {
|
|
|
1402
1544
|
function historyEntryTypeLabel(entry) {
|
|
1403
1545
|
if (!entry) return 'Change';
|
|
1404
1546
|
if (entry.selector === '__vvveb_body__') return 'Full page HTML';
|
|
1405
|
-
var t = (entry
|
|
1547
|
+
var t = normalizeChangesetType(entry);
|
|
1406
1548
|
if (t === 'content') return entry.html != null ? 'Inner HTML' : 'Text / content';
|
|
1407
1549
|
if (t === 'style') return 'Style: ' + (entry.property || '');
|
|
1408
1550
|
if (t === 'attribute') return 'Attribute: ' + (entry.attribute || '');
|
|
@@ -1416,7 +1558,8 @@ function historyEntryValuePreview(entry) {
|
|
|
1416
1558
|
if (entry.selector === '__vvveb_body__') return '(body snapshot)';
|
|
1417
1559
|
if (entry.html != null) return String(entry.html).slice(0, 120);
|
|
1418
1560
|
if (entry.value != null) return String(entry.value).slice(0, 120);
|
|
1419
|
-
|
|
1561
|
+
var nt = normalizeChangesetType(entry);
|
|
1562
|
+
if (nt === 'style' || nt === 'attribute') return String(entry.value != null ? entry.value : '').slice(0, 120);
|
|
1420
1563
|
return '';
|
|
1421
1564
|
}
|
|
1422
1565
|
|
|
@@ -1450,6 +1593,9 @@ function renderHistoryTab() {
|
|
|
1450
1593
|
var val = historyEntryValuePreview(item.entry);
|
|
1451
1594
|
html +=
|
|
1452
1595
|
'<div class="state-item">' +
|
|
1596
|
+
'<span class="state-item-idx" style="opacity:0.55;font-size:10px;min-width:2.2em;display:inline-block" title="Order in saved changesets">#' +
|
|
1597
|
+
item.idx +
|
|
1598
|
+
'</span>' +
|
|
1453
1599
|
'<span class="state-item-label">' +
|
|
1454
1600
|
esc(lab) +
|
|
1455
1601
|
'</span>' +
|
|
@@ -1458,7 +1604,9 @@ function renderHistoryTab() {
|
|
|
1458
1604
|
'">' +
|
|
1459
1605
|
esc(val) +
|
|
1460
1606
|
'</span>' +
|
|
1461
|
-
'<button type="button" class="state-remove" title="Remove
|
|
1607
|
+
'<button type="button" class="state-remove" title="Remove this saved row (#' +
|
|
1608
|
+
item.idx +
|
|
1609
|
+
')" onclick="removeHistoryChangeset(' +
|
|
1462
1610
|
item.idx +
|
|
1463
1611
|
')">✕</button>' +
|
|
1464
1612
|
'</div>';
|
|
@@ -1468,6 +1616,16 @@ function renderHistoryTab() {
|
|
|
1468
1616
|
container.innerHTML = html;
|
|
1469
1617
|
}
|
|
1470
1618
|
|
|
1619
|
+
function changesetListHasStructural(arr) {
|
|
1620
|
+
if (!arr || !arr.length) return false;
|
|
1621
|
+
for (var i = 0; i < arr.length; i++) {
|
|
1622
|
+
var e = arr[i];
|
|
1623
|
+
var t = normalizeChangesetType(e);
|
|
1624
|
+
if (e && (t === 'insert' || t === 'reorder')) return true;
|
|
1625
|
+
}
|
|
1626
|
+
return false;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1471
1629
|
function removeHistoryChangeset(idx) {
|
|
1472
1630
|
var v = getActiveVariationForHistory();
|
|
1473
1631
|
if (!v) return;
|
|
@@ -1480,18 +1638,31 @@ function removeHistoryChangeset(idx) {
|
|
|
1480
1638
|
try {
|
|
1481
1639
|
delete varHtmlCache[activeVarId];
|
|
1482
1640
|
} catch(_) {}
|
|
1483
|
-
|
|
1641
|
+
// Re-applying remaining rows on top of current DOM duplicates insert/reorder nodes; reload when any
|
|
1642
|
+
// structural row remains or was removed (revert may already have started a reload for insert/body).
|
|
1643
|
+
var removedType = normalizeChangesetType(removed);
|
|
1644
|
+
var needsStructuralReload =
|
|
1645
|
+
!didReload &&
|
|
1646
|
+
(removedType === 'insert' ||
|
|
1647
|
+
removedType === 'reorder' ||
|
|
1648
|
+
changesetListHasStructural(arr));
|
|
1649
|
+
if (didReload) {
|
|
1650
|
+
/* revertChangesetEntryOnDom already kicked off iframe reload */
|
|
1651
|
+
} else if (needsStructuralReload) {
|
|
1652
|
+
softReloadEditorIframe();
|
|
1653
|
+
} else {
|
|
1484
1654
|
try {
|
|
1655
|
+
appliedStructuralChangesetKeys = {};
|
|
1485
1656
|
applyActiveVariationHtml();
|
|
1486
1657
|
registerPendingGranularChangesets(
|
|
1487
1658
|
arr,
|
|
1488
1659
|
document.getElementById('iframeId').contentDocument,
|
|
1489
1660
|
);
|
|
1661
|
+
saveCurrentVariationHtml();
|
|
1490
1662
|
} catch(_) {}
|
|
1491
|
-
saveCurrentVariationHtml();
|
|
1492
1663
|
}
|
|
1493
1664
|
if (currentMainTab === 'history') renderHistoryTab();
|
|
1494
|
-
|
|
1665
|
+
recomputeEditorDirty();
|
|
1495
1666
|
scheduleDomTreeRefresh();
|
|
1496
1667
|
}
|
|
1497
1668
|
|
|
@@ -1501,15 +1672,82 @@ function clearAllHistoryChangesets() {
|
|
|
1501
1672
|
if (!parseVariationChangesets(v).length) return;
|
|
1502
1673
|
persistActiveVariationChangesets([]);
|
|
1503
1674
|
appliedChangesetSnapshots = {};
|
|
1675
|
+
appliedStructuralChangesetKeys = {};
|
|
1504
1676
|
try {
|
|
1505
1677
|
delete varHtmlCache[activeVarId];
|
|
1506
1678
|
} catch(_) {}
|
|
1507
1679
|
if (currentMainTab === 'history') renderHistoryTab();
|
|
1508
|
-
|
|
1680
|
+
recomputeEditorDirty();
|
|
1509
1681
|
scheduleDomTreeRefresh();
|
|
1510
1682
|
softReloadEditorIframe();
|
|
1511
1683
|
}
|
|
1512
1684
|
|
|
1685
|
+
// \u2500\u2500 Persisted active variation (survives iframe / full page reload) \u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1686
|
+
/** All Visual Editor iframe keys in localStorage use this prefix \u2014 cleared on close. */
|
|
1687
|
+
var VVE_LOCAL_STORAGE_PREFIX = 'vve:';
|
|
1688
|
+
|
|
1689
|
+
function clearVisualEditorLocalStorage() {
|
|
1690
|
+
try {
|
|
1691
|
+
for (var i = localStorage.length - 1; i >= 0; i--) {
|
|
1692
|
+
var k = localStorage.key(i);
|
|
1693
|
+
if (k && k.indexOf(VVE_LOCAL_STORAGE_PREFIX) === 0) {
|
|
1694
|
+
localStorage.removeItem(k);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
} catch(_) {}
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
function activeVariationStorageKeyFromPayload(data) {
|
|
1701
|
+
return (
|
|
1702
|
+
VVE_LOCAL_STORAGE_PREFIX +
|
|
1703
|
+
'activeVar:' +
|
|
1704
|
+
String((data && data.experimentId) || '') +
|
|
1705
|
+
':' +
|
|
1706
|
+
String((data && data.pageUrl) || '')
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
function readPersistedActiveVariationId(data) {
|
|
1711
|
+
try {
|
|
1712
|
+
var sk = activeVariationStorageKeyFromPayload(data);
|
|
1713
|
+
if (sk === VVE_LOCAL_STORAGE_PREFIX + 'activeVar::') return null;
|
|
1714
|
+
return localStorage.getItem(sk);
|
|
1715
|
+
} catch(_) {
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
function writePersistedActiveVariationId(varId) {
|
|
1721
|
+
try {
|
|
1722
|
+
if (!experimentData || !experimentData.experimentId) return;
|
|
1723
|
+
var sk =
|
|
1724
|
+
VVE_LOCAL_STORAGE_PREFIX +
|
|
1725
|
+
'activeVar:' +
|
|
1726
|
+
String(experimentData.experimentId || '') +
|
|
1727
|
+
':' +
|
|
1728
|
+
String(experimentData.pageUrl || '');
|
|
1729
|
+
if (sk === VVE_LOCAL_STORAGE_PREFIX + 'activeVar::') return;
|
|
1730
|
+
if (varId) localStorage.setItem(sk, String(varId));
|
|
1731
|
+
} catch(_) {}
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
/**
|
|
1735
|
+
* @param allowPrevMemory when true, keep in-session activeVarId if still valid (skip-reload path).
|
|
1736
|
+
*/
|
|
1737
|
+
function pickActiveVariationIdForLoad(data, variationsArr, prevMemoryId, allowPrevMemory) {
|
|
1738
|
+
var baseline = variationsArr.find(function(v) { return v.baseline; });
|
|
1739
|
+
var fallback = (baseline || variationsArr[0] || {})._id || null;
|
|
1740
|
+
if (!variationsArr.length) return null;
|
|
1741
|
+
if (allowPrevMemory && prevMemoryId && variationsArr.some(function(v) { return v._id === prevMemoryId; })) {
|
|
1742
|
+
return prevMemoryId;
|
|
1743
|
+
}
|
|
1744
|
+
var stored = readPersistedActiveVariationId(data);
|
|
1745
|
+
if (stored && variationsArr.some(function(v) { return v._id === stored; })) {
|
|
1746
|
+
return stored;
|
|
1747
|
+
}
|
|
1748
|
+
return fallback;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1513
1751
|
// \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
|
|
1514
1752
|
function handleLoadExperiment(data) {
|
|
1515
1753
|
clearPendingGranularChangesets();
|
|
@@ -1537,10 +1775,8 @@ function handleLoadExperiment(data) {
|
|
|
1537
1775
|
experimentData = data;
|
|
1538
1776
|
variations = Array.isArray(data.variations) ? data.variations : [];
|
|
1539
1777
|
var prevActive = activeVarId;
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
activeVarId =
|
|
1543
|
-
prevActive && variations.some(function(v) { return v._id === prevActive; }) ? prevActive : fallback;
|
|
1778
|
+
activeVarId = pickActiveVariationIdForLoad(data, variations, prevActive, true);
|
|
1779
|
+
writePersistedActiveVariationId(activeVarId);
|
|
1544
1780
|
renderVariationTabs();
|
|
1545
1781
|
var urlBarSkip = document.getElementById('url-bar');
|
|
1546
1782
|
urlBarSkip.textContent = pageUrl;
|
|
@@ -1554,24 +1790,30 @@ function handleLoadExperiment(data) {
|
|
|
1554
1790
|
if (ifrSkip && ifrSkip.contentDocument) attachIframeLoadingUntilComplete(ifrSkip);
|
|
1555
1791
|
} catch(_) {}
|
|
1556
1792
|
if (currentMainTab === 'history') renderHistoryTab();
|
|
1793
|
+
captureBaselineFromVariations(variations);
|
|
1794
|
+
recomputeEditorDirty();
|
|
1557
1795
|
return;
|
|
1558
1796
|
}
|
|
1559
1797
|
|
|
1560
1798
|
if (!experimentData || prevKey !== nextKey) {
|
|
1561
1799
|
varHtmlCache = {};
|
|
1800
|
+
sessionStructuralChainRowsByVarId = {};
|
|
1562
1801
|
appliedChangesetSnapshots = {};
|
|
1802
|
+
appliedStructuralChangesetKeys = {};
|
|
1563
1803
|
}
|
|
1564
1804
|
experimentData = data;
|
|
1565
1805
|
variations = Array.isArray(data.variations) ? data.variations : [];
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
activeVarId
|
|
1806
|
+
var sameExpPage = prevKey === nextKey;
|
|
1807
|
+
activeVarId = pickActiveVariationIdForLoad(data, variations, activeVarId, sameExpPage);
|
|
1808
|
+
writePersistedActiveVariationId(activeVarId);
|
|
1569
1809
|
renderVariationTabs();
|
|
1570
1810
|
|
|
1571
1811
|
var urlBar = document.getElementById('url-bar');
|
|
1572
1812
|
urlBar.textContent = pageUrl;
|
|
1573
1813
|
urlBar.title = pageUrl;
|
|
1574
1814
|
if (currentMainTab === 'history') renderHistoryTab();
|
|
1815
|
+
captureBaselineFromVariations(variations);
|
|
1816
|
+
recomputeEditorDirty();
|
|
1575
1817
|
loadPage(proxyUrl);
|
|
1576
1818
|
}
|
|
1577
1819
|
|
|
@@ -1671,9 +1913,7 @@ function granularAnySelectorMatches(doc, cs) {
|
|
|
1671
1913
|
if (!doc || !cs || !cs.length) return false;
|
|
1672
1914
|
var g = filterGranularChangesetEntries(cs);
|
|
1673
1915
|
for (var i = 0; i < g.length; i++) {
|
|
1674
|
-
|
|
1675
|
-
if (doc.querySelector(g[i].selector)) return true;
|
|
1676
|
-
} catch(_) {}
|
|
1916
|
+
if (querySelectorResolved(doc, g[i].selector)) return true;
|
|
1677
1917
|
}
|
|
1678
1918
|
return false;
|
|
1679
1919
|
}
|
|
@@ -1694,7 +1934,7 @@ function appendIframeReloadBust(url) {
|
|
|
1694
1934
|
// applying variation changesets there is then wiped when the real navigation commits.
|
|
1695
1935
|
function iframeDocMatchesNavigatedSrc(iframe, doc) {
|
|
1696
1936
|
if (!iframe || !doc) return false;
|
|
1697
|
-
var src = iframe.src || iframe.
|
|
1937
|
+
var src = iframe.getAttribute('src') || iframe.src || '';
|
|
1698
1938
|
if (!src || src === 'about:blank') return false;
|
|
1699
1939
|
var loc = '';
|
|
1700
1940
|
try {
|
|
@@ -1703,12 +1943,28 @@ function iframeDocMatchesNavigatedSrc(iframe, doc) {
|
|
|
1703
1943
|
return false;
|
|
1704
1944
|
}
|
|
1705
1945
|
if (!loc || loc === 'about:blank') return false;
|
|
1706
|
-
var rmSrc = src.match(/__ve_reload=([0-9]+)/);
|
|
1707
|
-
if (rmSrc) return loc.indexOf('__ve_reload=' + rmSrc[1]) !== -1;
|
|
1708
1946
|
try {
|
|
1709
1947
|
var base = window.location.href;
|
|
1710
1948
|
var su = new URL(src, base);
|
|
1949
|
+
if (su.searchParams && su.searchParams.has('__ve_reload')) {
|
|
1950
|
+
su.searchParams.delete('__ve_reload');
|
|
1951
|
+
}
|
|
1711
1952
|
var du = new URL(loc, base);
|
|
1953
|
+
if (du.searchParams && du.searchParams.has('__ve_reload')) {
|
|
1954
|
+
du.searchParams.delete('__ve_reload');
|
|
1955
|
+
}
|
|
1956
|
+
// Same-origin proxy that keeps document address aligned with iframe src
|
|
1957
|
+
if (su.origin === du.origin && su.pathname + su.search === du.pathname + du.search) {
|
|
1958
|
+
return true;
|
|
1959
|
+
}
|
|
1960
|
+
// conversion-proxy: iframe src stays on our app, but doc.URL is usually the target site after redirects
|
|
1961
|
+
var p = su.pathname || '';
|
|
1962
|
+
var isRootProxyPath = p === '/api/conversion-proxy' || p.indexOf('/api/conversion-proxy/') === 0;
|
|
1963
|
+
var isNestedMalformedProxy = !isRootProxyPath && p.indexOf('api/conversion-proxy') !== -1;
|
|
1964
|
+
if (isNestedMalformedProxy) return false;
|
|
1965
|
+
if (isRootProxyPath || String(su.href).indexOf('conversion-proxy') !== -1) {
|
|
1966
|
+
return doc === iframe.contentDocument;
|
|
1967
|
+
}
|
|
1712
1968
|
return su.pathname + su.search === du.pathname + du.search;
|
|
1713
1969
|
} catch(_) {
|
|
1714
1970
|
return false;
|
|
@@ -1737,10 +1993,73 @@ function clearPendingGranularChangesets() {
|
|
|
1737
1993
|
}
|
|
1738
1994
|
}
|
|
1739
1995
|
|
|
1996
|
+
function stopConsistencyWatchdog() {
|
|
1997
|
+
if (consistencyWatchTimer) {
|
|
1998
|
+
clearInterval(consistencyWatchTimer);
|
|
1999
|
+
consistencyWatchTimer = null;
|
|
2000
|
+
}
|
|
2001
|
+
consistencyWatchDoc = null;
|
|
2002
|
+
consistencyWatchTicks = 0;
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
function runConsistencyReconcile() {
|
|
2006
|
+
if (!activeVarId) return;
|
|
2007
|
+
var iframe = document.getElementById('iframeId');
|
|
2008
|
+
var doc = iframe && iframe.contentDocument;
|
|
2009
|
+
if (!doc || !doc.body) return;
|
|
2010
|
+
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2011
|
+
var cs = parseVariationChangesets(variation);
|
|
2012
|
+
if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
|
|
2013
|
+
var granular = filterGranularChangesetEntries(cs);
|
|
2014
|
+
var unresolved = countUnresolvedGranularSelectors(doc, granular);
|
|
2015
|
+
if (unresolved > 0 || changesetListHasStructural(cs)) {
|
|
2016
|
+
reapplyActiveVariationGranular(doc);
|
|
2017
|
+
registerPendingGranularChangesets(cs, doc);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
function scheduleConsistencyReconcile() {
|
|
2022
|
+
if (consistencyReconcileTimer) return;
|
|
2023
|
+
consistencyReconcileTimer = setTimeout(function() {
|
|
2024
|
+
consistencyReconcileTimer = null;
|
|
2025
|
+
runConsistencyReconcile();
|
|
2026
|
+
}, 80);
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
function startConsistencyWatchdog(doc) {
|
|
2030
|
+
if (!doc || !doc.body) return;
|
|
2031
|
+
if (consistencyWatchDoc === doc && consistencyWatchTimer) return;
|
|
2032
|
+
stopConsistencyWatchdog();
|
|
2033
|
+
consistencyWatchDoc = doc;
|
|
2034
|
+
consistencyWatchTicks = 0;
|
|
2035
|
+
consistencyWatchTimer = setInterval(function() {
|
|
2036
|
+
if (consistencyWatchDoc !== doc) {
|
|
2037
|
+
stopConsistencyWatchdog();
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
var iframe = document.getElementById('iframeId');
|
|
2041
|
+
if (!iframe || iframe.contentDocument !== doc || !doc.body) {
|
|
2042
|
+
stopConsistencyWatchdog();
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
consistencyWatchTicks += 1;
|
|
2046
|
+
scheduleConsistencyReconcile();
|
|
2047
|
+
if (consistencyWatchTicks >= CONSISTENCY_WATCH_MAX_TICKS) {
|
|
2048
|
+
stopConsistencyWatchdog();
|
|
2049
|
+
}
|
|
2050
|
+
}, 400);
|
|
2051
|
+
}
|
|
2052
|
+
|
|
1740
2053
|
function resetIframeBindings() {
|
|
1741
2054
|
detachIframeLoadingListeners();
|
|
1742
2055
|
stopIframeContentApplyWatcher();
|
|
2056
|
+
stopConsistencyWatchdog();
|
|
2057
|
+
if (consistencyReconcileTimer) {
|
|
2058
|
+
clearTimeout(consistencyReconcileTimer);
|
|
2059
|
+
consistencyReconcileTimer = null;
|
|
2060
|
+
}
|
|
1743
2061
|
appliedChangesetSnapshots = {};
|
|
2062
|
+
appliedStructuralChangesetKeys = {};
|
|
1744
2063
|
clickAttachDoc = null;
|
|
1745
2064
|
dragAttachDoc = null;
|
|
1746
2065
|
changeObserverDoc = null;
|
|
@@ -1811,13 +2130,19 @@ function switchVariation(varId) {
|
|
|
1811
2130
|
saveCurrentVariationHtml();
|
|
1812
2131
|
clearPendingGranularChangesets();
|
|
1813
2132
|
activeVarId = varId;
|
|
2133
|
+
writePersistedActiveVariationId(varId);
|
|
1814
2134
|
renderVariationTabs();
|
|
1815
2135
|
deselectElement();
|
|
1816
2136
|
try {
|
|
1817
2137
|
var iframe = document.getElementById('iframeId');
|
|
1818
2138
|
var saved = varHtmlCache[varId];
|
|
1819
2139
|
if (saved) {
|
|
1820
|
-
|
|
2140
|
+
beginSuppressIframeMutationDirty();
|
|
2141
|
+
try {
|
|
2142
|
+
iframe.contentDocument.body.innerHTML = saved;
|
|
2143
|
+
} finally {
|
|
2144
|
+
endSuppressIframeMutationDirty();
|
|
2145
|
+
}
|
|
1821
2146
|
detachIframeLoadingListeners();
|
|
1822
2147
|
setIframePageLoadingUi(false);
|
|
1823
2148
|
syncIframeInteractions('switch-variation-cache');
|
|
@@ -1840,6 +2165,7 @@ function switchVariation(varId) {
|
|
|
1840
2165
|
}
|
|
1841
2166
|
} catch(_) {}
|
|
1842
2167
|
if (currentMainTab === 'history') renderHistoryTab();
|
|
2168
|
+
recomputeEditorDirty();
|
|
1843
2169
|
}
|
|
1844
2170
|
|
|
1845
2171
|
function saveCurrentVariationHtml() {
|
|
@@ -1878,31 +2204,123 @@ function parseVariationChangesets(variation) {
|
|
|
1878
2204
|
}
|
|
1879
2205
|
}
|
|
1880
2206
|
|
|
2207
|
+
/** Lowercase entry.type so persisted / API rows match switches (e.g. Insert vs insert). */
|
|
2208
|
+
function normalizeChangesetType(entry) {
|
|
2209
|
+
return String(entry && entry.type != null ? entry.type : '').toLowerCase();
|
|
2210
|
+
}
|
|
2211
|
+
|
|
1881
2212
|
function filterGranularChangesetEntries(cs) {
|
|
1882
2213
|
if (!cs || !cs.length) return [];
|
|
1883
2214
|
var out = [];
|
|
1884
2215
|
for (var i = 0; i < cs.length; i++) {
|
|
1885
2216
|
var e = cs[i];
|
|
1886
|
-
if (e
|
|
2217
|
+
if (!e || !e.selector || e.selector === '__vvveb_body__') continue;
|
|
2218
|
+
out.push(e);
|
|
2219
|
+
}
|
|
2220
|
+
return out;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
/** Dedup key for merging chain-set rows (overlay wins over base). */
|
|
2224
|
+
function chainSetDedupKey(entry) {
|
|
2225
|
+
if (!entry || !entry.selector) return '';
|
|
2226
|
+
var t = normalizeChangesetType(entry);
|
|
2227
|
+
if (t === 'style') return entry.selector + '|s|' + String(entry.property || '');
|
|
2228
|
+
if (t === 'content') return entry.selector + '|c|' + (entry.html != null ? 'h' : 't');
|
|
2229
|
+
if (t === 'attribute') return entry.selector + '|a|' + String(entry.attribute || '');
|
|
2230
|
+
if (t === 'insert') return entry.selector + '|i|' + String(entry.action || '') + '|' + String(entry.html || '').slice(0, 120);
|
|
2231
|
+
if (t === 'remove') return entry.selector + '|r|';
|
|
2232
|
+
if (t === 'reorder') {
|
|
2233
|
+
return entry.selector + '|ro|' + String(entry.targetSelector || '') + '|' + String(entry.action || '');
|
|
2234
|
+
}
|
|
2235
|
+
try {
|
|
2236
|
+
return entry.selector + '|' + t + '|' + JSON.stringify(entry);
|
|
2237
|
+
} catch(_) {
|
|
2238
|
+
return entry.selector + '|' + t;
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
function mergeGranularChainSets(baseList, overlayList) {
|
|
2243
|
+
var map = {};
|
|
2244
|
+
var order = [];
|
|
2245
|
+
function ingest(arr) {
|
|
2246
|
+
if (!arr || !arr.length) return;
|
|
2247
|
+
for (var i = 0; i < arr.length; i++) {
|
|
2248
|
+
var e = arr[i];
|
|
2249
|
+
if (!e || !e.selector) continue;
|
|
2250
|
+
var k = chainSetDedupKey(e);
|
|
2251
|
+
if (!map[k]) order.push(k);
|
|
2252
|
+
map[k] = e;
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
ingest(baseList);
|
|
2256
|
+
ingest(overlayList);
|
|
2257
|
+
var out = [];
|
|
2258
|
+
for (var j = 0; j < order.length; j++) {
|
|
2259
|
+
out.push(map[order[j]]);
|
|
1887
2260
|
}
|
|
1888
2261
|
return out;
|
|
1889
2262
|
}
|
|
1890
2263
|
|
|
2264
|
+
function appendSessionStructuralChainRow(varId, row) {
|
|
2265
|
+
if (!varId || !row) return;
|
|
2266
|
+
if (!sessionStructuralChainRowsByVarId[varId]) sessionStructuralChainRowsByVarId[varId] = [];
|
|
2267
|
+
sessionStructuralChainRowsByVarId[varId].push(row);
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
/** One States-tab row -> Conversion.io chain-set shape (matches applyChangesetEntry). */
|
|
2271
|
+
function stateChangeToChainSet(c) {
|
|
2272
|
+
if (!c || !c.selector) return null;
|
|
2273
|
+
if (c.cssProp) {
|
|
2274
|
+
return { selector: c.selector, type: 'style', property: c.cssProp, value: c.value };
|
|
2275
|
+
}
|
|
2276
|
+
switch (c.inputId) {
|
|
2277
|
+
case 'pp-text':
|
|
2278
|
+
return { selector: c.selector, type: 'content', value: c.value };
|
|
2279
|
+
case 'pp-html':
|
|
2280
|
+
return { selector: c.selector, type: 'content', html: c.value };
|
|
2281
|
+
case 'pp-cls':
|
|
2282
|
+
return { selector: c.selector, type: 'attribute', attribute: 'class', value: c.value };
|
|
2283
|
+
case 'pp-id':
|
|
2284
|
+
return { selector: c.selector, type: 'attribute', attribute: 'id', value: c.value };
|
|
2285
|
+
case 'pp-href':
|
|
2286
|
+
return { selector: c.selector, type: 'attribute', attribute: 'href', value: c.value };
|
|
2287
|
+
case 'pp-target':
|
|
2288
|
+
return { selector: c.selector, type: 'attribute', attribute: 'target', value: c.value };
|
|
2289
|
+
case 'pp-src':
|
|
2290
|
+
return { selector: c.selector, type: 'attribute', attribute: 'src', value: c.value };
|
|
2291
|
+
case 'pp-alt':
|
|
2292
|
+
return { selector: c.selector, type: 'attribute', attribute: 'alt', value: c.value };
|
|
2293
|
+
case 'pp-ph':
|
|
2294
|
+
return { selector: c.selector, type: 'attribute', attribute: 'placeholder', value: c.value };
|
|
2295
|
+
case 'pp-css':
|
|
2296
|
+
return { selector: c.selector, type: 'attribute', attribute: 'style', value: c.value };
|
|
2297
|
+
case 'pp-mob-css':
|
|
2298
|
+
return { selector: c.selector, type: 'attribute', attribute: 'data-mobile-css', value: c.value };
|
|
2299
|
+
case 'pp-tab-css':
|
|
2300
|
+
return { selector: c.selector, type: 'attribute', attribute: 'data-tablet-css', value: c.value };
|
|
2301
|
+
default:
|
|
2302
|
+
return null;
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
|
|
1891
2306
|
function countUnresolvedGranularSelectors(iframeDoc, entries) {
|
|
1892
2307
|
if (!iframeDoc || !entries || !entries.length) return 0;
|
|
1893
2308
|
var n = 0;
|
|
1894
2309
|
for (var i = 0; i < entries.length; i++) {
|
|
1895
|
-
|
|
1896
|
-
try { el = iframeDoc.querySelector(entries[i].selector); } catch(_) {}
|
|
1897
|
-
if (!el) n++;
|
|
2310
|
+
if (!querySelectorResolved(iframeDoc, entries[i].selector)) n++;
|
|
1898
2311
|
}
|
|
1899
2312
|
return n;
|
|
1900
2313
|
}
|
|
1901
2314
|
|
|
1902
2315
|
function applyGranularChangesetEntries(iframeDoc, entries) {
|
|
1903
2316
|
if (!iframeDoc || !entries || !entries.length) return;
|
|
1904
|
-
|
|
1905
|
-
|
|
2317
|
+
beginSuppressIframeMutationDirty();
|
|
2318
|
+
try {
|
|
2319
|
+
for (var i = 0; i < entries.length; i++) {
|
|
2320
|
+
applyChangesetEntry(entries[i], iframeDoc);
|
|
2321
|
+
}
|
|
2322
|
+
} finally {
|
|
2323
|
+
endSuppressIframeMutationDirty();
|
|
1906
2324
|
}
|
|
1907
2325
|
}
|
|
1908
2326
|
|
|
@@ -1957,6 +2375,30 @@ function flushPendingGranularChangesets() {
|
|
|
1957
2375
|
scheduleGranularChangesetReapply();
|
|
1958
2376
|
}
|
|
1959
2377
|
|
|
2378
|
+
/** Stable key for structural changesets (insert/reorder) to avoid double-apply across early + full paint. */
|
|
2379
|
+
function structuralChangesetDedupKey(entry) {
|
|
2380
|
+
var nt = normalizeChangesetType(entry);
|
|
2381
|
+
if (!entry || (nt !== 'insert' && nt !== 'reorder')) return '';
|
|
2382
|
+
var vid = activeVarId || '';
|
|
2383
|
+
try {
|
|
2384
|
+
return (
|
|
2385
|
+
vid +
|
|
2386
|
+
'\0' +
|
|
2387
|
+
nt +
|
|
2388
|
+
'\0' +
|
|
2389
|
+
entry.selector +
|
|
2390
|
+
'\0' +
|
|
2391
|
+
String(entry.action || '') +
|
|
2392
|
+
'\0' +
|
|
2393
|
+
String(entry.html != null ? entry.html : '').slice(0, 240) +
|
|
2394
|
+
'\0' +
|
|
2395
|
+
String(entry.targetSelector || '')
|
|
2396
|
+
);
|
|
2397
|
+
} catch(_) {
|
|
2398
|
+
return vid + '\0' + nt + '\0' + entry.selector;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
|
|
1960
2402
|
/**
|
|
1961
2403
|
* Apply a single changeset entry inside the editor iframe.
|
|
1962
2404
|
* Backend format: { selector, type: 'content'|'style'|'attribute'|'insert'|'remove', ... }
|
|
@@ -1976,21 +2418,34 @@ function applyChangesetEntry(entry, iframeDoc) {
|
|
|
1976
2418
|
return;
|
|
1977
2419
|
}
|
|
1978
2420
|
|
|
2421
|
+
var structuralDedupeKey = structuralChangesetDedupKey(entry);
|
|
2422
|
+
if (structuralDedupeKey && appliedStructuralChangesetKeys[structuralDedupeKey]) return;
|
|
2423
|
+
|
|
1979
2424
|
// \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
|
|
1980
|
-
var el =
|
|
1981
|
-
try { el = iframeDoc.querySelector(entry.selector); } catch(_) {}
|
|
2425
|
+
var el = querySelectorResolved(iframeDoc, entry.selector);
|
|
1982
2426
|
if (!el) return;
|
|
1983
2427
|
|
|
1984
2428
|
captureChangesetSnapshotBeforeApply(entry, el, iframeDoc);
|
|
1985
2429
|
|
|
1986
|
-
switch (entry
|
|
2430
|
+
switch (normalizeChangesetType(entry)) {
|
|
1987
2431
|
case 'content':
|
|
1988
2432
|
if (entry.html != null) el.innerHTML = entry.html;
|
|
1989
2433
|
else if (entry.value != null) el.textContent = entry.value;
|
|
1990
2434
|
break;
|
|
1991
2435
|
case 'style':
|
|
1992
|
-
if (entry.property
|
|
1993
|
-
|
|
2436
|
+
if (entry.property) {
|
|
2437
|
+
var propKebab = entry.property;
|
|
2438
|
+
var cam = camelize(propKebab);
|
|
2439
|
+
if (entry.value == null || entry.value === '') {
|
|
2440
|
+
try { el.style.removeProperty(propKebab); } catch(_) {}
|
|
2441
|
+
try { if (cam in el.style) el.style[cam] = ''; } catch(__) {}
|
|
2442
|
+
} else {
|
|
2443
|
+
try {
|
|
2444
|
+
el.style.setProperty(propKebab, entry.value, 'important');
|
|
2445
|
+
} catch(_) {
|
|
2446
|
+
el.style[cam] = entry.value;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
1994
2449
|
}
|
|
1995
2450
|
break;
|
|
1996
2451
|
case 'attribute':
|
|
@@ -2001,11 +2456,23 @@ function applyChangesetEntry(entry, iframeDoc) {
|
|
|
2001
2456
|
case 'insert': {
|
|
2002
2457
|
var pos = entry.action === 'before' ? 'beforebegin' : 'afterend';
|
|
2003
2458
|
if (entry.html) el.insertAdjacentHTML(pos, entry.html);
|
|
2459
|
+
if (structuralDedupeKey) appliedStructuralChangesetKeys[structuralDedupeKey] = true;
|
|
2004
2460
|
break;
|
|
2005
2461
|
}
|
|
2006
2462
|
case 'remove':
|
|
2007
2463
|
el.style.display = 'none';
|
|
2008
2464
|
break;
|
|
2465
|
+
case 'reorder': {
|
|
2466
|
+
var target = entry.targetSelector ? querySelectorResolved(iframeDoc, entry.targetSelector) : null;
|
|
2467
|
+
if (!target || !el.parentNode || !target.parentNode) break;
|
|
2468
|
+
if (entry.action === 'before') {
|
|
2469
|
+
target.parentNode.insertBefore(el, target);
|
|
2470
|
+
} else {
|
|
2471
|
+
target.parentNode.insertBefore(el, target.nextSibling);
|
|
2472
|
+
}
|
|
2473
|
+
if (structuralDedupeKey) appliedStructuralChangesetKeys[structuralDedupeKey] = true;
|
|
2474
|
+
break;
|
|
2475
|
+
}
|
|
2009
2476
|
default: break;
|
|
2010
2477
|
}
|
|
2011
2478
|
}
|
|
@@ -2020,19 +2487,24 @@ function applyActiveVariationHtml() {
|
|
|
2020
2487
|
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2021
2488
|
var cs = parseVariationChangesets(variation);
|
|
2022
2489
|
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2490
|
+
beginSuppressIframeMutationDirty();
|
|
2491
|
+
try {
|
|
2492
|
+
// If we have an in-session HTML snapshot, use it (user edited in this session)
|
|
2493
|
+
var saved = varHtmlCache[activeVarId];
|
|
2494
|
+
if (saved) {
|
|
2495
|
+
try { iframeDoc.body.innerHTML = saved; } catch(_) {}
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2029
2498
|
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2499
|
+
if (!cs.length) return;
|
|
2500
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2501
|
+
applyChangesetEntry(cs[i], iframeDoc);
|
|
2502
|
+
}
|
|
2503
|
+
// Selectors often miss on first paint (client-rendered sections). Retry via timer + mutations.
|
|
2504
|
+
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2505
|
+
} finally {
|
|
2506
|
+
endSuppressIframeMutationDirty();
|
|
2033
2507
|
}
|
|
2034
|
-
// Selectors often miss on first paint (client-rendered sections). Retry via timer + mutations.
|
|
2035
|
-
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2036
2508
|
}
|
|
2037
2509
|
|
|
2038
2510
|
function changesetsHaveBodySnapshot(cs) {
|
|
@@ -2043,6 +2515,23 @@ function changesetsHaveBodySnapshot(cs) {
|
|
|
2043
2515
|
return false;
|
|
2044
2516
|
}
|
|
2045
2517
|
|
|
2518
|
+
/** Rows to persist for this variation on Finalize (same chain-set model as EditorShell \u2014 never __vvveb_body__). */
|
|
2519
|
+
function buildPersistedChainSetsForVariation(v) {
|
|
2520
|
+
if (!v || !v._id) return [];
|
|
2521
|
+
var parsed = parseVariationChangesets(v);
|
|
2522
|
+
var base = filterGranularChangesetEntries(parsed);
|
|
2523
|
+
var sessionExtra = sessionStructuralChainRowsByVarId[v._id] || [];
|
|
2524
|
+
if (v._id !== activeVarId) {
|
|
2525
|
+
return mergeGranularChainSets(base, sessionExtra);
|
|
2526
|
+
}
|
|
2527
|
+
var overlay = [];
|
|
2528
|
+
for (var si = 0; si < stateChanges.length; si++) {
|
|
2529
|
+
var row = stateChangeToChainSet(stateChanges[si]);
|
|
2530
|
+
if (row) overlay.push(row);
|
|
2531
|
+
}
|
|
2532
|
+
return mergeGranularChainSets(mergeGranularChainSets(base, sessionExtra), overlay);
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2046
2535
|
/**
|
|
2047
2536
|
* While document.readyState === 'loading', apply only granular changesets (no __vvveb_body__
|
|
2048
2537
|
* replacement) so the first painted nodes can receive edits before iframe/window load completes.
|
|
@@ -2053,10 +2542,15 @@ function applyVariationGranularOnly(iframeDoc) {
|
|
|
2053
2542
|
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2054
2543
|
var cs = parseVariationChangesets(variation);
|
|
2055
2544
|
if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
|
|
2056
|
-
|
|
2057
|
-
|
|
2545
|
+
beginSuppressIframeMutationDirty();
|
|
2546
|
+
try {
|
|
2547
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2548
|
+
applyChangesetEntry(cs[i], iframeDoc);
|
|
2549
|
+
}
|
|
2550
|
+
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2551
|
+
} finally {
|
|
2552
|
+
endSuppressIframeMutationDirty();
|
|
2058
2553
|
}
|
|
2059
|
-
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2060
2554
|
}
|
|
2061
2555
|
|
|
2062
2556
|
/** Re-try granular entries without resetting pending registration (use between poll ticks). */
|
|
@@ -2066,8 +2560,13 @@ function reapplyActiveVariationGranular(iframeDoc) {
|
|
|
2066
2560
|
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2067
2561
|
var cs = parseVariationChangesets(variation);
|
|
2068
2562
|
if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
|
|
2069
|
-
|
|
2070
|
-
|
|
2563
|
+
beginSuppressIframeMutationDirty();
|
|
2564
|
+
try {
|
|
2565
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2566
|
+
applyChangesetEntry(cs[i], iframeDoc);
|
|
2567
|
+
}
|
|
2568
|
+
} finally {
|
|
2569
|
+
endSuppressIframeMutationDirty();
|
|
2071
2570
|
}
|
|
2072
2571
|
}
|
|
2073
2572
|
|
|
@@ -2129,9 +2628,14 @@ function startIframeContentApplyWatcher(navGen) {
|
|
|
2129
2628
|
|
|
2130
2629
|
// \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
|
|
2131
2630
|
function selectElement(el) {
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2631
|
+
beginSuppressIframeMutationDirty();
|
|
2632
|
+
try {
|
|
2633
|
+
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} }
|
|
2634
|
+
selectedEl = el;
|
|
2635
|
+
try { el.classList.add('vve-selected'); } catch(_) {}
|
|
2636
|
+
} finally {
|
|
2637
|
+
endSuppressIframeMutationDirty();
|
|
2638
|
+
}
|
|
2135
2639
|
document.getElementById('bc-path').textContent = buildSelector(el);
|
|
2136
2640
|
document.getElementById('bc-path').style.color = 'var(--accent-txt)';
|
|
2137
2641
|
document.getElementById('no-sel').style.display = 'none';
|
|
@@ -2146,7 +2650,12 @@ function selectElement(el) {
|
|
|
2146
2650
|
|
|
2147
2651
|
function deselectElement() {
|
|
2148
2652
|
setDragHandleActive(false);
|
|
2149
|
-
|
|
2653
|
+
beginSuppressIframeMutationDirty();
|
|
2654
|
+
try {
|
|
2655
|
+
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} selectedEl = null; }
|
|
2656
|
+
} finally {
|
|
2657
|
+
endSuppressIframeMutationDirty();
|
|
2658
|
+
}
|
|
2150
2659
|
document.getElementById('no-sel').style.display = '';
|
|
2151
2660
|
document.getElementById('el-info').style.display = 'none';
|
|
2152
2661
|
document.getElementById('rp-accordion').style.display = 'none';
|
|
@@ -2170,18 +2679,27 @@ function injectIframeSelectionStyles(doc) {
|
|
|
2170
2679
|
'html.vve-drag-armed .vve-selected{cursor:grab!important;}' +
|
|
2171
2680
|
'.vve-dragging{opacity:0.92!important;outline:2px dashed #f59e0b!important;' +
|
|
2172
2681
|
'outline-offset:2px!important;cursor:grabbing!important;box-shadow:none!important;}';
|
|
2173
|
-
|
|
2682
|
+
beginSuppressIframeMutationDirty();
|
|
2683
|
+
try {
|
|
2684
|
+
doc.head.appendChild(st);
|
|
2685
|
+
} finally {
|
|
2686
|
+
endSuppressIframeMutationDirty();
|
|
2687
|
+
}
|
|
2174
2688
|
}
|
|
2175
2689
|
|
|
2176
2690
|
function setDragHandleActive(on) {
|
|
2177
2691
|
dragHandleActive = !!on;
|
|
2178
2692
|
var b = document.getElementById('sf-drag');
|
|
2179
2693
|
if (b) b.classList.toggle('active', dragHandleActive);
|
|
2694
|
+
beginSuppressIframeMutationDirty();
|
|
2180
2695
|
try {
|
|
2181
2696
|
var iframe = document.getElementById('iframeId');
|
|
2182
2697
|
var d = iframe && iframe.contentDocument && iframe.contentDocument.documentElement;
|
|
2183
2698
|
if (d) d.classList.toggle('vve-drag-armed', dragHandleActive);
|
|
2184
|
-
} catch(_) {
|
|
2699
|
+
} catch(_) {
|
|
2700
|
+
} finally {
|
|
2701
|
+
endSuppressIframeMutationDirty();
|
|
2702
|
+
}
|
|
2185
2703
|
}
|
|
2186
2704
|
|
|
2187
2705
|
function positionSelectionToolbar() {
|
|
@@ -2251,34 +2769,72 @@ function selectElementFromTree(el) {
|
|
|
2251
2769
|
|
|
2252
2770
|
function duplicateSelectedEl() {
|
|
2253
2771
|
if (!selectedEl || !selectedEl.parentNode) return;
|
|
2772
|
+
var anchorSel = buildSelector(selectedEl);
|
|
2254
2773
|
var clone = selectedEl.cloneNode(true);
|
|
2255
2774
|
clone.classList.remove('vve-selected');
|
|
2256
2775
|
var all = clone.querySelectorAll ? clone.querySelectorAll('.vve-selected') : [];
|
|
2257
2776
|
for (var i = 0; i < all.length; i++) all[i].classList.remove('vve-selected');
|
|
2258
2777
|
clone.style.visibility = '';
|
|
2259
2778
|
if (clone.removeAttribute) clone.removeAttribute('data-vve-hidden');
|
|
2779
|
+
stripDataVveInstanceSubtree(clone);
|
|
2780
|
+
try {
|
|
2781
|
+
if (clone.id) clone.removeAttribute('id');
|
|
2782
|
+
} catch(_) {}
|
|
2783
|
+
try {
|
|
2784
|
+
clone.setAttribute('data-vve-instance', generateVveInstanceId());
|
|
2785
|
+
} catch(_) {}
|
|
2786
|
+
if (activeVarId) {
|
|
2787
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
2788
|
+
selector: anchorSel,
|
|
2789
|
+
type: 'insert',
|
|
2790
|
+
action: 'after',
|
|
2791
|
+
html: clone.outerHTML,
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2260
2794
|
selectedEl.parentNode.insertBefore(clone, selectedEl.nextSibling);
|
|
2261
|
-
|
|
2795
|
+
saveCurrentVariationHtml();
|
|
2796
|
+
recomputeEditorDirty();
|
|
2262
2797
|
scheduleDomTreeRefresh();
|
|
2263
2798
|
selectElement(clone);
|
|
2264
2799
|
}
|
|
2265
2800
|
|
|
2266
2801
|
function toggleHideSelectedEl() {
|
|
2267
2802
|
if (!selectedEl) return;
|
|
2803
|
+
var hidSel = buildSelector(selectedEl);
|
|
2268
2804
|
if (selectedEl.getAttribute('data-vve-hidden') === '1') {
|
|
2269
2805
|
selectedEl.style.visibility = '';
|
|
2270
2806
|
selectedEl.removeAttribute('data-vve-hidden');
|
|
2807
|
+
if (activeVarId) {
|
|
2808
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
2809
|
+
selector: hidSel,
|
|
2810
|
+
type: 'style',
|
|
2811
|
+
property: 'visibility',
|
|
2812
|
+
value: '',
|
|
2813
|
+
});
|
|
2814
|
+
}
|
|
2271
2815
|
} else {
|
|
2272
2816
|
selectedEl.style.visibility = 'hidden';
|
|
2273
2817
|
selectedEl.setAttribute('data-vve-hidden', '1');
|
|
2818
|
+
if (activeVarId) {
|
|
2819
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
2820
|
+
selector: hidSel,
|
|
2821
|
+
type: 'style',
|
|
2822
|
+
property: 'visibility',
|
|
2823
|
+
value: 'hidden',
|
|
2824
|
+
});
|
|
2825
|
+
}
|
|
2274
2826
|
}
|
|
2275
|
-
|
|
2827
|
+
saveCurrentVariationHtml();
|
|
2828
|
+
recomputeEditorDirty();
|
|
2276
2829
|
}
|
|
2277
2830
|
|
|
2278
2831
|
function deleteSelectedEl() {
|
|
2279
2832
|
if (!selectedEl || !selectedEl.parentNode) return;
|
|
2833
|
+
var delSel = buildSelector(selectedEl);
|
|
2280
2834
|
selectedEl.remove();
|
|
2281
|
-
|
|
2835
|
+
if (activeVarId) appendSessionStructuralChainRow(activeVarId, { selector: delSel, type: 'remove' });
|
|
2836
|
+
saveCurrentVariationHtml();
|
|
2837
|
+
recomputeEditorDirty();
|
|
2282
2838
|
deselectElement();
|
|
2283
2839
|
scheduleDomTreeRefresh();
|
|
2284
2840
|
}
|
|
@@ -2570,14 +3126,12 @@ function renderImageSection(el) {
|
|
|
2570
3126
|
var prev = document.querySelector('.img-preview');
|
|
2571
3127
|
if (prev) prev.src = srcInp.value;
|
|
2572
3128
|
logChange(sel, 'pp-src', srcInp.value, el, orig);
|
|
2573
|
-
markDirty();
|
|
2574
3129
|
});
|
|
2575
3130
|
var altInp = document.getElementById('pp-img-alt');
|
|
2576
3131
|
if (altInp) altInp.addEventListener('input', function() {
|
|
2577
3132
|
var orig = getOriginalValue('pp-alt', el);
|
|
2578
3133
|
el.setAttribute('alt', altInp.value);
|
|
2579
3134
|
logChange(sel, 'pp-alt', altInp.value, el, orig);
|
|
2580
|
-
markDirty();
|
|
2581
3135
|
});
|
|
2582
3136
|
|
|
2583
3137
|
// Wire srcset entry inputs
|
|
@@ -2586,7 +3140,8 @@ function renderImageSection(el) {
|
|
|
2586
3140
|
var dInp = document.getElementById('pp-se-desc-'+i);
|
|
2587
3141
|
function flushSrcset() {
|
|
2588
3142
|
el.setAttribute('srcset', buildSrcset(_srcsetEntries));
|
|
2589
|
-
|
|
3143
|
+
saveCurrentVariationHtml();
|
|
3144
|
+
recomputeEditorDirty();
|
|
2590
3145
|
}
|
|
2591
3146
|
if (uInp) uInp.addEventListener('input', function(){ _srcsetEntries[i].url = uInp.value; flushSrcset(); });
|
|
2592
3147
|
if (dInp) dInp.addEventListener('input', function(){ _srcsetEntries[i].descriptor = dInp.value; flushSrcset(); });
|
|
@@ -2604,7 +3159,8 @@ function renderImageSection(el) {
|
|
|
2604
3159
|
}
|
|
2605
3160
|
function flushSizes() {
|
|
2606
3161
|
el.setAttribute('sizes', buildSizes(_sizesEntries));
|
|
2607
|
-
|
|
3162
|
+
saveCurrentVariationHtml();
|
|
3163
|
+
recomputeEditorDirty();
|
|
2608
3164
|
}
|
|
2609
3165
|
if (opInp) opInp.addEventListener('change', function(){ buildCondition(); flushSizes(); });
|
|
2610
3166
|
if (valInp) valInp.addEventListener('input', function(){ buildCondition(); flushSizes(); });
|
|
@@ -2618,7 +3174,12 @@ function addSrcsetEntry() {
|
|
|
2618
3174
|
}
|
|
2619
3175
|
function removeSrcsetEntry(i) {
|
|
2620
3176
|
_srcsetEntries.splice(i, 1);
|
|
2621
|
-
if (_imageEl) {
|
|
3177
|
+
if (_imageEl) {
|
|
3178
|
+
_imageEl.setAttribute('srcset', buildSrcset(_srcsetEntries));
|
|
3179
|
+
renderImageSection(_imageEl);
|
|
3180
|
+
saveCurrentVariationHtml();
|
|
3181
|
+
recomputeEditorDirty();
|
|
3182
|
+
}
|
|
2622
3183
|
}
|
|
2623
3184
|
function addSizesEntry() {
|
|
2624
3185
|
_sizesEntries.push({condition:'max-width: 760px', value:'760px'});
|
|
@@ -2626,7 +3187,12 @@ function addSizesEntry() {
|
|
|
2626
3187
|
}
|
|
2627
3188
|
function removeSizesEntry(i) {
|
|
2628
3189
|
_sizesEntries.splice(i, 1);
|
|
2629
|
-
if (_imageEl) {
|
|
3190
|
+
if (_imageEl) {
|
|
3191
|
+
_imageEl.setAttribute('sizes', buildSizes(_sizesEntries));
|
|
3192
|
+
renderImageSection(_imageEl);
|
|
3193
|
+
saveCurrentVariationHtml();
|
|
3194
|
+
recomputeEditorDirty();
|
|
3195
|
+
}
|
|
2630
3196
|
}
|
|
2631
3197
|
|
|
2632
3198
|
// \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
|
|
@@ -2831,20 +3397,51 @@ function renderRightPanel(el) {
|
|
|
2831
3397
|
var orig = getOriginalValue(b[0], el);
|
|
2832
3398
|
b[1](inp.value);
|
|
2833
3399
|
logChange(sel, b[0], inp.value, el, orig);
|
|
2834
|
-
markDirty();
|
|
2835
3400
|
});
|
|
2836
3401
|
});
|
|
2837
3402
|
}
|
|
2838
3403
|
|
|
2839
3404
|
// \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
|
|
3405
|
+
function generateVveInstanceId() {
|
|
3406
|
+
return 'v' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10);
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
/** Editor-assigned clone marker so duplicated subtrees do not share the same CSS path as the original. */
|
|
3410
|
+
function stripDataVveInstanceSubtree(root) {
|
|
3411
|
+
if (!root || root.nodeType !== 1) return;
|
|
3412
|
+
try {
|
|
3413
|
+
root.removeAttribute('data-vve-instance');
|
|
3414
|
+
} catch(_) {}
|
|
3415
|
+
var sub = root.querySelectorAll ? root.querySelectorAll('[data-vve-instance]') : [];
|
|
3416
|
+
for (var i = 0; i < sub.length; i++) {
|
|
3417
|
+
try {
|
|
3418
|
+
sub[i].removeAttribute('data-vve-instance');
|
|
3419
|
+
} catch(_) {}
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
|
|
2840
3423
|
function buildSelector(el) {
|
|
2841
3424
|
if (!el) return '';
|
|
3425
|
+
var doc = el.ownerDocument || document;
|
|
3426
|
+
var inst = el.getAttribute && el.getAttribute('data-vve-instance');
|
|
3427
|
+
if (inst && String(inst).trim()) {
|
|
3428
|
+
var safe = String(inst).split('"').join('\\"');
|
|
3429
|
+
var attrSel = '[data-vve-instance="' + safe + '"]';
|
|
3430
|
+
try {
|
|
3431
|
+
if (doc.querySelectorAll(attrSel).length === 1) return attrSel;
|
|
3432
|
+
} catch(_) {}
|
|
3433
|
+
}
|
|
2842
3434
|
if (el.id) return '#' + el.id;
|
|
2843
3435
|
var parts = [], node = el, depth = 0;
|
|
2844
3436
|
while (node && node.nodeType === 1 && depth < 5) {
|
|
2845
3437
|
if (node.id) { parts.unshift('#' + node.id); break; }
|
|
2846
3438
|
var p = node.tagName.toLowerCase();
|
|
2847
|
-
if (node.classList && node.classList.length)
|
|
3439
|
+
if (node.classList && node.classList.length) {
|
|
3440
|
+
var clsArr = Array.from(node.classList).filter(function(c) {
|
|
3441
|
+
return c.indexOf('vve-') !== 0;
|
|
3442
|
+
});
|
|
3443
|
+
if (clsArr.length) p += '.' + clsArr.slice(0, 2).join('.');
|
|
3444
|
+
}
|
|
2848
3445
|
var idx = 1, sib = node.previousElementSibling;
|
|
2849
3446
|
while (sib) { if (sib.tagName === node.tagName) idx++; sib = sib.previousElementSibling; }
|
|
2850
3447
|
if (idx > 1) p += ':nth-of-type(' + idx + ')';
|
|
@@ -2855,6 +3452,71 @@ function buildSelector(el) {
|
|
|
2855
3452
|
return parts.join(' > ');
|
|
2856
3453
|
}
|
|
2857
3454
|
|
|
3455
|
+
/**
|
|
3456
|
+
* Strip editor-only .vve-* class tokens from a selector string (fixes DB rows saved while an element was selected).
|
|
3457
|
+
*/
|
|
3458
|
+
function sanitizeSelectorForMatch(sel) {
|
|
3459
|
+
if (!sel || typeof sel !== 'string') return '';
|
|
3460
|
+
var s0 = sel.replace(/.vve-[a-zA-Z0-9_-]+/gi, '');
|
|
3461
|
+
var parts = s0.split(/s*>s*/).map(function(seg) {
|
|
3462
|
+
var t = seg.replace(/.+/g, '.').replace(/.$/, '');
|
|
3463
|
+
return t.trim();
|
|
3464
|
+
});
|
|
3465
|
+
return parts.filter(Boolean).join(' > ');
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
/** Drop the rightmost :nth-of-type(n) (hydration / layout often shifts sibling indices). */
|
|
3469
|
+
function stripRightmostNthOfType(sel) {
|
|
3470
|
+
if (!sel || typeof sel !== 'string') return null;
|
|
3471
|
+
var idx = sel.lastIndexOf(':nth-of-type(');
|
|
3472
|
+
if (idx === -1) return null;
|
|
3473
|
+
var j = idx + ':nth-of-type('.length;
|
|
3474
|
+
while (j < sel.length && sel.charCodeAt(j) >= 48 && sel.charCodeAt(j) <= 57) j++;
|
|
3475
|
+
if (j >= sel.length || sel.charAt(j) !== ')') return null;
|
|
3476
|
+
return (sel.slice(0, idx) + sel.slice(j + 1))
|
|
3477
|
+
.replace(/s{2,}/g, ' ')
|
|
3478
|
+
.replace(/s*>s*>/g, ' >')
|
|
3479
|
+
.trim();
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3482
|
+
function querySelectorResolved(iframeDoc, selector) {
|
|
3483
|
+
if (!iframeDoc || !selector) return null;
|
|
3484
|
+
var seen = {};
|
|
3485
|
+
function tryOne(s) {
|
|
3486
|
+
if (!s || seen[s]) return null;
|
|
3487
|
+
seen[s] = true;
|
|
3488
|
+
try {
|
|
3489
|
+
return iframeDoc.querySelector(s) || null;
|
|
3490
|
+
} catch(_) {
|
|
3491
|
+
return null;
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
function walkRelax(base) {
|
|
3495
|
+
if (!base) return null;
|
|
3496
|
+
var el = tryOne(base);
|
|
3497
|
+
if (el) return el;
|
|
3498
|
+
var cur = base;
|
|
3499
|
+
for (var g = 0; g < 28; g++) {
|
|
3500
|
+
var nxt = stripRightmostNthOfType(cur);
|
|
3501
|
+
if (!nxt || nxt === cur) break;
|
|
3502
|
+
cur = nxt;
|
|
3503
|
+
el = tryOne(cur);
|
|
3504
|
+
if (el) return el;
|
|
3505
|
+
}
|
|
3506
|
+
return null;
|
|
3507
|
+
}
|
|
3508
|
+
var alt = sanitizeSelectorForMatch(selector);
|
|
3509
|
+
// Prefer sanitized + nth relax FIRST: raw selectors often still contain .vve-* from
|
|
3510
|
+
// save-time selection; those only match after clicking (we re-add vve-selected).
|
|
3511
|
+
var el = walkRelax(alt || selector);
|
|
3512
|
+
if (el) return el;
|
|
3513
|
+
if (alt !== selector) {
|
|
3514
|
+
el = walkRelax(selector);
|
|
3515
|
+
if (el) return el;
|
|
3516
|
+
}
|
|
3517
|
+
return null;
|
|
3518
|
+
}
|
|
3519
|
+
|
|
2858
3520
|
// \u2500\u2500 Iframe interaction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2859
3521
|
function repositionDragSibling(dragEl, clientY) {
|
|
2860
3522
|
var p = dragEl.parentElement;
|
|
@@ -2880,6 +3542,27 @@ function repositionDragSibling(dragEl, clientY) {
|
|
|
2880
3542
|
}
|
|
2881
3543
|
}
|
|
2882
3544
|
|
|
3545
|
+
function recordReorderAfterDrag(movedEl) {
|
|
3546
|
+
if (!activeVarId || !movedEl || !movedEl.parentElement) return;
|
|
3547
|
+
var prev = movedEl.previousElementSibling;
|
|
3548
|
+
var next = movedEl.nextElementSibling;
|
|
3549
|
+
if (prev) {
|
|
3550
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
3551
|
+
selector: buildSelector(movedEl),
|
|
3552
|
+
type: 'reorder',
|
|
3553
|
+
targetSelector: buildSelector(prev),
|
|
3554
|
+
action: 'after',
|
|
3555
|
+
});
|
|
3556
|
+
} else if (next) {
|
|
3557
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
3558
|
+
selector: buildSelector(movedEl),
|
|
3559
|
+
type: 'reorder',
|
|
3560
|
+
targetSelector: buildSelector(next),
|
|
3561
|
+
action: 'before',
|
|
3562
|
+
});
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
|
|
2883
3566
|
function attachDragReposition() {
|
|
2884
3567
|
try {
|
|
2885
3568
|
var iframe = document.getElementById('iframeId');
|
|
@@ -2933,7 +3616,9 @@ function attachDragReposition() {
|
|
|
2933
3616
|
} catch(_) {}
|
|
2934
3617
|
suppressClickUntil = Date.now() + 200;
|
|
2935
3618
|
setDragHandleActive(false);
|
|
2936
|
-
|
|
3619
|
+
if (activeVarId) recordReorderAfterDrag(selectedEl);
|
|
3620
|
+
saveCurrentVariationHtml();
|
|
3621
|
+
recomputeEditorDirty();
|
|
2937
3622
|
updateSelectionToolbar();
|
|
2938
3623
|
scheduleDomTreeRefresh();
|
|
2939
3624
|
}
|
|
@@ -2986,11 +3671,31 @@ function attachChangeObserver() {
|
|
|
2986
3671
|
changeObserver = null;
|
|
2987
3672
|
changeObserverDoc = null;
|
|
2988
3673
|
}
|
|
2989
|
-
changeObserver = new MutationObserver(function() {
|
|
2990
|
-
|
|
2991
|
-
|
|
3674
|
+
changeObserver = new MutationObserver(function(mutations) {
|
|
3675
|
+
// Dirty state is derived from changesets baseline + stateChanges (not raw DOM mutations).
|
|
3676
|
+
var bodyReplaced = false;
|
|
3677
|
+
for (var mi = 0; mi < mutations.length; mi++) {
|
|
3678
|
+
var m = mutations[mi];
|
|
3679
|
+
if (
|
|
3680
|
+
m &&
|
|
3681
|
+
m.type === 'childList' &&
|
|
3682
|
+
m.target === doc.body &&
|
|
3683
|
+
m.addedNodes &&
|
|
3684
|
+
m.removedNodes &&
|
|
3685
|
+
m.addedNodes.length > 0 &&
|
|
3686
|
+
m.removedNodes.length > 0
|
|
3687
|
+
) {
|
|
3688
|
+
bodyReplaced = true;
|
|
3689
|
+
break;
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
if (bodyReplaced) {
|
|
3693
|
+
// Page JS replaced body children; allow structural rows (insert/reorder) to apply again.
|
|
3694
|
+
appliedStructuralChangesetKeys = {};
|
|
3695
|
+
}
|
|
2992
3696
|
scheduleDomTreeRefresh();
|
|
2993
3697
|
scheduleGranularChangesetReapply();
|
|
3698
|
+
scheduleConsistencyReconcile();
|
|
2994
3699
|
});
|
|
2995
3700
|
changeObserver.observe(doc.body, {
|
|
2996
3701
|
childList: true, subtree: true, attributes: true, characterData: true
|
|
@@ -3020,10 +3725,13 @@ function syncIframeInteractions(reason) {
|
|
|
3020
3725
|
attachClickHandler();
|
|
3021
3726
|
attachDragReposition();
|
|
3022
3727
|
attachChangeObserver();
|
|
3728
|
+
startConsistencyWatchdog(doc);
|
|
3729
|
+
scheduleConsistencyReconcile();
|
|
3023
3730
|
bindSelectionToolbarScroll();
|
|
3024
3731
|
var inp = document.getElementById('comp-search');
|
|
3025
3732
|
renderDomTree(inp ? inp.value : '');
|
|
3026
3733
|
updateSelectionToolbar();
|
|
3734
|
+
recomputeEditorDirty();
|
|
3027
3735
|
} catch(_) {}
|
|
3028
3736
|
}
|
|
3029
3737
|
|
|
@@ -3075,8 +3783,11 @@ function insertHtml(html) {
|
|
|
3075
3783
|
console.warn('[V2] insertHtml: iframe document not ready');
|
|
3076
3784
|
return;
|
|
3077
3785
|
}
|
|
3786
|
+
var htmlStr = String(html).trim();
|
|
3787
|
+
var anchorSel =
|
|
3788
|
+
selectedEl && selectedEl !== doc.body && selectedEl.parentNode ? buildSelector(selectedEl) : 'body';
|
|
3078
3789
|
var t = doc.createElement('template');
|
|
3079
|
-
t.innerHTML =
|
|
3790
|
+
t.innerHTML = htmlStr;
|
|
3080
3791
|
var frag = doc.createDocumentFragment();
|
|
3081
3792
|
var firstEl = null;
|
|
3082
3793
|
while (t.content.firstChild) {
|
|
@@ -3092,7 +3803,16 @@ function insertHtml(html) {
|
|
|
3092
3803
|
doc.body.appendChild(frag);
|
|
3093
3804
|
}
|
|
3094
3805
|
if (firstEl) selectElement(firstEl);
|
|
3095
|
-
|
|
3806
|
+
if (activeVarId) {
|
|
3807
|
+
appendSessionStructuralChainRow(activeVarId, {
|
|
3808
|
+
selector: anchorSel,
|
|
3809
|
+
type: 'insert',
|
|
3810
|
+
action: 'after',
|
|
3811
|
+
html: htmlStr,
|
|
3812
|
+
});
|
|
3813
|
+
}
|
|
3814
|
+
saveCurrentVariationHtml();
|
|
3815
|
+
recomputeEditorDirty();
|
|
3096
3816
|
scheduleDomTreeRefresh();
|
|
3097
3817
|
} catch(err) { console.warn('[V2] insertHtml:', err); }
|
|
3098
3818
|
}
|
|
@@ -3184,15 +3904,37 @@ document.getElementById('btn-close').addEventListener('click', handleClose);
|
|
|
3184
3904
|
function handleSave() {
|
|
3185
3905
|
saveCurrentVariationHtml();
|
|
3186
3906
|
var updatedVariations = variations.map(function(v) {
|
|
3187
|
-
var
|
|
3188
|
-
|
|
3189
|
-
|
|
3907
|
+
var prevParsed = parseVariationChangesets(v);
|
|
3908
|
+
var granularPrev = filterGranularChangesetEntries(prevParsed);
|
|
3909
|
+
var bodyOnlyLegacy = changesetsHaveBodySnapshot(prevParsed) && granularPrev.length === 0;
|
|
3910
|
+
|
|
3911
|
+
var rows = buildPersistedChainSetsForVariation(v);
|
|
3912
|
+
if (bodyOnlyLegacy && rows.length === 0) {
|
|
3913
|
+
return Object.assign({}, v);
|
|
3914
|
+
}
|
|
3915
|
+
var json = '[]';
|
|
3916
|
+
try {
|
|
3917
|
+
json = JSON.stringify(rows || []);
|
|
3918
|
+
} catch(_) {
|
|
3919
|
+
json = '[]';
|
|
3920
|
+
}
|
|
3921
|
+
return Object.assign({}, v, { changesets: json });
|
|
3190
3922
|
});
|
|
3923
|
+
variations = updatedVariations;
|
|
3924
|
+
varHtmlCache = {};
|
|
3925
|
+
sessionStructuralChainRowsByVarId = {};
|
|
3926
|
+
stateChanges = [];
|
|
3927
|
+
if (currentMainTab === 'states') renderStatesTab();
|
|
3928
|
+
captureBaselineFromVariations(variations);
|
|
3929
|
+
if (experimentData && Array.isArray(experimentData.variations)) {
|
|
3930
|
+
experimentData.variations = updatedVariations;
|
|
3931
|
+
}
|
|
3191
3932
|
send('save-experiment', { experimentId: experimentData ? experimentData.experimentId : null, variations: updatedVariations });
|
|
3192
|
-
|
|
3933
|
+
setEditorDirty(false);
|
|
3193
3934
|
}
|
|
3194
3935
|
|
|
3195
3936
|
function handleClose() {
|
|
3937
|
+
clearVisualEditorLocalStorage();
|
|
3196
3938
|
// Unsaved-changes UX lives in the parent (PlatformVisualEditorV2); avoid double confirm here.
|
|
3197
3939
|
send('close-editor', {});
|
|
3198
3940
|
}
|
|
@@ -3200,8 +3942,22 @@ function handleClose() {
|
|
|
3200
3942
|
// \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
|
|
3201
3943
|
document.addEventListener('keydown', function(e) {
|
|
3202
3944
|
var meta = e.metaKey || e.ctrlKey;
|
|
3203
|
-
if (meta && !e.shiftKey && e.key === 'z') {
|
|
3204
|
-
|
|
3945
|
+
if (meta && !e.shiftKey && e.key === 'z') {
|
|
3946
|
+
e.preventDefault();
|
|
3947
|
+
if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
|
|
3948
|
+
Vvveb.Undo.undo();
|
|
3949
|
+
saveCurrentVariationHtml();
|
|
3950
|
+
recomputeEditorDirty();
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
if (meta && e.shiftKey && e.key === 'z') {
|
|
3954
|
+
e.preventDefault();
|
|
3955
|
+
if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
|
|
3956
|
+
Vvveb.Undo.redo();
|
|
3957
|
+
saveCurrentVariationHtml();
|
|
3958
|
+
recomputeEditorDirty();
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3205
3961
|
if (meta && e.key === 's') { e.preventDefault(); handleSave(); }
|
|
3206
3962
|
if (e.key === 'Escape') {
|
|
3207
3963
|
var openTips = document.querySelectorAll('.ve-pl-tip.is-tip-open');
|