@accelerated-agency/visual-editor 0.3.1 → 0.3.2
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 +4 -0
- package/dist/vite.cjs +359 -60
- package/dist/vite.js +359 -60
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4647,6 +4647,7 @@ function PlatformVisualEditor({
|
|
|
4647
4647
|
}, [dirty, onDirtyChange]);
|
|
4648
4648
|
const loadPayload = useMemo(
|
|
4649
4649
|
() => ({
|
|
4650
|
+
iid: experiment?.iid,
|
|
4650
4651
|
experimentId: experiment?.experimentId,
|
|
4651
4652
|
name: experiment?.name,
|
|
4652
4653
|
status: experiment?.status,
|
|
@@ -4656,6 +4657,7 @@ function PlatformVisualEditor({
|
|
|
4656
4657
|
}),
|
|
4657
4658
|
[experiment]
|
|
4658
4659
|
);
|
|
4660
|
+
console.log("loadPayload", loadPayload);
|
|
4659
4661
|
useEffect(() => {
|
|
4660
4662
|
if (!editorReady) return;
|
|
4661
4663
|
const payloadKey = JSON.stringify(loadPayload);
|
|
@@ -4859,6 +4861,7 @@ function PlatformVisualEditorV2({
|
|
|
4859
4861
|
}, []);
|
|
4860
4862
|
const loadPayload = useMemo(
|
|
4861
4863
|
() => ({
|
|
4864
|
+
iid: experiment?.iid,
|
|
4862
4865
|
experimentId: experiment?.experimentId,
|
|
4863
4866
|
name: experiment?.name,
|
|
4864
4867
|
status: experiment?.status,
|
|
@@ -4868,6 +4871,7 @@ function PlatformVisualEditorV2({
|
|
|
4868
4871
|
}),
|
|
4869
4872
|
[experiment]
|
|
4870
4873
|
);
|
|
4874
|
+
console.log("loadPayload", loadPayload);
|
|
4871
4875
|
const editorSrc = useMemo(() => {
|
|
4872
4876
|
const safeBaseUrl = normalizeProxyBaseUrl(proxyBaseUrl);
|
|
4873
4877
|
return safeBaseUrl ? `${safeBaseUrl}/vvveb-editor` : "/vvveb-editor";
|
package/dist/vite.cjs
CHANGED
|
@@ -148,6 +148,17 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
148
148
|
.tb-save-txt{font-size:14px;color:#00C951;white-space:nowrap}
|
|
149
149
|
.tb-save-txt::before{content:'Saved'}
|
|
150
150
|
#dirty-dot.on~.tb-save-txt::before{content:'Unsaved'}
|
|
151
|
+
|
|
152
|
+
/* \u2500\u2500 In-editor toast notification \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
153
|
+
#ve-notification{
|
|
154
|
+
position:fixed;right:16px;top:64px;z-index:13000;max-width:360px;
|
|
155
|
+
padding:10px 12px;border-radius:8px;font-size:12px;line-height:1.35;
|
|
156
|
+
border:1px solid var(--border);background:#fff;color:var(--text);
|
|
157
|
+
box-shadow:0 10px 24px rgba(2,6,23,.18);display:none
|
|
158
|
+
}
|
|
159
|
+
#ve-notification.show{display:block}
|
|
160
|
+
#ve-notification.error{border-color:#fecaca;background:#fef2f2;color:#991b1b}
|
|
161
|
+
#ve-notification.success{border-color:#bbf7d0;background:#f0fdf4;color:#166534}
|
|
151
162
|
.tb-save-time{font-size:12px;color:#52525b;white-space:nowrap}
|
|
152
163
|
#dirty-dot.on~.tb-save-time{display:none}
|
|
153
164
|
/* Simulate + Finalize buttons */
|
|
@@ -455,6 +466,49 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
455
466
|
/* \u2500\u2500 Subgroup label \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
456
467
|
.sub-lbl{font-size:10px;text-transform:uppercase;color:var(--text-3);font-weight:700;letter-spacing:.05em;margin:8px 0 5px}
|
|
457
468
|
.sub-lbl:first-child{margin-top:0}
|
|
469
|
+
.sub-lbl-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:8px 0 5px}
|
|
470
|
+
.sub-lbl-row .sub-lbl{margin:0}
|
|
471
|
+
.css-expand-btn{
|
|
472
|
+
width:22px;height:22px;border:1px solid var(--border);border-radius:5px;background:#fff;
|
|
473
|
+
display:inline-flex;align-items:center;justify-content:center;cursor:pointer;color:var(--text-3);
|
|
474
|
+
transition:all .15s
|
|
475
|
+
}
|
|
476
|
+
.css-expand-btn:hover{border-color:var(--accent);color:var(--accent-txt);background:var(--accent-bg)}
|
|
477
|
+
|
|
478
|
+
/* \u2500\u2500 Custom CSS modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
479
|
+
#custom-css-modal{
|
|
480
|
+
position:fixed;inset:0;z-index:12000;background:rgba(15,23,42,.5);
|
|
481
|
+
display:none;align-items:center;justify-content:center;padding:20px
|
|
482
|
+
}
|
|
483
|
+
#custom-css-modal.open{display:flex}
|
|
484
|
+
.custom-css-dialog{
|
|
485
|
+
width:min(860px,96vw);max-height:86vh;background:#fff;border:1px solid var(--border);
|
|
486
|
+
border-radius:10px;box-shadow:0 20px 40px rgba(2,6,23,.35);display:flex;flex-direction:column;overflow:hidden
|
|
487
|
+
}
|
|
488
|
+
.custom-css-head{
|
|
489
|
+
display:flex;align-items:center;justify-content:space-between;gap:10px;
|
|
490
|
+
padding:10px 12px;border-bottom:1px solid var(--border);background:#fff
|
|
491
|
+
}
|
|
492
|
+
.custom-css-title{font-size:12px;font-weight:700;color:var(--text)}
|
|
493
|
+
.custom-css-close{
|
|
494
|
+
border:none;background:transparent;color:var(--text-3);cursor:pointer;width:24px;height:24px;border-radius:5px
|
|
495
|
+
}
|
|
496
|
+
.custom-css-close:hover{background:var(--bg-hover);color:var(--text)}
|
|
497
|
+
#custom-css-modal-textarea{
|
|
498
|
+
width:100%;min-height:360px;max-height:58vh;resize:vertical;border:none;outline:none;
|
|
499
|
+
font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
|
|
500
|
+
font-size:12px;line-height:1.5;padding:12px;background:#0b1220;color:#e2e8f0
|
|
501
|
+
}
|
|
502
|
+
.custom-css-actions{
|
|
503
|
+
display:flex;justify-content:flex-end;gap:8px;padding:10px 12px;border-top:1px solid var(--border);background:#fff
|
|
504
|
+
}
|
|
505
|
+
.custom-css-btn{
|
|
506
|
+
border:1px solid var(--border);background:#fff;color:var(--text-2);border-radius:6px;padding:6px 10px;cursor:pointer;
|
|
507
|
+
font-size:12px;font-weight:600;font-family:inherit
|
|
508
|
+
}
|
|
509
|
+
.custom-css-btn:hover{background:var(--bg-hover)}
|
|
510
|
+
.custom-css-btn.primary{background:var(--accent);border-color:var(--accent);color:#fff}
|
|
511
|
+
.custom-css-btn.primary:hover{filter:brightness(.97)}
|
|
458
512
|
|
|
459
513
|
/* \u2500\u2500 Shadow presets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
460
514
|
.shadow-presets{display:flex;gap:4px;flex-wrap:wrap;margin-top:6px}
|
|
@@ -844,6 +898,23 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
844
898
|
|
|
845
899
|
</div><!-- #app -->
|
|
846
900
|
|
|
901
|
+
<!-- Custom CSS full-screen modal -->
|
|
902
|
+
<div id="custom-css-modal" aria-hidden="true">
|
|
903
|
+
<div class="custom-css-dialog" role="dialog" aria-modal="true" aria-labelledby="custom-css-modal-title">
|
|
904
|
+
<div class="custom-css-head">
|
|
905
|
+
<div class="custom-css-title" id="custom-css-modal-title"><i class="bi bi-code-slash"></i> Custom CSS</div>
|
|
906
|
+
<button type="button" class="custom-css-close" onclick="closeCustomCssModal()" title="Close">
|
|
907
|
+
<i class="bi bi-x-lg"></i>
|
|
908
|
+
</button>
|
|
909
|
+
</div>
|
|
910
|
+
<textarea id="custom-css-modal-textarea" spellcheck="false" placeholder="Type your css here"></textarea>
|
|
911
|
+
<div class="custom-css-actions">
|
|
912
|
+
<button type="button" class="custom-css-btn" onclick="closeCustomCssModal()">Cancel</button>
|
|
913
|
+
<button type="button" class="custom-css-btn primary" onclick="applyCustomCssModal()">Add</button>
|
|
914
|
+
</div>
|
|
915
|
+
</div>
|
|
916
|
+
</div>
|
|
917
|
+
|
|
847
918
|
<!-- CDN scripts -->
|
|
848
919
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
849
920
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/builder.js"></script>
|
|
@@ -915,13 +986,37 @@ function send(type, payload) {
|
|
|
915
986
|
window.parent.postMessage({ channel: CHANNEL, type: type, payload: payload || {} }, '*');
|
|
916
987
|
}
|
|
917
988
|
|
|
989
|
+
var notificationTimer = null;
|
|
990
|
+
function showEditorNotification(message, kind, durationMs) {
|
|
991
|
+
var id = 've-notification';
|
|
992
|
+
var el = document.getElementById(id);
|
|
993
|
+
if (!el) {
|
|
994
|
+
el = document.createElement('div');
|
|
995
|
+
el.id = id;
|
|
996
|
+
el.setAttribute('role', 'status');
|
|
997
|
+
el.setAttribute('aria-live', 'polite');
|
|
998
|
+
document.body.appendChild(el);
|
|
999
|
+
}
|
|
1000
|
+
el.className = '';
|
|
1001
|
+
el.classList.add(kind === 'success' ? 'success' : 'error');
|
|
1002
|
+
el.classList.add('show');
|
|
1003
|
+
el.textContent = String(message || 'Something went wrong');
|
|
1004
|
+
if (notificationTimer) clearTimeout(notificationTimer);
|
|
1005
|
+
notificationTimer = setTimeout(function() {
|
|
1006
|
+
var cur = document.getElementById(id);
|
|
1007
|
+
if (!cur) return;
|
|
1008
|
+
cur.classList.remove('show');
|
|
1009
|
+
}, Math.max(1200, durationMs || 2600));
|
|
1010
|
+
}
|
|
1011
|
+
|
|
918
1012
|
function generatePreviewUrlString(args) {
|
|
919
1013
|
var baseUrl = (args && args.url) || '';
|
|
920
1014
|
var test = (args && args.test) || {};
|
|
921
1015
|
var variation = (args && args.variation) || {};
|
|
922
1016
|
if (!baseUrl) return '';
|
|
923
|
-
var testId = test.iid ||
|
|
924
|
-
var variationId = variation.iid ||
|
|
1017
|
+
var testId = test.iid || '';
|
|
1018
|
+
var variationId = variation.iid || '';
|
|
1019
|
+
if (!testId || !variationId) return '';
|
|
925
1020
|
var cId = String(testId || '') + '_' + String(variationId || '');
|
|
926
1021
|
var hasQueryParams = String(baseUrl).indexOf('?') >= 0;
|
|
927
1022
|
return (
|
|
@@ -945,6 +1040,10 @@ function simulateExperiment() {
|
|
|
945
1040
|
test.pageUrl ||
|
|
946
1041
|
(test.metadata_1 && test.metadata_1.editor_url) ||
|
|
947
1042
|
'';
|
|
1043
|
+
if (!test.iid || !activeVariation || !activeVariation.iid) {
|
|
1044
|
+
showEditorNotification('Cannot simulate: missing test.iid or variation.iid.', 'error', 3200);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
948
1047
|
var url = generatePreviewUrlString({
|
|
949
1048
|
url: targetUrl,
|
|
950
1049
|
test: test,
|
|
@@ -952,6 +1051,7 @@ function simulateExperiment() {
|
|
|
952
1051
|
});
|
|
953
1052
|
if (!url) {
|
|
954
1053
|
console.warn('[V2] simulateExperiment: missing target URL');
|
|
1054
|
+
showEditorNotification('Cannot simulate: missing target URL.', 'error', 3200);
|
|
955
1055
|
return;
|
|
956
1056
|
}
|
|
957
1057
|
try {
|
|
@@ -969,6 +1069,19 @@ window.addEventListener('message', function(e) {
|
|
|
969
1069
|
}
|
|
970
1070
|
});
|
|
971
1071
|
|
|
1072
|
+
document.addEventListener('keydown', function(e) {
|
|
1073
|
+
if (e.key !== 'Escape') return;
|
|
1074
|
+
var modal = document.getElementById('custom-css-modal');
|
|
1075
|
+
if (!modal || !modal.classList.contains('open')) return;
|
|
1076
|
+
closeCustomCssModal();
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
document.addEventListener('click', function(e) {
|
|
1080
|
+
var modal = document.getElementById('custom-css-modal');
|
|
1081
|
+
if (!modal || !modal.classList.contains('open')) return;
|
|
1082
|
+
if (e.target === modal) closeCustomCssModal();
|
|
1083
|
+
});
|
|
1084
|
+
|
|
972
1085
|
// \u2500\u2500 State \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
973
1086
|
var experimentData = null;
|
|
974
1087
|
var variations = [];
|
|
@@ -1048,14 +1161,17 @@ function endSuppressIframeMutationDirty() {
|
|
|
1048
1161
|
function commitStateChangesForActiveVariation() {
|
|
1049
1162
|
if (!activeVarId) return;
|
|
1050
1163
|
stateChangesByVarId[activeVarId] = (stateChanges || []).slice();
|
|
1164
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1051
1165
|
}
|
|
1052
1166
|
|
|
1053
1167
|
function loadStateChangesForActiveVariation() {
|
|
1054
1168
|
if (!activeVarId) {
|
|
1055
1169
|
stateChanges = [];
|
|
1170
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1056
1171
|
return;
|
|
1057
1172
|
}
|
|
1058
1173
|
stateChanges = (stateChangesByVarId[activeVarId] || []).slice();
|
|
1174
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1059
1175
|
}
|
|
1060
1176
|
|
|
1061
1177
|
function recoverSelectedElement(forceDeselectOnMiss) {
|
|
@@ -1535,24 +1651,26 @@ function persistActiveVariationChangesets(arr) {
|
|
|
1535
1651
|
}
|
|
1536
1652
|
}
|
|
1537
1653
|
}
|
|
1654
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1538
1655
|
}
|
|
1539
1656
|
|
|
1540
1657
|
function entrySnapshotKey(entry) {
|
|
1541
1658
|
if (!entry || !entry.selector) return '';
|
|
1659
|
+
var SEP = '__vve_sep__';
|
|
1542
1660
|
var selKey = sanitizeSelectorForMatch(entry.selector) || entry.selector;
|
|
1543
1661
|
return (
|
|
1544
1662
|
selKey +
|
|
1545
|
-
|
|
1663
|
+
SEP +
|
|
1546
1664
|
normalizeChangesetType(entry) +
|
|
1547
|
-
|
|
1665
|
+
SEP +
|
|
1548
1666
|
String(entry.property || '') +
|
|
1549
|
-
|
|
1667
|
+
SEP +
|
|
1550
1668
|
String(entry.attribute || '') +
|
|
1551
|
-
|
|
1669
|
+
SEP +
|
|
1552
1670
|
String(entry.action || '') +
|
|
1553
|
-
|
|
1671
|
+
SEP +
|
|
1554
1672
|
String(entry.html != null ? 'h' + String(entry.html).length : '') +
|
|
1555
|
-
|
|
1673
|
+
SEP +
|
|
1556
1674
|
String(entry.value != null ? 'v:' + entry.value : '')
|
|
1557
1675
|
);
|
|
1558
1676
|
}
|
|
@@ -1602,7 +1720,6 @@ function softReloadEditorIframe() {
|
|
|
1602
1720
|
var navGen = nextIframeContentNavGen();
|
|
1603
1721
|
resetIframeBindings();
|
|
1604
1722
|
setIframePageLoadingUi(true);
|
|
1605
|
-
iframe.src = '';
|
|
1606
1723
|
iframe.src = appendIframeReloadBust(src);
|
|
1607
1724
|
startIframeContentApplyWatcher(navGen);
|
|
1608
1725
|
scheduleDomTreeRefresh();
|
|
@@ -1611,6 +1728,7 @@ function softReloadEditorIframe() {
|
|
|
1611
1728
|
/** @returns {boolean} true if a full iframe reload was started */
|
|
1612
1729
|
function revertChangesetEntryOnDom(entry) {
|
|
1613
1730
|
if (!entry) return false;
|
|
1731
|
+
var styleOnly = isStyleOnlyChangesetEntry(entry);
|
|
1614
1732
|
if (entry.selector === '__vvveb_body__') {
|
|
1615
1733
|
var iframeDoc0 = document.getElementById('iframeId').contentDocument;
|
|
1616
1734
|
if (!iframeDoc0 || !iframeDoc0.body) return false;
|
|
@@ -1631,6 +1749,11 @@ function revertChangesetEntryOnDom(entry) {
|
|
|
1631
1749
|
var snap = appliedChangesetSnapshots[k];
|
|
1632
1750
|
var el = querySelectorResolved(iframeDoc, entry.selector);
|
|
1633
1751
|
if (!snap || !el) {
|
|
1752
|
+
if (styleOnly) {
|
|
1753
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1754
|
+
delete appliedChangesetSnapshots[k];
|
|
1755
|
+
return false;
|
|
1756
|
+
}
|
|
1634
1757
|
softReloadEditorIframe();
|
|
1635
1758
|
delete appliedChangesetSnapshots[k];
|
|
1636
1759
|
return true;
|
|
@@ -1645,6 +1768,11 @@ function revertChangesetEntryOnDom(entry) {
|
|
|
1645
1768
|
else el.setAttribute(snap.name, snap.v);
|
|
1646
1769
|
} else if (snap.kind === 'display') el.style.display = snap.v;
|
|
1647
1770
|
else {
|
|
1771
|
+
if (styleOnly) {
|
|
1772
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1773
|
+
delete appliedChangesetSnapshots[k];
|
|
1774
|
+
return false;
|
|
1775
|
+
}
|
|
1648
1776
|
softReloadEditorIframe();
|
|
1649
1777
|
delete appliedChangesetSnapshots[k];
|
|
1650
1778
|
return true;
|
|
@@ -1704,7 +1832,9 @@ function renderHistoryTab() {
|
|
|
1704
1832
|
var lab = historyEntryTypeLabel(item.entry);
|
|
1705
1833
|
var val = historyEntryValuePreview(item.entry);
|
|
1706
1834
|
html +=
|
|
1707
|
-
'<div class="state-item"
|
|
1835
|
+
'<div class="state-item" role="button" tabindex="0" title="Jump to element in iframe" onclick="focusHistoryChangeset(' +
|
|
1836
|
+
item.idx +
|
|
1837
|
+
')">' +
|
|
1708
1838
|
'<span class="state-item-idx" style="opacity:0.55;font-size:10px;min-width:2.2em;display:inline-block" title="Order in saved changesets">#' +
|
|
1709
1839
|
item.idx +
|
|
1710
1840
|
'</span>' +
|
|
@@ -1720,7 +1850,7 @@ function renderHistoryTab() {
|
|
|
1720
1850
|
item.idx +
|
|
1721
1851
|
')" onclick="removeHistoryChangeset(' +
|
|
1722
1852
|
item.idx +
|
|
1723
|
-
')">✕</button>' +
|
|
1853
|
+
', event)">✕</button>' +
|
|
1724
1854
|
'</div>';
|
|
1725
1855
|
});
|
|
1726
1856
|
html += '</div>';
|
|
@@ -1738,7 +1868,38 @@ function changesetListHasStructural(arr) {
|
|
|
1738
1868
|
return false;
|
|
1739
1869
|
}
|
|
1740
1870
|
|
|
1741
|
-
function
|
|
1871
|
+
function isStyleOnlyChangesetEntry(entry) {
|
|
1872
|
+
if (!entry) return false;
|
|
1873
|
+
var t = normalizeChangesetType(entry);
|
|
1874
|
+
if (t === 'style') return true;
|
|
1875
|
+
if (t === 'attribute' && String(entry.attribute || '').toLowerCase() === 'style') return true;
|
|
1876
|
+
return false;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
function focusHistoryChangeset(idx) {
|
|
1880
|
+
var v = getActiveVariationForHistory();
|
|
1881
|
+
if (!v) return;
|
|
1882
|
+
var arr = parseVariationChangesets(v);
|
|
1883
|
+
if (idx < 0 || idx >= arr.length) return;
|
|
1884
|
+
var entry = arr[idx];
|
|
1885
|
+
if (!entry || !entry.selector || entry.selector === '__vvveb_body__') return;
|
|
1886
|
+
try {
|
|
1887
|
+
var iframe = document.getElementById('iframeId');
|
|
1888
|
+
var iframeDoc = iframe && iframe.contentDocument;
|
|
1889
|
+
if (!iframeDoc) return;
|
|
1890
|
+
var el = querySelectorResolved(iframeDoc, entry.selector);
|
|
1891
|
+
if (!el) return;
|
|
1892
|
+
selectElement(el);
|
|
1893
|
+
try {
|
|
1894
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
|
|
1895
|
+
} catch(_) {
|
|
1896
|
+
el.scrollIntoView();
|
|
1897
|
+
}
|
|
1898
|
+
} catch(_) {}
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
function removeHistoryChangeset(idx, evt) {
|
|
1902
|
+
if (evt && evt.stopPropagation) evt.stopPropagation();
|
|
1742
1903
|
var v = getActiveVariationForHistory();
|
|
1743
1904
|
if (!v) return;
|
|
1744
1905
|
var arr = parseVariationChangesets(v);
|
|
@@ -1746,6 +1907,16 @@ function removeHistoryChangeset(idx) {
|
|
|
1746
1907
|
var removed = arr[idx];
|
|
1747
1908
|
arr.splice(idx, 1);
|
|
1748
1909
|
persistActiveVariationChangesets(arr);
|
|
1910
|
+
if (isStyleOnlyChangesetEntry(removed)) {
|
|
1911
|
+
try {
|
|
1912
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1913
|
+
saveCurrentVariationHtml();
|
|
1914
|
+
} catch(_) {}
|
|
1915
|
+
if (currentMainTab === 'history') renderHistoryTab();
|
|
1916
|
+
recomputeEditorDirty();
|
|
1917
|
+
scheduleDomTreeRefresh();
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1749
1920
|
var didReload = revertChangesetEntryOnDom(removed);
|
|
1750
1921
|
try {
|
|
1751
1922
|
delete varHtmlCache[activeVarId];
|
|
@@ -1753,15 +1924,19 @@ function removeHistoryChangeset(idx) {
|
|
|
1753
1924
|
// Re-applying remaining rows on top of current DOM duplicates insert/reorder nodes; reload when any
|
|
1754
1925
|
// structural row remains or was removed (revert may already have started a reload for insert/body).
|
|
1755
1926
|
var removedType = normalizeChangesetType(removed);
|
|
1756
|
-
var
|
|
1757
|
-
|
|
1758
|
-
(removedType === 'insert' ||
|
|
1759
|
-
removedType === 'reorder' ||
|
|
1760
|
-
changesetListHasStructural(arr));
|
|
1927
|
+
var hasStructuralRemaining = changesetListHasStructural(arr);
|
|
1928
|
+
var removedIsStructural = removedType === 'insert' || removedType === 'reorder';
|
|
1761
1929
|
if (didReload) {
|
|
1762
1930
|
/* revertChangesetEntryOnDom already kicked off iframe reload */
|
|
1763
|
-
} else if (
|
|
1931
|
+
} else if (removedIsStructural) {
|
|
1764
1932
|
softReloadEditorIframe();
|
|
1933
|
+
} else if (hasStructuralRemaining) {
|
|
1934
|
+
// Keep current DOM state (already reverted for removed row) and only refresh style layer.
|
|
1935
|
+
// Avoid full reload and avoid re-applying all rows, which can duplicate structural insert/reorder entries.
|
|
1936
|
+
try {
|
|
1937
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1938
|
+
saveCurrentVariationHtml();
|
|
1939
|
+
} catch(_) {}
|
|
1765
1940
|
} else {
|
|
1766
1941
|
try {
|
|
1767
1942
|
appliedStructuralChangesetKeys = {};
|
|
@@ -2129,7 +2304,7 @@ function runConsistencyReconcile() {
|
|
|
2129
2304
|
if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
|
|
2130
2305
|
var granular = filterGranularChangesetEntries(cs);
|
|
2131
2306
|
var unresolved = countUnresolvedGranularSelectors(doc, granular);
|
|
2132
|
-
if (unresolved > 0 ||
|
|
2307
|
+
if (unresolved > 0 || hasUnappliedStructuralChangesets(cs)) {
|
|
2133
2308
|
reapplyActiveVariationGranular(doc);
|
|
2134
2309
|
registerPendingGranularChangesets(cs, doc);
|
|
2135
2310
|
}
|
|
@@ -2499,24 +2674,136 @@ function flushPendingGranularChangesets() {
|
|
|
2499
2674
|
function structuralChangesetDedupKey(entry) {
|
|
2500
2675
|
var nt = normalizeChangesetType(entry);
|
|
2501
2676
|
if (!entry || (nt !== 'insert' && nt !== 'reorder')) return '';
|
|
2677
|
+
var SEP = '__vve_sep__';
|
|
2502
2678
|
var vid = activeVarId || '';
|
|
2503
2679
|
try {
|
|
2504
2680
|
return (
|
|
2505
2681
|
vid +
|
|
2506
|
-
|
|
2682
|
+
SEP +
|
|
2507
2683
|
nt +
|
|
2508
|
-
|
|
2684
|
+
SEP +
|
|
2509
2685
|
entry.selector +
|
|
2510
|
-
|
|
2686
|
+
SEP +
|
|
2511
2687
|
String(entry.action || '') +
|
|
2512
|
-
|
|
2688
|
+
SEP +
|
|
2513
2689
|
String(entry.html != null ? entry.html : '').slice(0, 240) +
|
|
2514
|
-
|
|
2690
|
+
SEP +
|
|
2515
2691
|
String(entry.targetSelector || '')
|
|
2516
2692
|
);
|
|
2517
2693
|
} catch(_) {
|
|
2518
|
-
return vid +
|
|
2694
|
+
return vid + SEP + nt + SEP + entry.selector;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
function hasUnappliedStructuralChangesets(cs) {
|
|
2699
|
+
if (!cs || !cs.length) return false;
|
|
2700
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2701
|
+
var e = cs[i];
|
|
2702
|
+
var t = normalizeChangesetType(e);
|
|
2703
|
+
if (t !== 'insert' && t !== 'reorder') continue;
|
|
2704
|
+
var k = structuralChangesetDedupKey(e);
|
|
2705
|
+
if (!k) return true;
|
|
2706
|
+
if (!appliedStructuralChangesetKeys[k]) return true;
|
|
2707
|
+
}
|
|
2708
|
+
return false;
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
function parseInlineStyleDeclarations(styleText) {
|
|
2712
|
+
var out = [];
|
|
2713
|
+
if (styleText == null) return out;
|
|
2714
|
+
var s = String(styleText);
|
|
2715
|
+
if (!s.trim()) return out;
|
|
2716
|
+
var parts = s.split(';');
|
|
2717
|
+
for (var i = 0; i < parts.length; i++) {
|
|
2718
|
+
var seg = parts[i];
|
|
2719
|
+
if (!seg) continue;
|
|
2720
|
+
var idx = seg.indexOf(':');
|
|
2721
|
+
if (idx <= 0) continue;
|
|
2722
|
+
var prop = seg.slice(0, idx).trim();
|
|
2723
|
+
var value = seg.slice(idx + 1).trim();
|
|
2724
|
+
if (!prop || !value) continue;
|
|
2725
|
+
out.push({ prop: prop, value: value });
|
|
2519
2726
|
}
|
|
2727
|
+
return out;
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
function buildPersistentStyleRulesForActiveVariation() {
|
|
2731
|
+
if (!activeVarId) return '';
|
|
2732
|
+
var v = variations.find(function(x) { return x && x._id === activeVarId; });
|
|
2733
|
+
var parsed = parseVariationChangesets(v);
|
|
2734
|
+
var map = {};
|
|
2735
|
+
var order = [];
|
|
2736
|
+
function put(selector, prop, value) {
|
|
2737
|
+
if (!selector || !prop) return;
|
|
2738
|
+
if (value == null || value === '') return;
|
|
2739
|
+
var sel = sanitizeSelectorForMatch(String(selector)) || String(selector);
|
|
2740
|
+
var pr = String(prop).trim();
|
|
2741
|
+
var val = String(value).trim();
|
|
2742
|
+
if (!sel || !pr || !val) return;
|
|
2743
|
+
var k = sel + '__vve_sep__' + pr;
|
|
2744
|
+
if (!map[k]) order.push(k);
|
|
2745
|
+
map[k] = { selector: sel, property: pr, value: val };
|
|
2746
|
+
}
|
|
2747
|
+
for (var i = 0; i < parsed.length; i++) {
|
|
2748
|
+
var e = parsed[i];
|
|
2749
|
+
if (!e) continue;
|
|
2750
|
+
var t = normalizeChangesetType(e);
|
|
2751
|
+
if (t === 'style') {
|
|
2752
|
+
put(e.selector, e.property || e.cssProp, e.value);
|
|
2753
|
+
continue;
|
|
2754
|
+
}
|
|
2755
|
+
if (t === 'attribute' && String(e.attribute || '').toLowerCase() === 'style') {
|
|
2756
|
+
var decls = parseInlineStyleDeclarations(e.value);
|
|
2757
|
+
for (var di = 0; di < decls.length; di++) {
|
|
2758
|
+
put(e.selector, decls[di].prop, decls[di].value);
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
for (var j = 0; j < stateChanges.length; j++) {
|
|
2763
|
+
var c = stateChanges[j];
|
|
2764
|
+
if (!c) continue;
|
|
2765
|
+
if (c.cssProp) {
|
|
2766
|
+
put(c.selector, c.cssProp, c.value);
|
|
2767
|
+
continue;
|
|
2768
|
+
}
|
|
2769
|
+
if (c.inputId === 'pp-css') {
|
|
2770
|
+
var liveDecls = parseInlineStyleDeclarations(c.value);
|
|
2771
|
+
for (var ldi = 0; ldi < liveDecls.length; ldi++) {
|
|
2772
|
+
put(c.selector, liveDecls[ldi].prop, liveDecls[ldi].value);
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
var lines = [];
|
|
2777
|
+
for (var oi = 0; oi < order.length; oi++) {
|
|
2778
|
+
var row = map[order[oi]];
|
|
2779
|
+
lines.push(row.selector + ' { ' + row.property + ': ' + row.value + ' !important; }');
|
|
2780
|
+
}
|
|
2781
|
+
return lines.join('\\n');
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
function upsertPersistentChangesetStyleTag(iframeDoc, rulesText) {
|
|
2785
|
+
if (!iframeDoc) return;
|
|
2786
|
+
var STYLE_ID = '__vve_persist_changesets_style__';
|
|
2787
|
+
var prev = iframeDoc.getElementById(STYLE_ID);
|
|
2788
|
+
if (!rulesText) {
|
|
2789
|
+
if (prev && prev.parentNode) prev.parentNode.removeChild(prev);
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
var head = iframeDoc.head || iframeDoc.getElementsByTagName('head')[0];
|
|
2793
|
+
if (!head) return;
|
|
2794
|
+
var styleEl = prev || iframeDoc.createElement('style');
|
|
2795
|
+
styleEl.id = STYLE_ID;
|
|
2796
|
+
if (styleEl.textContent !== rulesText) styleEl.textContent = rulesText;
|
|
2797
|
+
if (!styleEl.parentNode) head.appendChild(styleEl);
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
function refreshPersistentChangesetStyleTagForActiveVariation() {
|
|
2801
|
+
try {
|
|
2802
|
+
var iframe = document.getElementById('iframeId');
|
|
2803
|
+
var iframeDoc = iframe && iframe.contentDocument;
|
|
2804
|
+
if (!iframeDoc) return;
|
|
2805
|
+
upsertPersistentChangesetStyleTag(iframeDoc, buildPersistentStyleRulesForActiveVariation());
|
|
2806
|
+
} catch(_) {}
|
|
2520
2807
|
}
|
|
2521
2808
|
|
|
2522
2809
|
/**
|
|
@@ -2553,23 +2840,11 @@ function applyChangesetEntry(entry, iframeDoc) {
|
|
|
2553
2840
|
else if (entry.value != null) el.textContent = entry.value;
|
|
2554
2841
|
break;
|
|
2555
2842
|
case 'style':
|
|
2556
|
-
|
|
2557
|
-
var propKebab = entry.property;
|
|
2558
|
-
var cam = camelize(propKebab);
|
|
2559
|
-
if (entry.value == null || entry.value === '') {
|
|
2560
|
-
try { el.style.removeProperty(propKebab); } catch(_) {}
|
|
2561
|
-
try { if (cam in el.style) el.style[cam] = ''; } catch(__) {}
|
|
2562
|
-
} else {
|
|
2563
|
-
try {
|
|
2564
|
-
el.style.setProperty(propKebab, entry.value, 'important');
|
|
2565
|
-
} catch(_) {
|
|
2566
|
-
el.style[cam] = entry.value;
|
|
2567
|
-
}
|
|
2568
|
-
}
|
|
2569
|
-
}
|
|
2843
|
+
// Style changes are applied via persistent stylesheet injection.
|
|
2570
2844
|
break;
|
|
2571
2845
|
case 'attribute':
|
|
2572
2846
|
if (entry.attribute && entry.value != null) {
|
|
2847
|
+
if (String(entry.attribute).toLowerCase() === 'style') break;
|
|
2573
2848
|
el.setAttribute(entry.attribute, entry.value);
|
|
2574
2849
|
}
|
|
2575
2850
|
break;
|
|
@@ -2606,6 +2881,7 @@ function applyActiveVariationHtml() {
|
|
|
2606
2881
|
|
|
2607
2882
|
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2608
2883
|
var cs = parseVariationChangesets(variation);
|
|
2884
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
2609
2885
|
|
|
2610
2886
|
beginSuppressIframeMutationDirty();
|
|
2611
2887
|
try {
|
|
@@ -2620,6 +2896,7 @@ function applyActiveVariationHtml() {
|
|
|
2620
2896
|
for (var i = 0; i < cs.length; i++) {
|
|
2621
2897
|
applyChangesetEntry(cs[i], iframeDoc);
|
|
2622
2898
|
}
|
|
2899
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
2623
2900
|
// Selectors often miss on first paint (client-rendered sections). Retry via timer + mutations.
|
|
2624
2901
|
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2625
2902
|
} finally {
|
|
@@ -2668,6 +2945,7 @@ function applyVariationGranularOnly(iframeDoc) {
|
|
|
2668
2945
|
for (var i = 0; i < cs.length; i++) {
|
|
2669
2946
|
applyChangesetEntry(cs[i], iframeDoc);
|
|
2670
2947
|
}
|
|
2948
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
2671
2949
|
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2672
2950
|
} finally {
|
|
2673
2951
|
endSuppressIframeMutationDirty();
|
|
@@ -2686,6 +2964,7 @@ function reapplyActiveVariationGranular(iframeDoc) {
|
|
|
2686
2964
|
for (var i = 0; i < cs.length; i++) {
|
|
2687
2965
|
applyChangesetEntry(cs[i], iframeDoc);
|
|
2688
2966
|
}
|
|
2967
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
2689
2968
|
} finally {
|
|
2690
2969
|
endSuppressIframeMutationDirty();
|
|
2691
2970
|
}
|
|
@@ -3174,6 +3453,40 @@ function pr2(l1, i1, l2, i2) {
|
|
|
3174
3453
|
'<div class="pr2-item"><div class="pr2-lbl">'+l2+'</div>'+i2+'</div></div>';
|
|
3175
3454
|
}
|
|
3176
3455
|
function subLbl(text) { return '<div class="sub-lbl">'+text+'</div>'; }
|
|
3456
|
+
function openCustomCssModal() {
|
|
3457
|
+
var modal = document.getElementById('custom-css-modal');
|
|
3458
|
+
var ta = document.getElementById('custom-css-modal-textarea');
|
|
3459
|
+
if (!modal || !ta) return;
|
|
3460
|
+
var inp = document.getElementById('pp-css');
|
|
3461
|
+
var v = inp ? inp.value : (selectedEl && selectedEl.getAttribute ? (selectedEl.getAttribute('style') || '') : '');
|
|
3462
|
+
ta.value = v || '';
|
|
3463
|
+
modal.classList.add('open');
|
|
3464
|
+
modal.setAttribute('aria-hidden', 'false');
|
|
3465
|
+
setTimeout(function() {
|
|
3466
|
+
try { ta.focus(); ta.setSelectionRange(ta.value.length, ta.value.length); } catch(_) {}
|
|
3467
|
+
}, 0);
|
|
3468
|
+
}
|
|
3469
|
+
function closeCustomCssModal() {
|
|
3470
|
+
var modal = document.getElementById('custom-css-modal');
|
|
3471
|
+
if (!modal) return;
|
|
3472
|
+
modal.classList.remove('open');
|
|
3473
|
+
modal.setAttribute('aria-hidden', 'true');
|
|
3474
|
+
}
|
|
3475
|
+
function applyCustomCssModal() {
|
|
3476
|
+
var ta = document.getElementById('custom-css-modal-textarea');
|
|
3477
|
+
var inp = document.getElementById('pp-css');
|
|
3478
|
+
if (!ta || !inp) {
|
|
3479
|
+
closeCustomCssModal();
|
|
3480
|
+
return;
|
|
3481
|
+
}
|
|
3482
|
+
inp.value = ta.value || '';
|
|
3483
|
+
try {
|
|
3484
|
+
inp.dispatchEvent(new Event('input', { bubbles: true }));
|
|
3485
|
+
} catch(_) {
|
|
3486
|
+
if (typeof inp.oninput === 'function') inp.oninput();
|
|
3487
|
+
}
|
|
3488
|
+
closeCustomCssModal();
|
|
3489
|
+
}
|
|
3177
3490
|
function weightOpts(cur) {
|
|
3178
3491
|
return [['100','Thin'],['200','Extra Light'],['300','Light'],['400','Normal'],['500','Medium'],
|
|
3179
3492
|
['600','Semi Bold'],['700','Bold'],['800','Extra Bold'],['900','Black']]
|
|
@@ -3454,7 +3767,12 @@ function renderRightPanel(el) {
|
|
|
3454
3767
|
// \u2500\u2500 CSS and Classes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3455
3768
|
document.getElementById('acc-body-css').innerHTML =
|
|
3456
3769
|
pr('Classes', '<input class="pr-inp" id="pp-cls" type="text" value="'+esc(el.className||'')+'" placeholder="class1 class2">') +
|
|
3457
|
-
|
|
3770
|
+
'<div class="sub-lbl-row">' +
|
|
3771
|
+
'<div class="sub-lbl">Custom CSS</div>' +
|
|
3772
|
+
'<button type="button" class="css-expand-btn" title="Open full-screen editor" onclick="openCustomCssModal()">' +
|
|
3773
|
+
'<i class="bi bi-fullscreen"></i>' +
|
|
3774
|
+
'</button>' +
|
|
3775
|
+
'</div>' +
|
|
3458
3776
|
'<textarea class="pr-inp" id="pp-css" style="width:100%;min-height:80px;font-family:monospace;font-size:11px" placeholder="color: red; font-size: 16px;">'+esc(el.getAttribute('style')||'')+'</textarea>';
|
|
3459
3777
|
|
|
3460
3778
|
// \u2500\u2500 Attributes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -3818,26 +4136,6 @@ function attachChangeObserver() {
|
|
|
3818
4136
|
}
|
|
3819
4137
|
changeObserver = new MutationObserver(function(mutations) {
|
|
3820
4138
|
// Dirty state is derived from changesets baseline + stateChanges (not raw DOM mutations).
|
|
3821
|
-
var bodyReplaced = false;
|
|
3822
|
-
for (var mi = 0; mi < mutations.length; mi++) {
|
|
3823
|
-
var m = mutations[mi];
|
|
3824
|
-
if (
|
|
3825
|
-
m &&
|
|
3826
|
-
m.type === 'childList' &&
|
|
3827
|
-
m.target === doc.body &&
|
|
3828
|
-
m.addedNodes &&
|
|
3829
|
-
m.removedNodes &&
|
|
3830
|
-
m.addedNodes.length > 0 &&
|
|
3831
|
-
m.removedNodes.length > 0
|
|
3832
|
-
) {
|
|
3833
|
-
bodyReplaced = true;
|
|
3834
|
-
break;
|
|
3835
|
-
}
|
|
3836
|
-
}
|
|
3837
|
-
if (bodyReplaced) {
|
|
3838
|
-
// Page JS replaced body children; allow structural rows (insert/reorder) to apply again.
|
|
3839
|
-
appliedStructuralChangesetKeys = {};
|
|
3840
|
-
}
|
|
3841
4139
|
// Host scripts can replace selected nodes every few frames (e.g. A/B tool observers).
|
|
3842
4140
|
// Keep selection sticky by re-resolving from fingerprint.
|
|
3843
4141
|
recoverSelectedElement(false);
|
|
@@ -3871,6 +4169,7 @@ function syncIframeInteractions(reason) {
|
|
|
3871
4169
|
}
|
|
3872
4170
|
showNoUrl(false);
|
|
3873
4171
|
injectIframeSelectionStyles(doc);
|
|
4172
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
3874
4173
|
attachClickHandler();
|
|
3875
4174
|
attachDragReposition();
|
|
3876
4175
|
attachChangeObserver();
|
package/dist/vite.js
CHANGED
|
@@ -140,6 +140,17 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
140
140
|
.tb-save-txt{font-size:14px;color:#00C951;white-space:nowrap}
|
|
141
141
|
.tb-save-txt::before{content:'Saved'}
|
|
142
142
|
#dirty-dot.on~.tb-save-txt::before{content:'Unsaved'}
|
|
143
|
+
|
|
144
|
+
/* \u2500\u2500 In-editor toast notification \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
145
|
+
#ve-notification{
|
|
146
|
+
position:fixed;right:16px;top:64px;z-index:13000;max-width:360px;
|
|
147
|
+
padding:10px 12px;border-radius:8px;font-size:12px;line-height:1.35;
|
|
148
|
+
border:1px solid var(--border);background:#fff;color:var(--text);
|
|
149
|
+
box-shadow:0 10px 24px rgba(2,6,23,.18);display:none
|
|
150
|
+
}
|
|
151
|
+
#ve-notification.show{display:block}
|
|
152
|
+
#ve-notification.error{border-color:#fecaca;background:#fef2f2;color:#991b1b}
|
|
153
|
+
#ve-notification.success{border-color:#bbf7d0;background:#f0fdf4;color:#166534}
|
|
143
154
|
.tb-save-time{font-size:12px;color:#52525b;white-space:nowrap}
|
|
144
155
|
#dirty-dot.on~.tb-save-time{display:none}
|
|
145
156
|
/* Simulate + Finalize buttons */
|
|
@@ -447,6 +458,49 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
447
458
|
/* \u2500\u2500 Subgroup label \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
448
459
|
.sub-lbl{font-size:10px;text-transform:uppercase;color:var(--text-3);font-weight:700;letter-spacing:.05em;margin:8px 0 5px}
|
|
449
460
|
.sub-lbl:first-child{margin-top:0}
|
|
461
|
+
.sub-lbl-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:8px 0 5px}
|
|
462
|
+
.sub-lbl-row .sub-lbl{margin:0}
|
|
463
|
+
.css-expand-btn{
|
|
464
|
+
width:22px;height:22px;border:1px solid var(--border);border-radius:5px;background:#fff;
|
|
465
|
+
display:inline-flex;align-items:center;justify-content:center;cursor:pointer;color:var(--text-3);
|
|
466
|
+
transition:all .15s
|
|
467
|
+
}
|
|
468
|
+
.css-expand-btn:hover{border-color:var(--accent);color:var(--accent-txt);background:var(--accent-bg)}
|
|
469
|
+
|
|
470
|
+
/* \u2500\u2500 Custom CSS modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
471
|
+
#custom-css-modal{
|
|
472
|
+
position:fixed;inset:0;z-index:12000;background:rgba(15,23,42,.5);
|
|
473
|
+
display:none;align-items:center;justify-content:center;padding:20px
|
|
474
|
+
}
|
|
475
|
+
#custom-css-modal.open{display:flex}
|
|
476
|
+
.custom-css-dialog{
|
|
477
|
+
width:min(860px,96vw);max-height:86vh;background:#fff;border:1px solid var(--border);
|
|
478
|
+
border-radius:10px;box-shadow:0 20px 40px rgba(2,6,23,.35);display:flex;flex-direction:column;overflow:hidden
|
|
479
|
+
}
|
|
480
|
+
.custom-css-head{
|
|
481
|
+
display:flex;align-items:center;justify-content:space-between;gap:10px;
|
|
482
|
+
padding:10px 12px;border-bottom:1px solid var(--border);background:#fff
|
|
483
|
+
}
|
|
484
|
+
.custom-css-title{font-size:12px;font-weight:700;color:var(--text)}
|
|
485
|
+
.custom-css-close{
|
|
486
|
+
border:none;background:transparent;color:var(--text-3);cursor:pointer;width:24px;height:24px;border-radius:5px
|
|
487
|
+
}
|
|
488
|
+
.custom-css-close:hover{background:var(--bg-hover);color:var(--text)}
|
|
489
|
+
#custom-css-modal-textarea{
|
|
490
|
+
width:100%;min-height:360px;max-height:58vh;resize:vertical;border:none;outline:none;
|
|
491
|
+
font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
|
|
492
|
+
font-size:12px;line-height:1.5;padding:12px;background:#0b1220;color:#e2e8f0
|
|
493
|
+
}
|
|
494
|
+
.custom-css-actions{
|
|
495
|
+
display:flex;justify-content:flex-end;gap:8px;padding:10px 12px;border-top:1px solid var(--border);background:#fff
|
|
496
|
+
}
|
|
497
|
+
.custom-css-btn{
|
|
498
|
+
border:1px solid var(--border);background:#fff;color:var(--text-2);border-radius:6px;padding:6px 10px;cursor:pointer;
|
|
499
|
+
font-size:12px;font-weight:600;font-family:inherit
|
|
500
|
+
}
|
|
501
|
+
.custom-css-btn:hover{background:var(--bg-hover)}
|
|
502
|
+
.custom-css-btn.primary{background:var(--accent);border-color:var(--accent);color:#fff}
|
|
503
|
+
.custom-css-btn.primary:hover{filter:brightness(.97)}
|
|
450
504
|
|
|
451
505
|
/* \u2500\u2500 Shadow presets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
452
506
|
.shadow-presets{display:flex;gap:4px;flex-wrap:wrap;margin-top:6px}
|
|
@@ -836,6 +890,23 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
836
890
|
|
|
837
891
|
</div><!-- #app -->
|
|
838
892
|
|
|
893
|
+
<!-- Custom CSS full-screen modal -->
|
|
894
|
+
<div id="custom-css-modal" aria-hidden="true">
|
|
895
|
+
<div class="custom-css-dialog" role="dialog" aria-modal="true" aria-labelledby="custom-css-modal-title">
|
|
896
|
+
<div class="custom-css-head">
|
|
897
|
+
<div class="custom-css-title" id="custom-css-modal-title"><i class="bi bi-code-slash"></i> Custom CSS</div>
|
|
898
|
+
<button type="button" class="custom-css-close" onclick="closeCustomCssModal()" title="Close">
|
|
899
|
+
<i class="bi bi-x-lg"></i>
|
|
900
|
+
</button>
|
|
901
|
+
</div>
|
|
902
|
+
<textarea id="custom-css-modal-textarea" spellcheck="false" placeholder="Type your css here"></textarea>
|
|
903
|
+
<div class="custom-css-actions">
|
|
904
|
+
<button type="button" class="custom-css-btn" onclick="closeCustomCssModal()">Cancel</button>
|
|
905
|
+
<button type="button" class="custom-css-btn primary" onclick="applyCustomCssModal()">Add</button>
|
|
906
|
+
</div>
|
|
907
|
+
</div>
|
|
908
|
+
</div>
|
|
909
|
+
|
|
839
910
|
<!-- CDN scripts -->
|
|
840
911
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
841
912
|
<script src="https://cdn.jsdelivr.net/gh/givanz/VvvebJs@master/libs/builder/builder.js"></script>
|
|
@@ -907,13 +978,37 @@ function send(type, payload) {
|
|
|
907
978
|
window.parent.postMessage({ channel: CHANNEL, type: type, payload: payload || {} }, '*');
|
|
908
979
|
}
|
|
909
980
|
|
|
981
|
+
var notificationTimer = null;
|
|
982
|
+
function showEditorNotification(message, kind, durationMs) {
|
|
983
|
+
var id = 've-notification';
|
|
984
|
+
var el = document.getElementById(id);
|
|
985
|
+
if (!el) {
|
|
986
|
+
el = document.createElement('div');
|
|
987
|
+
el.id = id;
|
|
988
|
+
el.setAttribute('role', 'status');
|
|
989
|
+
el.setAttribute('aria-live', 'polite');
|
|
990
|
+
document.body.appendChild(el);
|
|
991
|
+
}
|
|
992
|
+
el.className = '';
|
|
993
|
+
el.classList.add(kind === 'success' ? 'success' : 'error');
|
|
994
|
+
el.classList.add('show');
|
|
995
|
+
el.textContent = String(message || 'Something went wrong');
|
|
996
|
+
if (notificationTimer) clearTimeout(notificationTimer);
|
|
997
|
+
notificationTimer = setTimeout(function() {
|
|
998
|
+
var cur = document.getElementById(id);
|
|
999
|
+
if (!cur) return;
|
|
1000
|
+
cur.classList.remove('show');
|
|
1001
|
+
}, Math.max(1200, durationMs || 2600));
|
|
1002
|
+
}
|
|
1003
|
+
|
|
910
1004
|
function generatePreviewUrlString(args) {
|
|
911
1005
|
var baseUrl = (args && args.url) || '';
|
|
912
1006
|
var test = (args && args.test) || {};
|
|
913
1007
|
var variation = (args && args.variation) || {};
|
|
914
1008
|
if (!baseUrl) return '';
|
|
915
|
-
var testId = test.iid ||
|
|
916
|
-
var variationId = variation.iid ||
|
|
1009
|
+
var testId = test.iid || '';
|
|
1010
|
+
var variationId = variation.iid || '';
|
|
1011
|
+
if (!testId || !variationId) return '';
|
|
917
1012
|
var cId = String(testId || '') + '_' + String(variationId || '');
|
|
918
1013
|
var hasQueryParams = String(baseUrl).indexOf('?') >= 0;
|
|
919
1014
|
return (
|
|
@@ -937,6 +1032,10 @@ function simulateExperiment() {
|
|
|
937
1032
|
test.pageUrl ||
|
|
938
1033
|
(test.metadata_1 && test.metadata_1.editor_url) ||
|
|
939
1034
|
'';
|
|
1035
|
+
if (!test.iid || !activeVariation || !activeVariation.iid) {
|
|
1036
|
+
showEditorNotification('Cannot simulate: missing test.iid or variation.iid.', 'error', 3200);
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
940
1039
|
var url = generatePreviewUrlString({
|
|
941
1040
|
url: targetUrl,
|
|
942
1041
|
test: test,
|
|
@@ -944,6 +1043,7 @@ function simulateExperiment() {
|
|
|
944
1043
|
});
|
|
945
1044
|
if (!url) {
|
|
946
1045
|
console.warn('[V2] simulateExperiment: missing target URL');
|
|
1046
|
+
showEditorNotification('Cannot simulate: missing target URL.', 'error', 3200);
|
|
947
1047
|
return;
|
|
948
1048
|
}
|
|
949
1049
|
try {
|
|
@@ -961,6 +1061,19 @@ window.addEventListener('message', function(e) {
|
|
|
961
1061
|
}
|
|
962
1062
|
});
|
|
963
1063
|
|
|
1064
|
+
document.addEventListener('keydown', function(e) {
|
|
1065
|
+
if (e.key !== 'Escape') return;
|
|
1066
|
+
var modal = document.getElementById('custom-css-modal');
|
|
1067
|
+
if (!modal || !modal.classList.contains('open')) return;
|
|
1068
|
+
closeCustomCssModal();
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
document.addEventListener('click', function(e) {
|
|
1072
|
+
var modal = document.getElementById('custom-css-modal');
|
|
1073
|
+
if (!modal || !modal.classList.contains('open')) return;
|
|
1074
|
+
if (e.target === modal) closeCustomCssModal();
|
|
1075
|
+
});
|
|
1076
|
+
|
|
964
1077
|
// \u2500\u2500 State \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
965
1078
|
var experimentData = null;
|
|
966
1079
|
var variations = [];
|
|
@@ -1040,14 +1153,17 @@ function endSuppressIframeMutationDirty() {
|
|
|
1040
1153
|
function commitStateChangesForActiveVariation() {
|
|
1041
1154
|
if (!activeVarId) return;
|
|
1042
1155
|
stateChangesByVarId[activeVarId] = (stateChanges || []).slice();
|
|
1156
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1043
1157
|
}
|
|
1044
1158
|
|
|
1045
1159
|
function loadStateChangesForActiveVariation() {
|
|
1046
1160
|
if (!activeVarId) {
|
|
1047
1161
|
stateChanges = [];
|
|
1162
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1048
1163
|
return;
|
|
1049
1164
|
}
|
|
1050
1165
|
stateChanges = (stateChangesByVarId[activeVarId] || []).slice();
|
|
1166
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1051
1167
|
}
|
|
1052
1168
|
|
|
1053
1169
|
function recoverSelectedElement(forceDeselectOnMiss) {
|
|
@@ -1527,24 +1643,26 @@ function persistActiveVariationChangesets(arr) {
|
|
|
1527
1643
|
}
|
|
1528
1644
|
}
|
|
1529
1645
|
}
|
|
1646
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1530
1647
|
}
|
|
1531
1648
|
|
|
1532
1649
|
function entrySnapshotKey(entry) {
|
|
1533
1650
|
if (!entry || !entry.selector) return '';
|
|
1651
|
+
var SEP = '__vve_sep__';
|
|
1534
1652
|
var selKey = sanitizeSelectorForMatch(entry.selector) || entry.selector;
|
|
1535
1653
|
return (
|
|
1536
1654
|
selKey +
|
|
1537
|
-
|
|
1655
|
+
SEP +
|
|
1538
1656
|
normalizeChangesetType(entry) +
|
|
1539
|
-
|
|
1657
|
+
SEP +
|
|
1540
1658
|
String(entry.property || '') +
|
|
1541
|
-
|
|
1659
|
+
SEP +
|
|
1542
1660
|
String(entry.attribute || '') +
|
|
1543
|
-
|
|
1661
|
+
SEP +
|
|
1544
1662
|
String(entry.action || '') +
|
|
1545
|
-
|
|
1663
|
+
SEP +
|
|
1546
1664
|
String(entry.html != null ? 'h' + String(entry.html).length : '') +
|
|
1547
|
-
|
|
1665
|
+
SEP +
|
|
1548
1666
|
String(entry.value != null ? 'v:' + entry.value : '')
|
|
1549
1667
|
);
|
|
1550
1668
|
}
|
|
@@ -1594,7 +1712,6 @@ function softReloadEditorIframe() {
|
|
|
1594
1712
|
var navGen = nextIframeContentNavGen();
|
|
1595
1713
|
resetIframeBindings();
|
|
1596
1714
|
setIframePageLoadingUi(true);
|
|
1597
|
-
iframe.src = '';
|
|
1598
1715
|
iframe.src = appendIframeReloadBust(src);
|
|
1599
1716
|
startIframeContentApplyWatcher(navGen);
|
|
1600
1717
|
scheduleDomTreeRefresh();
|
|
@@ -1603,6 +1720,7 @@ function softReloadEditorIframe() {
|
|
|
1603
1720
|
/** @returns {boolean} true if a full iframe reload was started */
|
|
1604
1721
|
function revertChangesetEntryOnDom(entry) {
|
|
1605
1722
|
if (!entry) return false;
|
|
1723
|
+
var styleOnly = isStyleOnlyChangesetEntry(entry);
|
|
1606
1724
|
if (entry.selector === '__vvveb_body__') {
|
|
1607
1725
|
var iframeDoc0 = document.getElementById('iframeId').contentDocument;
|
|
1608
1726
|
if (!iframeDoc0 || !iframeDoc0.body) return false;
|
|
@@ -1623,6 +1741,11 @@ function revertChangesetEntryOnDom(entry) {
|
|
|
1623
1741
|
var snap = appliedChangesetSnapshots[k];
|
|
1624
1742
|
var el = querySelectorResolved(iframeDoc, entry.selector);
|
|
1625
1743
|
if (!snap || !el) {
|
|
1744
|
+
if (styleOnly) {
|
|
1745
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1746
|
+
delete appliedChangesetSnapshots[k];
|
|
1747
|
+
return false;
|
|
1748
|
+
}
|
|
1626
1749
|
softReloadEditorIframe();
|
|
1627
1750
|
delete appliedChangesetSnapshots[k];
|
|
1628
1751
|
return true;
|
|
@@ -1637,6 +1760,11 @@ function revertChangesetEntryOnDom(entry) {
|
|
|
1637
1760
|
else el.setAttribute(snap.name, snap.v);
|
|
1638
1761
|
} else if (snap.kind === 'display') el.style.display = snap.v;
|
|
1639
1762
|
else {
|
|
1763
|
+
if (styleOnly) {
|
|
1764
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1765
|
+
delete appliedChangesetSnapshots[k];
|
|
1766
|
+
return false;
|
|
1767
|
+
}
|
|
1640
1768
|
softReloadEditorIframe();
|
|
1641
1769
|
delete appliedChangesetSnapshots[k];
|
|
1642
1770
|
return true;
|
|
@@ -1696,7 +1824,9 @@ function renderHistoryTab() {
|
|
|
1696
1824
|
var lab = historyEntryTypeLabel(item.entry);
|
|
1697
1825
|
var val = historyEntryValuePreview(item.entry);
|
|
1698
1826
|
html +=
|
|
1699
|
-
'<div class="state-item"
|
|
1827
|
+
'<div class="state-item" role="button" tabindex="0" title="Jump to element in iframe" onclick="focusHistoryChangeset(' +
|
|
1828
|
+
item.idx +
|
|
1829
|
+
')">' +
|
|
1700
1830
|
'<span class="state-item-idx" style="opacity:0.55;font-size:10px;min-width:2.2em;display:inline-block" title="Order in saved changesets">#' +
|
|
1701
1831
|
item.idx +
|
|
1702
1832
|
'</span>' +
|
|
@@ -1712,7 +1842,7 @@ function renderHistoryTab() {
|
|
|
1712
1842
|
item.idx +
|
|
1713
1843
|
')" onclick="removeHistoryChangeset(' +
|
|
1714
1844
|
item.idx +
|
|
1715
|
-
')">✕</button>' +
|
|
1845
|
+
', event)">✕</button>' +
|
|
1716
1846
|
'</div>';
|
|
1717
1847
|
});
|
|
1718
1848
|
html += '</div>';
|
|
@@ -1730,7 +1860,38 @@ function changesetListHasStructural(arr) {
|
|
|
1730
1860
|
return false;
|
|
1731
1861
|
}
|
|
1732
1862
|
|
|
1733
|
-
function
|
|
1863
|
+
function isStyleOnlyChangesetEntry(entry) {
|
|
1864
|
+
if (!entry) return false;
|
|
1865
|
+
var t = normalizeChangesetType(entry);
|
|
1866
|
+
if (t === 'style') return true;
|
|
1867
|
+
if (t === 'attribute' && String(entry.attribute || '').toLowerCase() === 'style') return true;
|
|
1868
|
+
return false;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
function focusHistoryChangeset(idx) {
|
|
1872
|
+
var v = getActiveVariationForHistory();
|
|
1873
|
+
if (!v) return;
|
|
1874
|
+
var arr = parseVariationChangesets(v);
|
|
1875
|
+
if (idx < 0 || idx >= arr.length) return;
|
|
1876
|
+
var entry = arr[idx];
|
|
1877
|
+
if (!entry || !entry.selector || entry.selector === '__vvveb_body__') return;
|
|
1878
|
+
try {
|
|
1879
|
+
var iframe = document.getElementById('iframeId');
|
|
1880
|
+
var iframeDoc = iframe && iframe.contentDocument;
|
|
1881
|
+
if (!iframeDoc) return;
|
|
1882
|
+
var el = querySelectorResolved(iframeDoc, entry.selector);
|
|
1883
|
+
if (!el) return;
|
|
1884
|
+
selectElement(el);
|
|
1885
|
+
try {
|
|
1886
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
|
|
1887
|
+
} catch(_) {
|
|
1888
|
+
el.scrollIntoView();
|
|
1889
|
+
}
|
|
1890
|
+
} catch(_) {}
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
function removeHistoryChangeset(idx, evt) {
|
|
1894
|
+
if (evt && evt.stopPropagation) evt.stopPropagation();
|
|
1734
1895
|
var v = getActiveVariationForHistory();
|
|
1735
1896
|
if (!v) return;
|
|
1736
1897
|
var arr = parseVariationChangesets(v);
|
|
@@ -1738,6 +1899,16 @@ function removeHistoryChangeset(idx) {
|
|
|
1738
1899
|
var removed = arr[idx];
|
|
1739
1900
|
arr.splice(idx, 1);
|
|
1740
1901
|
persistActiveVariationChangesets(arr);
|
|
1902
|
+
if (isStyleOnlyChangesetEntry(removed)) {
|
|
1903
|
+
try {
|
|
1904
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1905
|
+
saveCurrentVariationHtml();
|
|
1906
|
+
} catch(_) {}
|
|
1907
|
+
if (currentMainTab === 'history') renderHistoryTab();
|
|
1908
|
+
recomputeEditorDirty();
|
|
1909
|
+
scheduleDomTreeRefresh();
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1741
1912
|
var didReload = revertChangesetEntryOnDom(removed);
|
|
1742
1913
|
try {
|
|
1743
1914
|
delete varHtmlCache[activeVarId];
|
|
@@ -1745,15 +1916,19 @@ function removeHistoryChangeset(idx) {
|
|
|
1745
1916
|
// Re-applying remaining rows on top of current DOM duplicates insert/reorder nodes; reload when any
|
|
1746
1917
|
// structural row remains or was removed (revert may already have started a reload for insert/body).
|
|
1747
1918
|
var removedType = normalizeChangesetType(removed);
|
|
1748
|
-
var
|
|
1749
|
-
|
|
1750
|
-
(removedType === 'insert' ||
|
|
1751
|
-
removedType === 'reorder' ||
|
|
1752
|
-
changesetListHasStructural(arr));
|
|
1919
|
+
var hasStructuralRemaining = changesetListHasStructural(arr);
|
|
1920
|
+
var removedIsStructural = removedType === 'insert' || removedType === 'reorder';
|
|
1753
1921
|
if (didReload) {
|
|
1754
1922
|
/* revertChangesetEntryOnDom already kicked off iframe reload */
|
|
1755
|
-
} else if (
|
|
1923
|
+
} else if (removedIsStructural) {
|
|
1756
1924
|
softReloadEditorIframe();
|
|
1925
|
+
} else if (hasStructuralRemaining) {
|
|
1926
|
+
// Keep current DOM state (already reverted for removed row) and only refresh style layer.
|
|
1927
|
+
// Avoid full reload and avoid re-applying all rows, which can duplicate structural insert/reorder entries.
|
|
1928
|
+
try {
|
|
1929
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
1930
|
+
saveCurrentVariationHtml();
|
|
1931
|
+
} catch(_) {}
|
|
1757
1932
|
} else {
|
|
1758
1933
|
try {
|
|
1759
1934
|
appliedStructuralChangesetKeys = {};
|
|
@@ -2121,7 +2296,7 @@ function runConsistencyReconcile() {
|
|
|
2121
2296
|
if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
|
|
2122
2297
|
var granular = filterGranularChangesetEntries(cs);
|
|
2123
2298
|
var unresolved = countUnresolvedGranularSelectors(doc, granular);
|
|
2124
|
-
if (unresolved > 0 ||
|
|
2299
|
+
if (unresolved > 0 || hasUnappliedStructuralChangesets(cs)) {
|
|
2125
2300
|
reapplyActiveVariationGranular(doc);
|
|
2126
2301
|
registerPendingGranularChangesets(cs, doc);
|
|
2127
2302
|
}
|
|
@@ -2491,24 +2666,136 @@ function flushPendingGranularChangesets() {
|
|
|
2491
2666
|
function structuralChangesetDedupKey(entry) {
|
|
2492
2667
|
var nt = normalizeChangesetType(entry);
|
|
2493
2668
|
if (!entry || (nt !== 'insert' && nt !== 'reorder')) return '';
|
|
2669
|
+
var SEP = '__vve_sep__';
|
|
2494
2670
|
var vid = activeVarId || '';
|
|
2495
2671
|
try {
|
|
2496
2672
|
return (
|
|
2497
2673
|
vid +
|
|
2498
|
-
|
|
2674
|
+
SEP +
|
|
2499
2675
|
nt +
|
|
2500
|
-
|
|
2676
|
+
SEP +
|
|
2501
2677
|
entry.selector +
|
|
2502
|
-
|
|
2678
|
+
SEP +
|
|
2503
2679
|
String(entry.action || '') +
|
|
2504
|
-
|
|
2680
|
+
SEP +
|
|
2505
2681
|
String(entry.html != null ? entry.html : '').slice(0, 240) +
|
|
2506
|
-
|
|
2682
|
+
SEP +
|
|
2507
2683
|
String(entry.targetSelector || '')
|
|
2508
2684
|
);
|
|
2509
2685
|
} catch(_) {
|
|
2510
|
-
return vid +
|
|
2686
|
+
return vid + SEP + nt + SEP + entry.selector;
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
function hasUnappliedStructuralChangesets(cs) {
|
|
2691
|
+
if (!cs || !cs.length) return false;
|
|
2692
|
+
for (var i = 0; i < cs.length; i++) {
|
|
2693
|
+
var e = cs[i];
|
|
2694
|
+
var t = normalizeChangesetType(e);
|
|
2695
|
+
if (t !== 'insert' && t !== 'reorder') continue;
|
|
2696
|
+
var k = structuralChangesetDedupKey(e);
|
|
2697
|
+
if (!k) return true;
|
|
2698
|
+
if (!appliedStructuralChangesetKeys[k]) return true;
|
|
2699
|
+
}
|
|
2700
|
+
return false;
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
function parseInlineStyleDeclarations(styleText) {
|
|
2704
|
+
var out = [];
|
|
2705
|
+
if (styleText == null) return out;
|
|
2706
|
+
var s = String(styleText);
|
|
2707
|
+
if (!s.trim()) return out;
|
|
2708
|
+
var parts = s.split(';');
|
|
2709
|
+
for (var i = 0; i < parts.length; i++) {
|
|
2710
|
+
var seg = parts[i];
|
|
2711
|
+
if (!seg) continue;
|
|
2712
|
+
var idx = seg.indexOf(':');
|
|
2713
|
+
if (idx <= 0) continue;
|
|
2714
|
+
var prop = seg.slice(0, idx).trim();
|
|
2715
|
+
var value = seg.slice(idx + 1).trim();
|
|
2716
|
+
if (!prop || !value) continue;
|
|
2717
|
+
out.push({ prop: prop, value: value });
|
|
2511
2718
|
}
|
|
2719
|
+
return out;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
function buildPersistentStyleRulesForActiveVariation() {
|
|
2723
|
+
if (!activeVarId) return '';
|
|
2724
|
+
var v = variations.find(function(x) { return x && x._id === activeVarId; });
|
|
2725
|
+
var parsed = parseVariationChangesets(v);
|
|
2726
|
+
var map = {};
|
|
2727
|
+
var order = [];
|
|
2728
|
+
function put(selector, prop, value) {
|
|
2729
|
+
if (!selector || !prop) return;
|
|
2730
|
+
if (value == null || value === '') return;
|
|
2731
|
+
var sel = sanitizeSelectorForMatch(String(selector)) || String(selector);
|
|
2732
|
+
var pr = String(prop).trim();
|
|
2733
|
+
var val = String(value).trim();
|
|
2734
|
+
if (!sel || !pr || !val) return;
|
|
2735
|
+
var k = sel + '__vve_sep__' + pr;
|
|
2736
|
+
if (!map[k]) order.push(k);
|
|
2737
|
+
map[k] = { selector: sel, property: pr, value: val };
|
|
2738
|
+
}
|
|
2739
|
+
for (var i = 0; i < parsed.length; i++) {
|
|
2740
|
+
var e = parsed[i];
|
|
2741
|
+
if (!e) continue;
|
|
2742
|
+
var t = normalizeChangesetType(e);
|
|
2743
|
+
if (t === 'style') {
|
|
2744
|
+
put(e.selector, e.property || e.cssProp, e.value);
|
|
2745
|
+
continue;
|
|
2746
|
+
}
|
|
2747
|
+
if (t === 'attribute' && String(e.attribute || '').toLowerCase() === 'style') {
|
|
2748
|
+
var decls = parseInlineStyleDeclarations(e.value);
|
|
2749
|
+
for (var di = 0; di < decls.length; di++) {
|
|
2750
|
+
put(e.selector, decls[di].prop, decls[di].value);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
for (var j = 0; j < stateChanges.length; j++) {
|
|
2755
|
+
var c = stateChanges[j];
|
|
2756
|
+
if (!c) continue;
|
|
2757
|
+
if (c.cssProp) {
|
|
2758
|
+
put(c.selector, c.cssProp, c.value);
|
|
2759
|
+
continue;
|
|
2760
|
+
}
|
|
2761
|
+
if (c.inputId === 'pp-css') {
|
|
2762
|
+
var liveDecls = parseInlineStyleDeclarations(c.value);
|
|
2763
|
+
for (var ldi = 0; ldi < liveDecls.length; ldi++) {
|
|
2764
|
+
put(c.selector, liveDecls[ldi].prop, liveDecls[ldi].value);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
var lines = [];
|
|
2769
|
+
for (var oi = 0; oi < order.length; oi++) {
|
|
2770
|
+
var row = map[order[oi]];
|
|
2771
|
+
lines.push(row.selector + ' { ' + row.property + ': ' + row.value + ' !important; }');
|
|
2772
|
+
}
|
|
2773
|
+
return lines.join('\\n');
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
function upsertPersistentChangesetStyleTag(iframeDoc, rulesText) {
|
|
2777
|
+
if (!iframeDoc) return;
|
|
2778
|
+
var STYLE_ID = '__vve_persist_changesets_style__';
|
|
2779
|
+
var prev = iframeDoc.getElementById(STYLE_ID);
|
|
2780
|
+
if (!rulesText) {
|
|
2781
|
+
if (prev && prev.parentNode) prev.parentNode.removeChild(prev);
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2784
|
+
var head = iframeDoc.head || iframeDoc.getElementsByTagName('head')[0];
|
|
2785
|
+
if (!head) return;
|
|
2786
|
+
var styleEl = prev || iframeDoc.createElement('style');
|
|
2787
|
+
styleEl.id = STYLE_ID;
|
|
2788
|
+
if (styleEl.textContent !== rulesText) styleEl.textContent = rulesText;
|
|
2789
|
+
if (!styleEl.parentNode) head.appendChild(styleEl);
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
function refreshPersistentChangesetStyleTagForActiveVariation() {
|
|
2793
|
+
try {
|
|
2794
|
+
var iframe = document.getElementById('iframeId');
|
|
2795
|
+
var iframeDoc = iframe && iframe.contentDocument;
|
|
2796
|
+
if (!iframeDoc) return;
|
|
2797
|
+
upsertPersistentChangesetStyleTag(iframeDoc, buildPersistentStyleRulesForActiveVariation());
|
|
2798
|
+
} catch(_) {}
|
|
2512
2799
|
}
|
|
2513
2800
|
|
|
2514
2801
|
/**
|
|
@@ -2545,23 +2832,11 @@ function applyChangesetEntry(entry, iframeDoc) {
|
|
|
2545
2832
|
else if (entry.value != null) el.textContent = entry.value;
|
|
2546
2833
|
break;
|
|
2547
2834
|
case 'style':
|
|
2548
|
-
|
|
2549
|
-
var propKebab = entry.property;
|
|
2550
|
-
var cam = camelize(propKebab);
|
|
2551
|
-
if (entry.value == null || entry.value === '') {
|
|
2552
|
-
try { el.style.removeProperty(propKebab); } catch(_) {}
|
|
2553
|
-
try { if (cam in el.style) el.style[cam] = ''; } catch(__) {}
|
|
2554
|
-
} else {
|
|
2555
|
-
try {
|
|
2556
|
-
el.style.setProperty(propKebab, entry.value, 'important');
|
|
2557
|
-
} catch(_) {
|
|
2558
|
-
el.style[cam] = entry.value;
|
|
2559
|
-
}
|
|
2560
|
-
}
|
|
2561
|
-
}
|
|
2835
|
+
// Style changes are applied via persistent stylesheet injection.
|
|
2562
2836
|
break;
|
|
2563
2837
|
case 'attribute':
|
|
2564
2838
|
if (entry.attribute && entry.value != null) {
|
|
2839
|
+
if (String(entry.attribute).toLowerCase() === 'style') break;
|
|
2565
2840
|
el.setAttribute(entry.attribute, entry.value);
|
|
2566
2841
|
}
|
|
2567
2842
|
break;
|
|
@@ -2598,6 +2873,7 @@ function applyActiveVariationHtml() {
|
|
|
2598
2873
|
|
|
2599
2874
|
var variation = variations.find(function(v) { return v._id === activeVarId; });
|
|
2600
2875
|
var cs = parseVariationChangesets(variation);
|
|
2876
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
2601
2877
|
|
|
2602
2878
|
beginSuppressIframeMutationDirty();
|
|
2603
2879
|
try {
|
|
@@ -2612,6 +2888,7 @@ function applyActiveVariationHtml() {
|
|
|
2612
2888
|
for (var i = 0; i < cs.length; i++) {
|
|
2613
2889
|
applyChangesetEntry(cs[i], iframeDoc);
|
|
2614
2890
|
}
|
|
2891
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
2615
2892
|
// Selectors often miss on first paint (client-rendered sections). Retry via timer + mutations.
|
|
2616
2893
|
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2617
2894
|
} finally {
|
|
@@ -2660,6 +2937,7 @@ function applyVariationGranularOnly(iframeDoc) {
|
|
|
2660
2937
|
for (var i = 0; i < cs.length; i++) {
|
|
2661
2938
|
applyChangesetEntry(cs[i], iframeDoc);
|
|
2662
2939
|
}
|
|
2940
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
2663
2941
|
registerPendingGranularChangesets(cs, iframeDoc);
|
|
2664
2942
|
} finally {
|
|
2665
2943
|
endSuppressIframeMutationDirty();
|
|
@@ -2678,6 +2956,7 @@ function reapplyActiveVariationGranular(iframeDoc) {
|
|
|
2678
2956
|
for (var i = 0; i < cs.length; i++) {
|
|
2679
2957
|
applyChangesetEntry(cs[i], iframeDoc);
|
|
2680
2958
|
}
|
|
2959
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
2681
2960
|
} finally {
|
|
2682
2961
|
endSuppressIframeMutationDirty();
|
|
2683
2962
|
}
|
|
@@ -3166,6 +3445,40 @@ function pr2(l1, i1, l2, i2) {
|
|
|
3166
3445
|
'<div class="pr2-item"><div class="pr2-lbl">'+l2+'</div>'+i2+'</div></div>';
|
|
3167
3446
|
}
|
|
3168
3447
|
function subLbl(text) { return '<div class="sub-lbl">'+text+'</div>'; }
|
|
3448
|
+
function openCustomCssModal() {
|
|
3449
|
+
var modal = document.getElementById('custom-css-modal');
|
|
3450
|
+
var ta = document.getElementById('custom-css-modal-textarea');
|
|
3451
|
+
if (!modal || !ta) return;
|
|
3452
|
+
var inp = document.getElementById('pp-css');
|
|
3453
|
+
var v = inp ? inp.value : (selectedEl && selectedEl.getAttribute ? (selectedEl.getAttribute('style') || '') : '');
|
|
3454
|
+
ta.value = v || '';
|
|
3455
|
+
modal.classList.add('open');
|
|
3456
|
+
modal.setAttribute('aria-hidden', 'false');
|
|
3457
|
+
setTimeout(function() {
|
|
3458
|
+
try { ta.focus(); ta.setSelectionRange(ta.value.length, ta.value.length); } catch(_) {}
|
|
3459
|
+
}, 0);
|
|
3460
|
+
}
|
|
3461
|
+
function closeCustomCssModal() {
|
|
3462
|
+
var modal = document.getElementById('custom-css-modal');
|
|
3463
|
+
if (!modal) return;
|
|
3464
|
+
modal.classList.remove('open');
|
|
3465
|
+
modal.setAttribute('aria-hidden', 'true');
|
|
3466
|
+
}
|
|
3467
|
+
function applyCustomCssModal() {
|
|
3468
|
+
var ta = document.getElementById('custom-css-modal-textarea');
|
|
3469
|
+
var inp = document.getElementById('pp-css');
|
|
3470
|
+
if (!ta || !inp) {
|
|
3471
|
+
closeCustomCssModal();
|
|
3472
|
+
return;
|
|
3473
|
+
}
|
|
3474
|
+
inp.value = ta.value || '';
|
|
3475
|
+
try {
|
|
3476
|
+
inp.dispatchEvent(new Event('input', { bubbles: true }));
|
|
3477
|
+
} catch(_) {
|
|
3478
|
+
if (typeof inp.oninput === 'function') inp.oninput();
|
|
3479
|
+
}
|
|
3480
|
+
closeCustomCssModal();
|
|
3481
|
+
}
|
|
3169
3482
|
function weightOpts(cur) {
|
|
3170
3483
|
return [['100','Thin'],['200','Extra Light'],['300','Light'],['400','Normal'],['500','Medium'],
|
|
3171
3484
|
['600','Semi Bold'],['700','Bold'],['800','Extra Bold'],['900','Black']]
|
|
@@ -3446,7 +3759,12 @@ function renderRightPanel(el) {
|
|
|
3446
3759
|
// \u2500\u2500 CSS and Classes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3447
3760
|
document.getElementById('acc-body-css').innerHTML =
|
|
3448
3761
|
pr('Classes', '<input class="pr-inp" id="pp-cls" type="text" value="'+esc(el.className||'')+'" placeholder="class1 class2">') +
|
|
3449
|
-
|
|
3762
|
+
'<div class="sub-lbl-row">' +
|
|
3763
|
+
'<div class="sub-lbl">Custom CSS</div>' +
|
|
3764
|
+
'<button type="button" class="css-expand-btn" title="Open full-screen editor" onclick="openCustomCssModal()">' +
|
|
3765
|
+
'<i class="bi bi-fullscreen"></i>' +
|
|
3766
|
+
'</button>' +
|
|
3767
|
+
'</div>' +
|
|
3450
3768
|
'<textarea class="pr-inp" id="pp-css" style="width:100%;min-height:80px;font-family:monospace;font-size:11px" placeholder="color: red; font-size: 16px;">'+esc(el.getAttribute('style')||'')+'</textarea>';
|
|
3451
3769
|
|
|
3452
3770
|
// \u2500\u2500 Attributes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -3810,26 +4128,6 @@ function attachChangeObserver() {
|
|
|
3810
4128
|
}
|
|
3811
4129
|
changeObserver = new MutationObserver(function(mutations) {
|
|
3812
4130
|
// Dirty state is derived from changesets baseline + stateChanges (not raw DOM mutations).
|
|
3813
|
-
var bodyReplaced = false;
|
|
3814
|
-
for (var mi = 0; mi < mutations.length; mi++) {
|
|
3815
|
-
var m = mutations[mi];
|
|
3816
|
-
if (
|
|
3817
|
-
m &&
|
|
3818
|
-
m.type === 'childList' &&
|
|
3819
|
-
m.target === doc.body &&
|
|
3820
|
-
m.addedNodes &&
|
|
3821
|
-
m.removedNodes &&
|
|
3822
|
-
m.addedNodes.length > 0 &&
|
|
3823
|
-
m.removedNodes.length > 0
|
|
3824
|
-
) {
|
|
3825
|
-
bodyReplaced = true;
|
|
3826
|
-
break;
|
|
3827
|
-
}
|
|
3828
|
-
}
|
|
3829
|
-
if (bodyReplaced) {
|
|
3830
|
-
// Page JS replaced body children; allow structural rows (insert/reorder) to apply again.
|
|
3831
|
-
appliedStructuralChangesetKeys = {};
|
|
3832
|
-
}
|
|
3833
4131
|
// Host scripts can replace selected nodes every few frames (e.g. A/B tool observers).
|
|
3834
4132
|
// Keep selection sticky by re-resolving from fingerprint.
|
|
3835
4133
|
recoverSelectedElement(false);
|
|
@@ -3863,6 +4161,7 @@ function syncIframeInteractions(reason) {
|
|
|
3863
4161
|
}
|
|
3864
4162
|
showNoUrl(false);
|
|
3865
4163
|
injectIframeSelectionStyles(doc);
|
|
4164
|
+
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
3866
4165
|
attachClickHandler();
|
|
3867
4166
|
attachDragReposition();
|
|
3868
4167
|
attachChangeObserver();
|