@accelerated-agency/visual-editor 0.3.4 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vite.cjs CHANGED
@@ -11,6 +11,42 @@ var fs__default = /*#__PURE__*/_interopDefault(fs);
11
11
  var path__default = /*#__PURE__*/_interopDefault(path);
12
12
 
13
13
  // src/visualEditorProxyPlugin.ts
14
+ var SCRAPER_PROXY_HOST = process.env.SCRAPERAPI_PROXY_HOST || "proxy-server.scraperapi.com";
15
+ var SCRAPER_PROXY_PORT = Number(process.env.SCRAPERAPI_PROXY_PORT || "8001");
16
+ var SCRAPER_PROXY_USERNAME_BASE = process.env.SCRAPERAPI_PROXY_USERNAME_BASE || "scraperapi";
17
+ var SCRAPER_PROXY_USERNAME_PARAMS = process.env.SCRAPERAPI_PROXY_USERNAME_PARAMS || "render=true.wait_for_selector=body.follow_redirect=false.keep_headers=true";
18
+ var SCRAPER_PROXY_USERNAME = process.env.SCRAPERAPI_PROXY_USERNAME || [
19
+ SCRAPER_PROXY_USERNAME_BASE,
20
+ SCRAPER_PROXY_USERNAME_PARAMS
21
+ ].filter(Boolean).join(".");
22
+ var SCRAPER_PROXY_PASSWORD = process.env.SCRAPERAPI_PROXY_PASSWORD || process.env.SCRAPERAPI_API_KEY || "e0252333bde7cbf61d2d388e8c4a962a";
23
+ var SCRAPER_API_ENDPOINT = process.env.SCRAPERAPI_ENDPOINT || "https://api.scraperapi.com/";
24
+ var SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED_RAW = process.env.SCRAPERAPI_REQUEST_TLS_REJECT_UNAUTHORIZED || "false";
25
+ var SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED = !["0", "false", "no"].includes(
26
+ SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED_RAW.toLowerCase()
27
+ );
28
+ var scraperProxyClientPromise = null;
29
+ async function getScraperProxyClient() {
30
+ if (scraperProxyClientPromise) return scraperProxyClientPromise;
31
+ scraperProxyClientPromise = (async () => {
32
+ try {
33
+ const undici = await import('undici');
34
+ const proxyUrl = "http://" + encodeURIComponent(SCRAPER_PROXY_USERNAME) + ":" + encodeURIComponent(SCRAPER_PROXY_PASSWORD) + "@" + SCRAPER_PROXY_HOST + ":" + String(SCRAPER_PROXY_PORT);
35
+ return {
36
+ dispatcher: new undici.ProxyAgent({
37
+ uri: proxyUrl,
38
+ requestTls: {
39
+ rejectUnauthorized: SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED
40
+ }
41
+ }),
42
+ fetchFn: undici.fetch
43
+ };
44
+ } catch (_) {
45
+ return null;
46
+ }
47
+ })();
48
+ return scraperProxyClientPromise;
49
+ }
14
50
  var iframeAlwaysShowCss = `<style id="__ce_force_show">#CybotCookiebotDialog,#CybotCookiebotDialogBodyUnderlay,#onetrust-consent-sdk,#onetrust-banner-sdk,.cc-window,.cc-banner,.cc-overlay,#cookie-notice,#cookie-banner,#cookie-consent,.cookie-notice,.cookie-banner,.cookie-consent,.cookie-popup,.cookie-bar,.cookie-message,.cookie-alert,.gdpr-banner,.gdpr-consent,.gdpr-popup,.gdpr-overlay,#gdpr-consent,#gdpr-banner,.consent-banner,.consent-popup,.consent-overlay,#consent-banner,#consent-popup,[class*="cookie-consent"],[class*="cookie-banner"],[class*="cookie-notice"],[class*="CookieConsent"],[class*="CookieBanner"],[id*="cookie-consent"],[id*="cookie-banner"],[id*="cookie-notice"],[aria-label*="cookie" i],[aria-label*="consent" i],.klaro,.klaro .cookie-modal,#usercentrics-root,.trustarc-banner,#truste-consent-track,#hs-eu-cookie-confirmation,.osano-cm-window,.osano-cm-dialog,.evidon-banner,#_evidon_banner,.js-cookie-consent,.cookie-disclaimer,.shopify-section-cookies,#shopify-section-cookies,#shopify-pc__banner,#shopify-pc__modal,.privacy-banner,.privacy-popup,[data-testid="cookie-banner"],[data-testid="consent-banner"],.amgdprcookie-bar-container,[data-amcookie-js="bar"],.amgdprjs-bar-template,.amgdprcookie-modal-container,.amgdprcookie-modal-overlay,#cmplz-cookiebanner-container,.cmplz-cookiebanner,#iubenda-cs-banner,.iubenda-cs-container,#qc-cmp2-container,.qc-cmp2-consent-info,#didomi-host,.didomi-popup-container,.didomi-notice,#termly-code-snippet-support,[class*="termly"],[class*="gdprcookie"],[class*="amgdpr"],[id*="gdpr-cookie"],[class*="cookie-modal"],[id*="cookie-modal"],[class*="cookieConsent"],[id*="cookieConsent"]{display:revert!important;visibility:visible!important;opacity:1!important;pointer-events:auto!important;height:auto!important;max-height:none!important;overflow:visible!important;}</style>`;
15
51
  var iframeAlwaysShowCssGuardScript = `<script id="__ce_force_show_guard">(function(){try{
16
52
  function ensureForceShowStyleLast(){
@@ -355,21 +391,21 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
355
391
  .lp-sec{border-bottom:1px solid var(--border);flex-shrink:0;padding: 24px;}
356
392
  .lp-sec-no-border{border-bottom:none!important}
357
393
  .lp-sec-hd{
358
- padding:10px 12px 8px;font-size:14px;font-weight:600;
394
+ margin-bottom: 12px;
395
+ font-size:14px;font-weight:600;
359
396
  color:#404040;display:flex;align-items:center;justify-content:space-between;gap:5px
360
397
  }
361
398
  .lp-sec-hd-left{display:flex;align-items:center;gap:5px}
362
399
  #active-var-label{display:none;color:var(--accent-txt);font-size:10px;font-weight:500}
363
400
  .lp-info-icon{font-size:11px;color:var(--text-3);cursor:default;opacity:.7}
364
401
  .lp-add-btn{
365
- display:none;
366
402
  background:none;border:none;color:var(--text);font-size:14px;font-weight:500;
367
403
  cursor:pointer;padding:2px 5px;border-radius:4px;transition:all .12s;flex-shrink:0
368
404
  }
369
405
  .lp-add-btn:hover{background:var(--bg-hover);color:var(--accent-txt)}
370
406
 
371
407
  /* \u2500\u2500 Variation tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
372
- #variation-tabs{padding:2px 0 6px;display:flex;flex-direction:column; gap:8px;}
408
+ #variation-tabs{display:flex;flex-direction:column; gap:8px;}
373
409
  .var-tab{
374
410
  border-radius: 4px;
375
411
  border: 1px solid #e5e7eb;
@@ -400,7 +436,7 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
400
436
  #comp-search:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(99,102,241,.12)}
401
437
 
402
438
  /* \u2500\u2500 Left tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
403
- .lp-tabs{display:flex;border-bottom:1px solid var(--border);flex-shrink:0;background:#fff}
439
+ .lp-tabs, .section-components-tabs{display:flex;border-bottom:1px solid var(--border);flex-shrink:0;background:#fff}
404
440
  .lp-tab{
405
441
  flex:1;padding:8px 2px;text-align:center;font-size:10px;color:var(--text-3);
406
442
  cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;font-weight:600;line-height:1.15
@@ -410,10 +446,10 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
410
446
  .future-hidden{display:none!important}
411
447
 
412
448
  /* \u2500\u2500 Left panel body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
413
- .lp-body{flex:1;overflow-y:auto}
414
- .lp-body::-webkit-scrollbar{width:3px}
415
- .lp-body::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:2px}
416
- .tab-pane{display:none}.tab-pane.active{display:block}
449
+ .lp-body, .section-components-body{flex:1;overflow-y:auto}
450
+ .lp-body::-webkit-scrollbar, .section-components-body::-webkit-scrollbar{width:3px}
451
+ .lp-body::-webkit-scrollbar-thumb, .section-components-body::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:2px}
452
+ .tab-pane, .section-components-tab-pane{display:none}.tab-pane.active, .section-components-tab-pane.active{display:block}
417
453
 
418
454
  /* \u2500\u2500 Component grid \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
419
455
  .cg-hdr{padding:8px 10px 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)}
@@ -625,12 +661,83 @@ select.pr-inp{cursor:pointer;background:#fff}
625
661
  }
626
662
  #states-clear:hover{border-color:#fca5a5;color:#ef4444;background:#fef2f2}
627
663
  #history-clear{
628
- display:block;width:calc(100% - 24px);margin:10px 12px;
664
+ display:block;width:calc(100% - 24px);margin:10px 12px 8px;
629
665
  background:none;border:1px solid var(--border);border-radius:6px;
630
666
  padding:6px;font-size:11px;color:var(--text-2);cursor:pointer;font-family:inherit;
631
667
  transition:all .15s
632
668
  }
633
669
  #history-clear:hover{border-color:#fca5a5;color:#ef4444;background:#fef2f2}
670
+ .history-timeline{
671
+ position:relative;
672
+ margin:8px 0 12px;
673
+ padding:0 12px 0 36px;
674
+ }
675
+ .history-timeline::before{
676
+ content:'';
677
+ position:absolute;
678
+ left:20px;
679
+ top:4px;
680
+ bottom:4px;
681
+ width:2px;
682
+ background:#eceff3;
683
+ border-radius:2px;
684
+ }
685
+ .history-item{
686
+ position:relative;
687
+ display:flex;
688
+ align-items:flex-start;
689
+ gap:10px;
690
+ padding:10px 8px 10px 0;
691
+ cursor:pointer;
692
+ }
693
+ .history-dot{
694
+ position:absolute;
695
+ left:-20px;
696
+ top:18px;
697
+ width:10px;
698
+ height:10px;
699
+ border-radius:50%;
700
+ background:#fff;
701
+ border:2px solid #d7dde6;
702
+ }
703
+ .history-card{
704
+ flex:1;
705
+ min-width:0;
706
+ }
707
+ .history-title{
708
+ font-size:14px;
709
+ font-weight:600;
710
+ color:var(--text);
711
+ line-height:1.3;
712
+ }
713
+ .history-meta{
714
+ margin-top:4px;
715
+ display:flex;
716
+ align-items:center;
717
+ gap:6px;
718
+ color:var(--text-2);
719
+ font-size:11px;
720
+ }
721
+ .history-avatar{
722
+ width:18px;
723
+ height:18px;
724
+ border-radius:50%;
725
+ background:#e9e5ff;
726
+ color:#5b47d6;
727
+ display:flex;
728
+ align-items:center;
729
+ justify-content:center;
730
+ font-size:10px;
731
+ font-weight:700;
732
+ }
733
+ .history-time{
734
+ margin-top:3px;
735
+ font-size:11px;
736
+ color:var(--text-3);
737
+ }
738
+ .history-remove{
739
+ margin-top:2px;
740
+ }
634
741
  </style>
635
742
  </head>
636
743
  <body class="mode-editor">
@@ -712,7 +819,7 @@ select.pr-inp{cursor:pointer;background:#fff}
712
819
  <div class="lp-sec">
713
820
  <div class="lp-sec-hd">
714
821
  <span class="lp-sec-hd-left">Variations <span id="active-var-label"></span></span>
715
- <button class="lp-add-btn" title="Add variation">+ Add</button>
822
+ <button class="lp-add-btn" style="display:none" title="Add variation">+ Add</button>
716
823
  </div>
717
824
  <div id="variation-tabs"></div>
718
825
  </div>
@@ -737,27 +844,39 @@ select.pr-inp{cursor:pointer;background:#fff}
737
844
  <span class="lp-sec-hd-left">Elements <i class="bi bi-info-circle lp-info-icon" title="Page elements"></i></span>
738
845
  <button class="lp-add-btn" title="Add element">+ Add</button>
739
846
  </div>
740
- </div>
741
847
 
742
- <!-- Search (hidden, kept for JS) -->
743
- <div style="display:none">
848
+ <!-- Search (hidden, kept for JS) -->
849
+ <div>
744
850
  <input type="search" id="comp-search" placeholder="Search layers\u2026" autocomplete="off">
745
851
  </div>
746
852
 
853
+
747
854
  <!-- Tabs (hidden, kept for JS) -->
748
- <div class="lp-tabs" style="display:none">
749
- <div class="lp-tab active" onclick="switchLeftTab('elements')">Elements</div>
750
- <div class="lp-tab future-hidden" onclick="switchLeftTab('components')">Components</div>
751
- <div class="lp-tab future-hidden" onclick="switchLeftTab('sections')">Sections</div>
855
+ <div class="lp-tabs" >
856
+ <div class="lp-tab active" onclick="switchLeftTab('elements')">Elements</div>
857
+ <div class="lp-tab" onclick="switchLeftTab('dom-tree')">DOM Tree</div>
858
+ </div>
859
+
752
860
  </div>
753
861
 
754
862
  <!-- Tab content -->
755
863
  <div class="lp-body">
756
- <div id="tab-elements" class="tab-pane active"><div id="dom-tree-root" class="dt-tree"></div></div>
757
- <div id="tab-components" class="tab-pane future-hidden"></div>
758
- <div id="tab-sections" class="tab-pane future-hidden"></div>
864
+ <div id="tab-dom-tree" class="tab-pane">
865
+ <div id="dom-tree-root" class="dt-tree">
866
+ </div>
867
+ </div>
868
+ <div id="tab-elements" class="tab-pane active">
869
+ <div id="elements-root" class="elements-tree">
870
+ </div>
871
+ </div>
759
872
  </div>
760
873
 
874
+
875
+
876
+
877
+
878
+
879
+
761
880
  </div><!-- #left-panel -->
762
881
 
763
882
  <!-- Center / iframe panel -->
@@ -765,7 +884,8 @@ select.pr-inp{cursor:pointer;background:#fff}
765
884
 
766
885
  <!-- Floating toolbar for selected element (positioned over iframe) -->
767
886
  <div id="selection-floater" aria-label="Selection actions">
768
- <button type="button" class="sf-btn" id="sf-drag" title="Move up/down (drag on page after activating)"><i class="bi bi-arrows-move"></i></button>
887
+ <button type="button" class="sf-btn" id="sf-move-up" title="Move up"><i class="bi bi-arrow-up"></i></button>
888
+ <button type="button" class="sf-btn" id="sf-move-down" title="Move down"><i class="bi bi-arrow-down"></i></button>
769
889
  <span class="sf-sep"></span>
770
890
  <button type="button" class="sf-btn" id="sf-resize" disabled title="Resize (coming soon)"><i class="bi bi-arrows-angle-expand"></i></button>
771
891
  <button type="button" class="sf-btn" id="sf-rotate" disabled title="Rotate (coming soon)"><i class="bi bi-arrow-repeat"></i></button>
@@ -792,13 +912,20 @@ select.pr-inp{cursor:pointer;background:#fff}
792
912
 
793
913
  <!-- Right panel -->
794
914
  <div id="right-panel">
795
-
915
+ <!-- Left-tab controls moved here -->
916
+ <div class="section-components-tabs">
917
+ <div class="lp-tab" onclick="switchSectionComponentsTab('components')">Components</div>
918
+ <div class="lp-tab" onclick="switchSectionComponentsTab('sections')">Sections</div>
919
+ </div>
920
+ <div class="lp-body">
921
+ <div id="tab-components" class="tab-pane"></div>
922
+ <div id="tab-sections" class="tab-pane"></div>
923
+ </div>
796
924
  <!-- Element badge (hidden until selection) -->
797
925
  <div id="el-info" style="display:none">
798
926
  <div id="el-info-tag"></div>
799
927
  <div id="el-info-sel"></div>
800
928
  </div>
801
-
802
929
  <!-- \u2500\u2500 3 main tabs \u2500\u2500 -->
803
930
  <div id="main-tabs">
804
931
  <button class="main-tab active" onclick="switchMainTab('design')">Design</button>
@@ -880,12 +1007,7 @@ select.pr-inp{cursor:pointer;background:#fff}
880
1007
 
881
1008
  <!-- \u2500\u2500 States pane \u2500\u2500 -->
882
1009
  <div id="tab-states" class="rp-pane">
883
- <div id="states-list">
884
- <div class="states-empty">
885
- <i class="bi bi-layers"></i>
886
- No changes yet \u2014 edit elements on the page to see states here
887
- </div>
888
- </div>
1010
+ <div id="states-list"></div>
889
1011
  </div><!-- #tab-states -->
890
1012
 
891
1013
  <!-- \u2500\u2500 History pane (saved DB changesets for active variation) \u2500\u2500 -->
@@ -1133,6 +1255,7 @@ var suppressClickUntil = 0;
1133
1255
  var dragAttachDoc = null;
1134
1256
  var currentMainTab = 'design';
1135
1257
  var currentLeftTab = 'elements';
1258
+ var currentSectionComponentsTab = 'components';
1136
1259
  var dragHandleActive = false;
1137
1260
  var domTreeCollapsed = {};
1138
1261
  var domTreeRefreshTimer = null;
@@ -1164,6 +1287,13 @@ var stateChangesByVarId = {};
1164
1287
  var appliedChangesetSnapshots = {};
1165
1288
  /** Canonical JSON fingerprints of persisted changesets per variation (last load / finalize) */
1166
1289
  var baselineChangesetsByVarId = {};
1290
+ /** Monotonic timestamp key for ordering mixed live + saved history rows. */
1291
+ var vveHistorySeq = 0;
1292
+
1293
+ function nextHistoryTimestamp() {
1294
+ vveHistorySeq += 1;
1295
+ return Date.now() * 1000 + vveHistorySeq;
1296
+ }
1167
1297
 
1168
1298
  // \u2500\u2500 Dirty tracking (compare DB baseline + session stateChanges vs current export) \u2500\u2500
1169
1299
  function beginSuppressIframeMutationDirty() {
@@ -1379,27 +1509,45 @@ function setDevice(device) {
1379
1509
 
1380
1510
  // \u2500\u2500 Left panel tab switch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1381
1511
  function switchLeftTab(tab) {
1512
+ if (tab !== 'elements' && tab !== 'dom-tree') return;
1382
1513
  currentLeftTab = tab;
1383
- var tabs = document.querySelectorAll('.lp-tab');
1384
- tabs[0].classList.toggle('active', tab === 'elements');
1385
- tabs[1].classList.toggle('active', tab === 'components');
1386
- tabs[2].classList.toggle('active', tab === 'sections');
1387
- document.getElementById('tab-elements').classList.toggle('active', tab === 'elements');
1388
- document.getElementById('tab-components').classList.toggle('active', tab === 'components');
1389
- document.getElementById('tab-sections').classList.toggle('active', tab === 'sections');
1514
+ var tabs = document.querySelectorAll('.lp-tabs .lp-tab');
1515
+ for (var i = 0; i < tabs.length; i++) {
1516
+ var oc = tabs[i].getAttribute('onclick') || '';
1517
+ tabs[i].classList.toggle('active', oc.indexOf("switchLeftTab('" + tab + "')") >= 0);
1518
+ }
1519
+ var paneNames = ['elements', 'dom-tree'];
1520
+ for (var p = 0; p < paneNames.length; p++) {
1521
+ var pane = document.getElementById('tab-' + paneNames[p]);
1522
+ if (pane) pane.classList.toggle('active', paneNames[p] === tab);
1523
+ }
1390
1524
  var inp = document.getElementById('comp-search');
1391
1525
  if (tab === 'elements') {
1526
+ inp.placeholder = 'Search elements\u2026';
1527
+ renderElementsTree(inp.value);
1528
+ } else if (tab === 'dom-tree') {
1392
1529
  inp.placeholder = 'Search layers\u2026';
1393
1530
  renderDomTree(inp.value);
1394
- } else if (tab === 'sections') {
1395
- inp.placeholder = 'Search sections\u2026';
1396
- renderSidebar(inp.value);
1397
- } else {
1398
- inp.placeholder = 'Search components\u2026';
1399
- renderSidebar(inp.value);
1400
1531
  }
1401
1532
  }
1402
1533
 
1534
+ function switchSectionComponentsTab(tab) {
1535
+ if (tab !== 'components' && tab !== 'sections') return;
1536
+ currentSectionComponentsTab = tab;
1537
+ var tabs = document.querySelectorAll('.section-components-tabs .lp-tab');
1538
+ for (var i = 0; i < tabs.length; i++) {
1539
+ var oc = tabs[i].getAttribute('onclick') || '';
1540
+ tabs[i].classList.toggle('active', oc.indexOf("switchSectionComponentsTab('" + tab + "')") >= 0);
1541
+ }
1542
+ var compPane = document.getElementById('tab-components');
1543
+ var secPane = document.getElementById('tab-sections');
1544
+ if (compPane) compPane.classList.toggle('active', tab === 'components');
1545
+ if (secPane) secPane.classList.toggle('active', tab === 'sections');
1546
+ var inp = document.getElementById('comp-search');
1547
+ if (inp) inp.placeholder = tab === 'sections' ? 'Search sections\u2026' : 'Search components\u2026';
1548
+ renderSidebar(inp ? inp.value : '');
1549
+ }
1550
+
1403
1551
  // \u2500\u2500 Accordion toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1404
1552
  function toggleAcc(name) {
1405
1553
  var sec = document.getElementById('acc-' + name);
@@ -1522,42 +1670,20 @@ function logChange(selector, inputId, value, targetEl, originalValue) {
1522
1670
  : (originalValue != null ? originalValue : '');
1523
1671
  var entry = {
1524
1672
  selector: selector, inputId: inputId, label: meta.label,
1525
- cssProp: meta.cssProp, value: value, targetEl: targetEl, originalValue: orig
1673
+ cssProp: meta.cssProp, value: value, targetEl: targetEl, originalValue: orig, vveTs: nextHistoryTimestamp()
1526
1674
  };
1527
1675
  if (idx >= 0) { stateChanges[idx] = entry; } else { stateChanges.push(entry); }
1528
1676
  }
1529
1677
  if (currentMainTab === 'states') renderStatesTab();
1678
+ if (currentMainTab === 'history') renderHistoryTab();
1530
1679
  commitStateChangesForActiveVariation();
1531
1680
  recomputeEditorDirty();
1532
1681
  }
1533
1682
 
1534
1683
  function renderStatesTab() {
1535
1684
  var container = document.getElementById('states-list');
1536
- if (!stateChanges.length) {
1537
- container.innerHTML = '<div class="states-empty"><i class="bi bi-layers"></i>No changes yet \u2014 edit elements on the page to see states here</div>';
1538
- return;
1539
- }
1540
- // Group by selector
1541
- var groups = {};
1542
- var order = [];
1543
- stateChanges.forEach(function(c) {
1544
- if (!groups[c.selector]) { groups[c.selector] = []; order.push(c.selector); }
1545
- groups[c.selector].push(c);
1546
- });
1547
- var html = '<button id="states-clear" onclick="clearAllStates()"><i class="bi bi-trash3"></i> Clear all changes</button>';
1548
- order.forEach(function(sel) {
1549
- html += '<div class="state-group"><div class="state-group-sel">'+esc(sel)+'</div>';
1550
- groups[sel].forEach(function(c) {
1551
- var idx = stateChanges.indexOf(c);
1552
- html += '<div class="state-item">' +
1553
- '<span class="state-item-label">'+esc(c.label)+'</span>' +
1554
- '<span class="state-item-val" title="'+esc(c.value)+'">'+esc(c.value)+'</span>' +
1555
- '<button class="state-remove" title="Remove this change" onclick="removeStateChange('+idx+')">&#x2715;</button>' +
1556
- '</div>';
1557
- });
1558
- html += '</div>';
1559
- });
1560
- container.innerHTML = html;
1685
+ if (!container) return;
1686
+ container.innerHTML = '';
1561
1687
  }
1562
1688
 
1563
1689
  // Resolve a live DOM element for a state-change entry.
@@ -1636,7 +1762,9 @@ function removeStateChange(idx) {
1636
1762
  stateChanges.splice(idx, 1);
1637
1763
  commitStateChangesForActiveVariation();
1638
1764
  renderStatesTab();
1765
+ if (currentMainTab === 'history') renderHistoryTab();
1639
1766
  recomputeEditorDirty();
1767
+ scheduleDomTreeRefresh();
1640
1768
  }
1641
1769
 
1642
1770
  function clearAllStates() {
@@ -1647,7 +1775,9 @@ function clearAllStates() {
1647
1775
  stateChanges = [];
1648
1776
  commitStateChangesForActiveVariation();
1649
1777
  renderStatesTab();
1778
+ if (currentMainTab === 'history') renderHistoryTab();
1650
1779
  recomputeEditorDirty();
1780
+ scheduleDomTreeRefresh();
1651
1781
  }
1652
1782
 
1653
1783
  // \u2500\u2500 History tab (saved changesets from DB for active variation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -1820,61 +1950,169 @@ function historyEntryValuePreview(entry) {
1820
1950
  return '';
1821
1951
  }
1822
1952
 
1953
+ function getHistoryTimestampValue(raw, fallback) {
1954
+ var n = Number(raw);
1955
+ if (Number.isFinite(n) && n > 0) return n;
1956
+ return fallback;
1957
+ }
1958
+
1959
+ function historyTimestampForChangeset(entry, idx) {
1960
+ var base = idx + 1;
1961
+ if (!entry) return base;
1962
+ return getHistoryTimestampValue(
1963
+ entry.vveTs != null ? entry.vveTs : (entry.timestamp != null ? entry.timestamp : entry.ts),
1964
+ base,
1965
+ );
1966
+ }
1967
+
1968
+ function historyTimestampForStateChange(change, idx) {
1969
+ return getHistoryTimestampValue(change && change.vveTs, idx + 1);
1970
+ }
1971
+
1972
+ function formatHistoryTimestamp(ts) {
1973
+ if (!Number.isFinite(ts) || ts <= 0) return '';
1974
+ var ms = ts > 9999999999999 ? Math.floor(ts / 1000) : ts;
1975
+ var d = new Date(ms);
1976
+ if (isNaN(d.getTime())) return '';
1977
+ return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
1978
+ }
1979
+
1980
+ function formatHistoryRelativeTime(ts) {
1981
+ if (!Number.isFinite(ts) || ts <= 0) return '';
1982
+ var ms = ts > 9999999999999 ? Math.floor(ts / 1000) : ts;
1983
+ var diff = Math.max(0, Date.now() - ms);
1984
+ var sec = Math.floor(diff / 1000);
1985
+ if (sec < 5) return 'just now';
1986
+ if (sec < 60) return sec + ' seconds ago';
1987
+ var min = Math.floor(sec / 60);
1988
+ if (min < 60) return min + ' minute' + (min === 1 ? '' : 's') + ' ago';
1989
+ var hr = Math.floor(min / 60);
1990
+ if (hr < 24) return hr + ' hour' + (hr === 1 ? '' : 's') + ' ago';
1991
+ var day = Math.floor(hr / 24);
1992
+ return day + ' day' + (day === 1 ? '' : 's') + ' ago';
1993
+ }
1994
+
1995
+ function getUnifiedHistoryItems() {
1996
+ var out = [];
1997
+ var v = getActiveVariationForHistory();
1998
+ var saved = v ? parseVariationChangesets(v) : [];
1999
+ for (var i = 0; i < saved.length; i++) {
2000
+ var e = saved[i];
2001
+ out.push({
2002
+ source: 'saved',
2003
+ idx: i,
2004
+ selector: (e && e.selector) || '(unknown)',
2005
+ label: historyEntryTypeLabel(e),
2006
+ value: historyEntryValuePreview(e),
2007
+ ts: historyTimestampForChangeset(e, i),
2008
+ tsLabel: formatHistoryTimestamp(historyTimestampForChangeset(e, i)),
2009
+ actor: 'Saved changeset',
2010
+ });
2011
+ }
2012
+ var live = stateChanges || [];
2013
+ for (var j = 0; j < live.length; j++) {
2014
+ var c = live[j];
2015
+ if (!c) continue;
2016
+ var ts = historyTimestampForStateChange(c, j + saved.length);
2017
+ out.push({
2018
+ source: 'live',
2019
+ idx: j,
2020
+ selector: c.selector || '(unknown)',
2021
+ label: c.label || 'Live change',
2022
+ value: c.value != null ? String(c.value).slice(0, 120) : '',
2023
+ ts: ts,
2024
+ tsLabel: formatHistoryTimestamp(ts),
2025
+ actor: 'You',
2026
+ });
2027
+ }
2028
+ out.sort(function(a, b) {
2029
+ if (b.ts !== a.ts) return b.ts - a.ts;
2030
+ if (a.source !== b.source) return a.source === 'live' ? -1 : 1;
2031
+ return b.idx - a.idx;
2032
+ });
2033
+ return out;
2034
+ }
2035
+
1823
2036
  function renderHistoryTab() {
1824
2037
  var container = document.getElementById('history-list');
1825
2038
  if (!container) return;
1826
- var v = getActiveVariationForHistory();
1827
- var arr = v ? parseVariationChangesets(v) : [];
1828
- if (!arr.length) {
2039
+ var items = getUnifiedHistoryItems();
2040
+ if (!items.length) {
1829
2041
  container.innerHTML =
1830
- '<div class="states-empty"><i class="bi bi-clock-history"></i>No saved changesets for this variation \u2014 use Finalize to persist edits to the server</div>';
2042
+ '<div class="states-empty"><i class="bi bi-clock-history"></i>No changes yet</div>';
1831
2043
  return;
1832
2044
  }
1833
- var groups = {};
1834
- var order = [];
1835
- for (var gi = 0; gi < arr.length; gi++) {
1836
- var entry = arr[gi];
1837
- var sel = entry.selector || '(unknown)';
1838
- if (!groups[sel]) {
1839
- groups[sel] = [];
1840
- order.push(sel);
1841
- }
1842
- groups[sel].push({ entry: entry, idx: gi });
1843
- }
1844
2045
  var html =
1845
- '<button type="button" id="history-clear" onclick="clearAllHistoryChangesets()"><i class="bi bi-trash3"></i> Clear all saved changes</button>';
1846
- order.forEach(function(sel) {
1847
- html += '<div class="state-group"><div class="state-group-sel">' + esc(sel) + '</div>';
1848
- groups[sel].forEach(function(item) {
1849
- var lab = historyEntryTypeLabel(item.entry);
1850
- var val = historyEntryValuePreview(item.entry);
1851
- html +=
1852
- '<div class="state-item" role="button" tabindex="0" title="Jump to element in iframe" onclick="focusHistoryChangeset(' +
1853
- item.idx +
1854
- ')">' +
1855
- '<span class="state-item-idx" style="opacity:0.55;font-size:10px;min-width:2.2em;display:inline-block" title="Order in saved changesets">#' +
1856
- item.idx +
1857
- '</span>' +
1858
- '<span class="state-item-label">' +
1859
- esc(lab) +
1860
- '</span>' +
1861
- '<span class="state-item-val" title="' +
1862
- esc(val) +
1863
- '">' +
1864
- esc(val) +
1865
- '</span>' +
1866
- '<button type="button" class="state-remove" title="Remove this saved row (#' +
1867
- item.idx +
1868
- ')" onclick="removeHistoryChangeset(' +
1869
- item.idx +
1870
- ', event)">&#x2715;</button>' +
1871
- '</div>';
1872
- });
1873
- html += '</div>';
1874
- });
2046
+ '<button type="button" id="history-clear" onclick="clearAllUnifiedHistory()"><i class="bi bi-trash3"></i> Clear all changes</button>';
2047
+ html += '<div class="history-timeline">';
2048
+ for (var i = 0; i < items.length; i++) {
2049
+ var it = items[i];
2050
+ var title = 'Edit - ' + (it.label || 'Change');
2051
+ var avatarLabel = it.source === 'live' ? 'Y' : 'S';
2052
+ var timeText = formatHistoryRelativeTime(it.ts) || (it.tsLabel || '');
2053
+ html +=
2054
+ '<div class="history-item" role="button" tabindex="0" title="Jump to element in iframe" onclick="focusHistoryItem(&quot;' +
2055
+ esc(it.source) +
2056
+ '&quot;,' +
2057
+ it.idx +
2058
+ ')">' +
2059
+ '<span class="history-dot"></span>' +
2060
+ '<div class="history-card">' +
2061
+ '<div class="history-title">' + esc(title) + '</div>' +
2062
+ '<div class="history-meta">' +
2063
+ '<span class="history-avatar">' + esc(avatarLabel) + '</span>' +
2064
+ '<span>' + esc(it.actor || 'Editor') + '</span>' +
2065
+ '</div>' +
2066
+ '<div class="history-time">' + esc(timeText || 'n/a') + '</div>' +
2067
+ '</div>' +
2068
+ '<button type="button" class="state-remove history-remove" title="Remove this change" onclick="removeHistoryItem(&quot;' +
2069
+ esc(it.source) +
2070
+ '&quot;,' +
2071
+ it.idx +
2072
+ ', event)">&#x2715;</button>' +
2073
+ '</div>';
2074
+ }
2075
+ html += '</div>';
1875
2076
  container.innerHTML = html;
1876
2077
  }
1877
2078
 
2079
+ function focusHistoryItem(source, idx) {
2080
+ if (source === 'live') {
2081
+ var change = stateChanges[idx];
2082
+ if (!change || !change.selector) return;
2083
+ try {
2084
+ var iframe = document.getElementById('iframeId');
2085
+ var iframeDoc = iframe && iframe.contentDocument;
2086
+ if (!iframeDoc) return;
2087
+ var el = querySelectorResolved(iframeDoc, change.selector);
2088
+ if (!el) return;
2089
+ selectElement(el);
2090
+ try {
2091
+ el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
2092
+ } catch(_) {
2093
+ el.scrollIntoView();
2094
+ }
2095
+ } catch(_) {}
2096
+ return;
2097
+ }
2098
+ focusHistoryChangeset(idx);
2099
+ }
2100
+
2101
+ function removeHistoryItem(source, idx, evt) {
2102
+ if (source === 'live') {
2103
+ if (evt && evt.stopPropagation) evt.stopPropagation();
2104
+ removeStateChange(idx);
2105
+ if (currentMainTab === 'history') renderHistoryTab();
2106
+ return;
2107
+ }
2108
+ removeHistoryChangeset(idx, evt);
2109
+ }
2110
+
2111
+ function getLatestHistoryUndoTarget() {
2112
+ var list = getUnifiedHistoryItems();
2113
+ return list.length ? list[0] : null;
2114
+ }
2115
+
1878
2116
  function changesetListHasStructural(arr) {
1879
2117
  if (!arr || !arr.length) return false;
1880
2118
  for (var i = 0; i < arr.length; i++) {
@@ -1986,6 +2224,12 @@ function clearAllHistoryChangesets() {
1986
2224
  softReloadEditorIframe();
1987
2225
  }
1988
2226
 
2227
+ function clearAllUnifiedHistory() {
2228
+ clearAllStates();
2229
+ clearAllHistoryChangesets();
2230
+ if (currentMainTab === 'history') renderHistoryTab();
2231
+ }
2232
+
1989
2233
  // \u2500\u2500 Persisted active variation (survives iframe / full page reload) \u2500\u2500\u2500\u2500\u2500\u2500\u2500
1990
2234
  /** All Visual Editor iframe keys in localStorage use this prefix \u2014 cleared on close. */
1991
2235
  var VVE_LOCAL_STORAGE_PREFIX = 'vve:';
@@ -2251,13 +2495,7 @@ function granularAnySelectorMatches(doc, cs) {
2251
2495
 
2252
2496
  /** Bust bfcache / same-URL no-op reloads so the iframe actually re-parses (loading \u2192 interactive). */
2253
2497
  function appendIframeReloadBust(url) {
2254
- if (!url || url === 'about:blank') return url;
2255
- var stamp = Date.now();
2256
- if (url.indexOf('__ve_reload=') !== -1) {
2257
- return url.replace(/([&?])__ve_reload=[0-9]+/, '$1__ve_reload=' + stamp);
2258
- }
2259
- var sep = url.indexOf('?') !== -1 ? '&' : '?';
2260
- return url + sep + '__ve_reload=' + stamp;
2498
+ return url;
2261
2499
  }
2262
2500
 
2263
2501
  // True when the iframe contentDocument belongs to the current iframe.src navigation.
@@ -2277,13 +2515,7 @@ function iframeDocMatchesNavigatedSrc(iframe, doc) {
2277
2515
  try {
2278
2516
  var base = window.location.href;
2279
2517
  var su = new URL(src, base);
2280
- if (su.searchParams && su.searchParams.has('__ve_reload')) {
2281
- su.searchParams.delete('__ve_reload');
2282
- }
2283
2518
  var du = new URL(loc, base);
2284
- if (du.searchParams && du.searchParams.has('__ve_reload')) {
2285
- du.searchParams.delete('__ve_reload');
2286
- }
2287
2519
  // Same-origin proxy that keeps document address aligned with iframe src
2288
2520
  if (su.origin === du.origin && su.pathname + su.search === du.pathname + du.search) {
2289
2521
  return true;
@@ -2339,7 +2571,7 @@ function runConsistencyReconcile() {
2339
2571
  var doc = iframe && iframe.contentDocument;
2340
2572
  if (!doc || !doc.body) return;
2341
2573
  var variation = variations.find(function(v) { return v._id === activeVarId; });
2342
- var cs = parseVariationChangesets(variation);
2574
+ var cs = buildPersistedChainSetsForVariation(variation);
2343
2575
  if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
2344
2576
  var granular = filterGranularChangesetEntries(cs);
2345
2577
  var unresolved = countUnresolvedGranularSelectors(doc, granular);
@@ -2413,6 +2645,43 @@ function isIframeDomReady(iframe, doc) {
2413
2645
  return true;
2414
2646
  }
2415
2647
 
2648
+ function parseEditorUrlPayload(rawUrl) {
2649
+ if (!rawUrl) return null;
2650
+ try {
2651
+ var parsed = new URL(String(rawUrl), window.location.href);
2652
+ var nestedUrl = parsed.searchParams.get('url');
2653
+ var nestedPassword = parsed.searchParams.get('password');
2654
+ if (nestedUrl || nestedPassword) {
2655
+ return {
2656
+ url: nestedUrl || undefined,
2657
+ password: nestedPassword || undefined,
2658
+ };
2659
+ }
2660
+ return {
2661
+ url: parsed.toString(),
2662
+ password:
2663
+ (experimentData && experimentData.editorPassword)
2664
+ ? String(experimentData.editorPassword)
2665
+ : undefined,
2666
+ };
2667
+ } catch(_) {
2668
+ return null;
2669
+ }
2670
+ }
2671
+
2672
+ var lastEditorUrlPayloadKey = '';
2673
+ function emitEditorUrlChangedPayload(payload) {
2674
+ if (!payload) return;
2675
+ var key = String(payload.url || '') + '|' + String(payload.password || '');
2676
+ if (key === lastEditorUrlPayloadKey) return;
2677
+ lastEditorUrlPayloadKey = key;
2678
+ send('editor-url-changed', payload);
2679
+ }
2680
+ function emitEditorUrlChanged(rawUrl) {
2681
+ var payload = parseEditorUrlPayload(rawUrl);
2682
+ emitEditorUrlChangedPayload(payload);
2683
+ }
2684
+
2416
2685
  function loadPage(proxyUrl) {
2417
2686
  showNoUrl(false);
2418
2687
  lastLoadedProxyUrl = proxyUrl;
@@ -2422,6 +2691,7 @@ function loadPage(proxyUrl) {
2422
2691
  iframe.style.display = 'block';
2423
2692
  setIframePageLoadingUi(true);
2424
2693
  iframe.src = appendIframeReloadBust(proxyUrl);
2694
+ emitEditorUrlChanged(proxyUrl);
2425
2695
  startIframeContentApplyWatcher(navGen, iframe.contentDocument || null);
2426
2696
  scheduleDomTreeRefresh();
2427
2697
  }
@@ -2599,6 +2869,7 @@ function mergeGranularChainSets(baseList, overlayList) {
2599
2869
  function appendSessionStructuralChainRow(varId, row) {
2600
2870
  if (!varId || !row) return;
2601
2871
  if (!sessionStructuralChainRowsByVarId[varId]) sessionStructuralChainRowsByVarId[varId] = [];
2872
+ if (row.vveTs == null) row.vveTs = nextHistoryTimestamp();
2602
2873
  sessionStructuralChainRowsByVarId[varId].push(row);
2603
2874
  }
2604
2875
 
@@ -2606,33 +2877,33 @@ function appendSessionStructuralChainRow(varId, row) {
2606
2877
  function stateChangeToChainSet(c) {
2607
2878
  if (!c || !c.selector) return null;
2608
2879
  if (c.cssProp) {
2609
- return { selector: c.selector, type: 'style', property: c.cssProp, value: c.value };
2880
+ return { selector: c.selector, type: 'style', property: c.cssProp, value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2610
2881
  }
2611
2882
  switch (c.inputId) {
2612
2883
  case 'pp-text':
2613
- return { selector: c.selector, type: 'content', value: c.value };
2884
+ return { selector: c.selector, type: 'content', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2614
2885
  case 'pp-html':
2615
- return { selector: c.selector, type: 'content', html: c.value };
2886
+ return { selector: c.selector, type: 'content', html: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2616
2887
  case 'pp-cls':
2617
- return { selector: c.selector, type: 'attribute', attribute: 'class', value: c.value };
2888
+ return { selector: c.selector, type: 'attribute', attribute: 'class', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2618
2889
  case 'pp-id':
2619
- return { selector: c.selector, type: 'attribute', attribute: 'id', value: c.value };
2890
+ return { selector: c.selector, type: 'attribute', attribute: 'id', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2620
2891
  case 'pp-href':
2621
- return { selector: c.selector, type: 'attribute', attribute: 'href', value: c.value };
2892
+ return { selector: c.selector, type: 'attribute', attribute: 'href', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2622
2893
  case 'pp-target':
2623
- return { selector: c.selector, type: 'attribute', attribute: 'target', value: c.value };
2894
+ return { selector: c.selector, type: 'attribute', attribute: 'target', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2624
2895
  case 'pp-src':
2625
- return { selector: c.selector, type: 'attribute', attribute: 'src', value: c.value };
2896
+ return { selector: c.selector, type: 'attribute', attribute: 'src', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2626
2897
  case 'pp-alt':
2627
- return { selector: c.selector, type: 'attribute', attribute: 'alt', value: c.value };
2898
+ return { selector: c.selector, type: 'attribute', attribute: 'alt', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2628
2899
  case 'pp-ph':
2629
- return { selector: c.selector, type: 'attribute', attribute: 'placeholder', value: c.value };
2900
+ return { selector: c.selector, type: 'attribute', attribute: 'placeholder', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2630
2901
  case 'pp-css':
2631
- return { selector: c.selector, type: 'attribute', attribute: 'style', value: c.value };
2902
+ return { selector: c.selector, type: 'attribute', attribute: 'style', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2632
2903
  case 'pp-mob-css':
2633
- return { selector: c.selector, type: 'attribute', attribute: 'data-mobile-css', value: c.value };
2904
+ return { selector: c.selector, type: 'attribute', attribute: 'data-mobile-css', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2634
2905
  case 'pp-tab-css':
2635
- return { selector: c.selector, type: 'attribute', attribute: 'data-tablet-css', value: c.value };
2906
+ return { selector: c.selector, type: 'attribute', attribute: 'data-tablet-css', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2636
2907
  default:
2637
2908
  return null;
2638
2909
  }
@@ -2920,7 +3191,7 @@ function applyActiveVariationHtml() {
2920
3191
  if (!iframeDoc || !iframeDoc.body) return;
2921
3192
 
2922
3193
  var variation = variations.find(function(v) { return v._id === activeVarId; });
2923
- var cs = parseVariationChangesets(variation);
3194
+ var cs = buildPersistedChainSetsForVariation(variation);
2924
3195
  refreshPersistentChangesetStyleTagForActiveVariation();
2925
3196
 
2926
3197
  beginSuppressIframeMutationDirty();
@@ -2978,7 +3249,7 @@ function applyVariationGranularOnly(iframeDoc) {
2978
3249
  if (!activeVarId || !iframeDoc || !iframeDoc.body) return;
2979
3250
  if (varHtmlCache[activeVarId]) return;
2980
3251
  var variation = variations.find(function(v) { return v._id === activeVarId; });
2981
- var cs = parseVariationChangesets(variation);
3252
+ var cs = buildPersistedChainSetsForVariation(variation);
2982
3253
  if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
2983
3254
  beginSuppressIframeMutationDirty();
2984
3255
  try {
@@ -2997,7 +3268,7 @@ function reapplyActiveVariationGranular(iframeDoc) {
2997
3268
  if (!activeVarId || !iframeDoc || !iframeDoc.body) return;
2998
3269
  if (varHtmlCache[activeVarId]) return;
2999
3270
  var variation = variations.find(function(v) { return v._id === activeVarId; });
3000
- var cs = parseVariationChangesets(variation);
3271
+ var cs = buildPersistedChainSetsForVariation(variation);
3001
3272
  if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
3002
3273
  beginSuppressIframeMutationDirty();
3003
3274
  try {
@@ -3041,7 +3312,7 @@ function startIframeContentApplyWatcher(navGen, prevDocRef) {
3041
3312
 
3042
3313
  if (doc.readyState === 'loading') {
3043
3314
  var variation = variations.find(function(v) { return v._id === activeVarId; });
3044
- var cs0 = parseVariationChangesets(variation);
3315
+ var cs0 = buildPersistedChainSetsForVariation(variation);
3045
3316
  if (!cs0.length || changesetsHaveBodySnapshot(cs0)) return;
3046
3317
  var granular = filterGranularChangesetEntries(cs0);
3047
3318
  if (!granular.length) return;
@@ -3087,8 +3358,9 @@ function selectElement(el) {
3087
3358
  document.getElementById('no-sel').style.display = 'none';
3088
3359
  renderRightPanel(el);
3089
3360
  updateSelectionToolbar();
3090
- if (currentLeftTab === 'elements') {
3091
- var dr = document.getElementById('dom-tree-root');
3361
+ if (currentLeftTab === 'elements' || currentLeftTab === 'dom-tree') {
3362
+ var treeRootId = currentLeftTab === 'elements' ? 'elements-root' : 'dom-tree-root';
3363
+ var dr = document.getElementById(treeRootId);
3092
3364
  if (dr && dr.querySelector('.dt-row')) syncDomTreeSelection();
3093
3365
  else scheduleDomTreeRefresh();
3094
3366
  }
@@ -3351,27 +3623,32 @@ function deleteSelectedEl() {
3351
3623
  }
3352
3624
 
3353
3625
  function syncDomTreeSelection() {
3354
- var root = document.getElementById('dom-tree-root');
3355
- if (!root) return;
3356
- var rows = root.querySelectorAll('.dt-row');
3357
- for (var i = 0; i < rows.length; i++) {
3358
- rows[i].classList.toggle('dt-selected', !!(selectedEl && rows[i]._dtEl === selectedEl));
3359
- }
3360
- if (!selectedEl) return;
3361
- var found = null;
3362
- for (var j = 0; j < rows.length; j++) {
3363
- if (rows[j]._dtEl === selectedEl) { found = rows[j]; break; }
3626
+ var roots = ['dom-tree-root', 'elements-root'];
3627
+ for (var r = 0; r < roots.length; r++) {
3628
+ var root = document.getElementById(roots[r]);
3629
+ if (!root) continue;
3630
+ var rows = root.querySelectorAll('.dt-row');
3631
+ for (var i = 0; i < rows.length; i++) {
3632
+ rows[i].classList.toggle('dt-selected', !!(selectedEl && rows[i]._dtEl === selectedEl));
3633
+ }
3634
+ if (!selectedEl) continue;
3635
+ var found = null;
3636
+ for (var j = 0; j < rows.length; j++) {
3637
+ if (rows[j]._dtEl === selectedEl) { found = rows[j]; break; }
3638
+ }
3639
+ if (found) found.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
3364
3640
  }
3365
- if (found) found.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
3366
3641
  }
3367
3642
 
3368
3643
  function scheduleDomTreeRefresh() {
3369
- if (currentLeftTab !== 'elements') return;
3644
+ if (currentLeftTab !== 'elements' && currentLeftTab !== 'dom-tree') return;
3370
3645
  if (domTreeRefreshTimer) clearTimeout(domTreeRefreshTimer);
3371
3646
  domTreeRefreshTimer = setTimeout(function() {
3372
3647
  domTreeRefreshTimer = null;
3373
3648
  var inp = document.getElementById('comp-search');
3374
- renderDomTree(inp ? inp.value : '');
3649
+ var q = inp ? inp.value : '';
3650
+ if (currentLeftTab === 'elements') renderElementsTree(q);
3651
+ else if (currentLeftTab === 'dom-tree') renderDomTree(q);
3375
3652
  }, 150);
3376
3653
  }
3377
3654
 
@@ -3394,6 +3671,129 @@ function domTreePathSegment(el) {
3394
3671
  return tag + '[' + idx + ']';
3395
3672
  }
3396
3673
 
3674
+ function renderElementsTree(filterRaw) {
3675
+ var filterText = (filterRaw || '').toLowerCase().trim();
3676
+ var root = document.getElementById('elements-root');
3677
+ if (!root) return;
3678
+ var iframe = document.getElementById('iframeId');
3679
+ var doc = iframe && iframe.contentDocument;
3680
+ if (!isIframeDomReady(iframe, doc)) {
3681
+ root.innerHTML = '<div class="dt-muted">Load a page to see the elements.</div>';
3682
+ return;
3683
+ }
3684
+
3685
+ function skippable(el) {
3686
+ return isDomTreeSkippableTagName(el.tagName);
3687
+ }
3688
+
3689
+ function nodeIcon(tag) {
3690
+ tag = (tag || '').toLowerCase();
3691
+ if (/^h[1-6]$/.test(tag)) return 'bi bi-type-h1';
3692
+ if (tag === 'a') return 'bi bi-link-45deg';
3693
+ if (tag === 'img') return 'bi bi-image';
3694
+ if (tag === 'section' || tag === 'main' || tag === 'article' || tag === 'header' || tag === 'footer' || tag === 'nav') return 'bi bi-layout-three-columns';
3695
+ if (tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea') return 'bi bi-ui-radios';
3696
+ if (tag === 'ul' || tag === 'ol') return 'bi bi-list-ul';
3697
+ if (tag === 'li') return 'bi bi-dot';
3698
+ if (tag === 'svg') return 'bi bi-bezier2';
3699
+ if (tag === 'p' || tag === 'span') return 'bi bi-text-left';
3700
+ return 'bi bi-square';
3701
+ }
3702
+
3703
+ function labelFor(el) {
3704
+ var tag = (el.tagName || '').toLowerCase();
3705
+ return tag.toUpperCase();
3706
+ }
3707
+
3708
+ function isListableNode(el) {
3709
+ if (!el || el.nodeType !== 1) return false;
3710
+ if (skippable(el)) return false;
3711
+ var tag = (el.tagName || '').toLowerCase();
3712
+ if (tag !== 'svg') {
3713
+ var p = el.parentElement;
3714
+ while (p) {
3715
+ if ((p.tagName || '').toLowerCase() === 'svg') return false;
3716
+ p = p.parentElement;
3717
+ }
3718
+ }
3719
+ return true;
3720
+ }
3721
+
3722
+ var nodes = [];
3723
+ var i, cursor;
3724
+ try {
3725
+ cursor = doc.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT, null);
3726
+ } catch(_) {
3727
+ cursor = null;
3728
+ }
3729
+ if (cursor) {
3730
+ while (cursor.nextNode()) {
3731
+ var node = cursor.currentNode;
3732
+ if (isListableNode(node)) nodes.push(node);
3733
+ if (nodes.length > 4000) break;
3734
+ }
3735
+ } else {
3736
+ function collectFlat(el) {
3737
+ if (!el || !el.children) return;
3738
+ for (var j = 0; j < el.children.length; j++) {
3739
+ var c = el.children[j];
3740
+ if (!isListableNode(c)) continue;
3741
+ nodes.push(c);
3742
+ if (nodes.length > 4000) return;
3743
+ collectFlat(c);
3744
+ if (nodes.length > 4000) return;
3745
+ }
3746
+ }
3747
+ collectFlat(doc.body);
3748
+ }
3749
+
3750
+ root.innerHTML = '';
3751
+ for (i = 0; i < nodes.length; i++) {
3752
+ var el = nodes[i];
3753
+ var lblText = labelFor(el);
3754
+ if (filterText && lblText.toLowerCase().indexOf(filterText) < 0) continue;
3755
+
3756
+ var row = document.createElement('div');
3757
+ row.className = 'dt-row';
3758
+ row._dtEl = el;
3759
+ if (el === selectedEl) row.classList.add('dt-selected');
3760
+ row.style.paddingLeft = '4px';
3761
+
3762
+ var spacer = document.createElement('button');
3763
+ spacer.type = 'button';
3764
+ spacer.className = 'dt-chev dt-spacer';
3765
+
3766
+ var ico = document.createElement('div');
3767
+ ico.className = 'dt-ico';
3768
+ ico.innerHTML = '<i class="' + nodeIcon(el.tagName) + '"></i>';
3769
+
3770
+ var lbl = document.createElement('div');
3771
+ lbl.className = 'dt-lbl';
3772
+ lbl.textContent = lblText;
3773
+ lbl.title = buildSelector(el);
3774
+
3775
+ row.appendChild(spacer);
3776
+ row.appendChild(ico);
3777
+ row.appendChild(lbl);
3778
+ row.onclick = (function(targetEl) {
3779
+ return function() { selectElementFromTree(targetEl); };
3780
+ })(el);
3781
+ row.onmouseenter = (function(targetEl) {
3782
+ return function() { setTreeHoverHighlight(targetEl); };
3783
+ })(el);
3784
+ root.appendChild(row);
3785
+ }
3786
+
3787
+ if (!root.querySelector('.dt-row')) {
3788
+ root.innerHTML = filterText
3789
+ ? '<div class="dt-muted">No elements match your search.</div>'
3790
+ : '<div class="dt-muted">No elements found.</div>';
3791
+ }
3792
+ root.onmouseleave = function() {
3793
+ clearTreeHoverHighlight();
3794
+ };
3795
+ }
3796
+
3397
3797
  function renderDomTree(filterRaw) {
3398
3798
  var filterText = (filterRaw || '').toLowerCase().trim();
3399
3799
  var root = document.getElementById('dom-tree-root');
@@ -4132,6 +4532,20 @@ function recordReorderAfterDrag(movedEl) {
4132
4532
  }
4133
4533
  }
4134
4534
 
4535
+ function moveSelectedElByDirection(direction) {
4536
+ if (!selectedEl || !selectedEl.parentElement) return;
4537
+ var p = selectedEl.parentElement;
4538
+ var sibling = direction < 0 ? selectedEl.previousElementSibling : selectedEl.nextElementSibling;
4539
+ if (!sibling) return;
4540
+ if (direction < 0) p.insertBefore(selectedEl, sibling);
4541
+ else p.insertBefore(sibling, selectedEl);
4542
+ recordReorderAfterDrag(selectedEl);
4543
+ saveCurrentVariationHtml();
4544
+ recomputeEditorDirty();
4545
+ scheduleDomTreeRefresh();
4546
+ updateSelectionToolbar();
4547
+ }
4548
+
4135
4549
  function attachDragReposition() {
4136
4550
  try {
4137
4551
  var iframe = document.getElementById('iframeId');
@@ -4318,7 +4732,9 @@ function syncIframeInteractions(reason) {
4318
4732
  scheduleConsistencyReconcile();
4319
4733
  bindSelectionToolbarScroll();
4320
4734
  var inp = document.getElementById('comp-search');
4321
- renderDomTree(inp ? inp.value : '');
4735
+ var q = inp ? inp.value : '';
4736
+ if (currentLeftTab === 'elements') renderElementsTree(q);
4737
+ else if (currentLeftTab === 'dom-tree') renderDomTree(q);
4322
4738
  updateSelectionToolbar();
4323
4739
  recomputeEditorDirty();
4324
4740
  } catch(_) {}
@@ -4482,8 +4898,11 @@ function renderSidebar(filter) {
4482
4898
  }
4483
4899
 
4484
4900
  document.getElementById('comp-search').addEventListener('input', function() {
4485
- if (currentLeftTab === 'elements') renderDomTree(this.value);
4486
- else renderSidebar(this.value);
4901
+ if (currentLeftTab === 'elements') renderElementsTree(this.value);
4902
+ else if (currentLeftTab === 'dom-tree') renderDomTree(this.value);
4903
+ if (currentSectionComponentsTab === 'components' || currentSectionComponentsTab === 'sections') {
4904
+ renderSidebar(this.value);
4905
+ }
4487
4906
  });
4488
4907
 
4489
4908
  // \u2500\u2500 Save / Close \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -4549,19 +4968,11 @@ document.addEventListener('keydown', function(e) {
4549
4968
  var k = (e.key || '').toLowerCase();
4550
4969
  if (meta && !e.shiftKey && k === 'z') {
4551
4970
  e.preventDefault();
4552
- if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
4553
- Vvveb.Undo.undo();
4554
- saveCurrentVariationHtml();
4555
- recomputeEditorDirty();
4556
- }
4971
+ runEditorUndo();
4557
4972
  }
4558
4973
  if (meta && e.shiftKey && k === 'z') {
4559
4974
  e.preventDefault();
4560
- if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
4561
- Vvveb.Undo.redo();
4562
- saveCurrentVariationHtml();
4563
- recomputeEditorDirty();
4564
- }
4975
+ runEditorRedo();
4565
4976
  }
4566
4977
  if (meta && e.key === 's') { e.preventDefault(); handleSave(); }
4567
4978
  if (e.key === 'Escape') {
@@ -4581,8 +4992,39 @@ document.addEventListener('keydown', function(e) {
4581
4992
  if (selectedEl) deselectElement();
4582
4993
  }
4583
4994
  });
4584
- document.getElementById('btn-undo').addEventListener('click', function() { if (typeof Vvveb !== 'undefined' && Vvveb.Undo) Vvveb.Undo.undo(); });
4585
- document.getElementById('btn-redo').addEventListener('click', function() { if (typeof Vvveb !== 'undefined' && Vvveb.Undo) Vvveb.Undo.redo(); });
4995
+ function runEditorUndo() {
4996
+ var target = getLatestHistoryUndoTarget();
4997
+ if (target) {
4998
+ removeHistoryItem(target.source, target.idx);
4999
+ return;
5000
+ }
5001
+ if (!(typeof Vvveb !== 'undefined' && Vvveb.Undo)) return;
5002
+ Vvveb.Undo.undo();
5003
+ saveCurrentVariationHtml();
5004
+ recomputeEditorDirty();
5005
+ scheduleDomTreeRefresh();
5006
+ updateSelectionToolbar();
5007
+ }
5008
+
5009
+ function runEditorRedo() {
5010
+ if (!(typeof Vvveb !== 'undefined' && Vvveb.Undo)) return;
5011
+ Vvveb.Undo.redo();
5012
+ saveCurrentVariationHtml();
5013
+ recomputeEditorDirty();
5014
+ scheduleDomTreeRefresh();
5015
+ updateSelectionToolbar();
5016
+ }
5017
+
5018
+ document.getElementById('btn-undo').addEventListener('click', function(e) {
5019
+ e.preventDefault();
5020
+ e.stopPropagation();
5021
+ runEditorUndo();
5022
+ });
5023
+ document.getElementById('btn-redo').addEventListener('click', function(e) {
5024
+ e.preventDefault();
5025
+ e.stopPropagation();
5026
+ runEditorRedo();
5027
+ });
4586
5028
 
4587
5029
  function layoutLoadingTooltip(host) {
4588
5030
  var tip = host.querySelector('.ve-pl-tooltip');
@@ -4658,8 +5100,8 @@ function registerCROSections() {
4658
5100
 
4659
5101
  window.addEventListener('load', function() {
4660
5102
  registerCROSections();
4661
- renderSidebar();
4662
- renderDomTree(document.getElementById('comp-search').value);
5103
+ switchSectionComponentsTab(currentSectionComponentsTab);
5104
+ renderElementsTree(document.getElementById('comp-search').value);
4663
5105
  vvvebReady = true;
4664
5106
  bindLoadingTooltipPositioning();
4665
5107
 
@@ -4668,6 +5110,18 @@ window.addEventListener('load', function() {
4668
5110
 
4669
5111
  // After each iframe load: apply variation, wire click+mutation handlers
4670
5112
  var iframe = document.getElementById('iframeId');
5113
+ window.addEventListener('message', function(ev) {
5114
+ try {
5115
+ var d = ev && ev.data;
5116
+ if (!d || d.channel !== 'vvveb-proxy-url' || d.type !== 'editor-url-changed') return;
5117
+ if (!iframe || !iframe.contentWindow || ev.source !== iframe.contentWindow) return;
5118
+ var payload = d.payload || {};
5119
+ emitEditorUrlChangedPayload({
5120
+ url: payload.url || undefined,
5121
+ password: payload.password || undefined,
5122
+ });
5123
+ } catch(_) {}
5124
+ });
4671
5125
  iframe.addEventListener('load', function() {
4672
5126
  if (!iframe.src || iframe.src === 'about:blank' || iframe.src === window.location.href) return;
4673
5127
  var doc = iframe.contentDocument;
@@ -4678,6 +5132,7 @@ window.addEventListener('load', function() {
4678
5132
  }
4679
5133
  var docUrl = '';
4680
5134
  try { docUrl = String(doc.URL || ''); } catch(_) {}
5135
+ emitEditorUrlChanged(iframe.src || docUrl);
4681
5136
  // Stale events: src may already be the proxy URL while the document is still
4682
5137
  // about:blank (e.g. src cleared then reset to force reload). Ask sync path to retry.
4683
5138
  if (docUrl === 'about:blank') {
@@ -4712,12 +5167,22 @@ window.addEventListener('load', function() {
4712
5167
  syncIframeInteractions('iframe-load');
4713
5168
  });
4714
5169
 
4715
- document.getElementById('sf-drag').addEventListener('click', function(e) {
4716
- e.preventDefault();
4717
- e.stopPropagation();
4718
- if (!selectedEl) return;
4719
- setDragHandleActive(!dragHandleActive);
4720
- });
5170
+ var sfMoveUp = document.getElementById('sf-move-up');
5171
+ if (sfMoveUp) {
5172
+ sfMoveUp.addEventListener('click', function(e) {
5173
+ e.preventDefault();
5174
+ e.stopPropagation();
5175
+ moveSelectedElByDirection(-1);
5176
+ });
5177
+ }
5178
+ var sfMoveDown = document.getElementById('sf-move-down');
5179
+ if (sfMoveDown) {
5180
+ sfMoveDown.addEventListener('click', function(e) {
5181
+ e.preventDefault();
5182
+ e.stopPropagation();
5183
+ moveSelectedElByDirection(1);
5184
+ });
5185
+ }
4721
5186
  document.getElementById('sf-dup').addEventListener('click', function(e) {
4722
5187
  e.preventDefault();
4723
5188
  e.stopPropagation();
@@ -4932,6 +5397,8 @@ function createVisualEditorMiddleware(options) {
4932
5397
  const targetUrl = url.searchParams.get("url");
4933
5398
  const password = url.searchParams.get("password") || "";
4934
5399
  const strictFreezeParam = (url.searchParams.get("strictObserverFreeze") || "").toLowerCase();
5400
+ const proxyParam = (url.searchParams.get("proxy") || "").toLowerCase();
5401
+ const useScraperProxy = proxyParam === "1" || proxyParam === "true" || proxyParam === "yes";
4935
5402
  const strictObserverFreezeForRequest = strictFreezeParam === "1" || strictFreezeParam === "true" || strictFreezeParam === "yes" ? true : strictFreezeParam === "0" || strictFreezeParam === "false" || strictFreezeParam === "no" ? false : strictObserverFreeze;
4936
5403
  if (!targetUrl) {
4937
5404
  res.statusCode = 400;
@@ -4941,13 +5408,37 @@ function createVisualEditorMiddleware(options) {
4941
5408
  const parsed = new URL(targetUrl);
4942
5409
  const origin = parsed.origin;
4943
5410
  const method = (req.method || "GET").toUpperCase();
5411
+ const scraperProxyClient = useScraperProxy ? await getScraperProxyClient() : null;
5412
+ if (useScraperProxy && !scraperProxyClient) {
5413
+ res.statusCode = 500;
5414
+ res.setHeader("Content-Type", "application/json");
5415
+ res.end(
5416
+ JSON.stringify({
5417
+ error: "ScraperAPI proxy is not configured. Set SCRAPERAPI_PROXY_PASSWORD or SCRAPERAPI_API_KEY."
5418
+ })
5419
+ );
5420
+ return;
5421
+ }
5422
+ if (!SCRAPER_PROXY_PASSWORD) ;
5423
+ const upstreamFetch = (input, init = {}) => {
5424
+ if (!useScraperProxy || !scraperProxyClient) {
5425
+ const scraperUrl = new URL(SCRAPER_API_ENDPOINT);
5426
+ scraperUrl.searchParams.set("api_key", SCRAPER_PROXY_PASSWORD);
5427
+ scraperUrl.searchParams.set("url", input);
5428
+ return fetch(scraperUrl.toString(), init);
5429
+ }
5430
+ return scraperProxyClient.fetchFn(input, {
5431
+ ...init,
5432
+ dispatcher: scraperProxyClient.dispatcher
5433
+ });
5434
+ };
4944
5435
  const headers = {
4945
5436
  "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
4946
5437
  Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
4947
5438
  };
4948
5439
  let cookieHeader = "";
4949
5440
  if (password) {
4950
- const passResp = await fetch(`${origin}/password`, {
5441
+ const passResp = await upstreamFetch(`${origin}/password`, {
4951
5442
  method: "POST",
4952
5443
  headers: {
4953
5444
  "Content-Type": "application/x-www-form-urlencoded",
@@ -4994,7 +5485,7 @@ function createVisualEditorMiddleware(options) {
4994
5485
  const timeoutId = setTimeout(() => ac.abort(), upstreamTimeoutMs);
4995
5486
  let upstream;
4996
5487
  try {
4997
- upstream = await fetch(targetUrl, {
5488
+ upstream = await upstreamFetch(targetUrl, {
4998
5489
  method,
4999
5490
  headers: fetchHeaders,
5000
5491
  body: requestBody ? Buffer.from(requestBody) : null,
@@ -5004,11 +5495,27 @@ function createVisualEditorMiddleware(options) {
5004
5495
  } catch (fetchErr) {
5005
5496
  clearTimeout(timeoutId);
5006
5497
  const aborted = fetchErr?.name === "AbortError";
5498
+ const fetchCause = fetchErr?.cause;
5007
5499
  res.statusCode = aborted ? 504 : 502;
5008
5500
  res.setHeader("Content-Type", "application/json");
5009
5501
  res.end(
5010
5502
  JSON.stringify({
5011
- error: aborted ? `Upstream request timed out after ${upstreamTimeoutMs / 1e3}s` : fetchErr?.message || "Upstream fetch failed"
5503
+ error: aborted ? `Upstream request timed out after ${upstreamTimeoutMs / 1e3}s` : fetchErr?.message || "Upstream fetch failed",
5504
+ phase: useScraperProxy ? "scraperapi-proxy-fetch" : "scraperapi-simple-fetch",
5505
+ fetchMode: useScraperProxy ? "proxy" : "simple",
5506
+ scraperapi: {
5507
+ host: SCRAPER_PROXY_HOST,
5508
+ port: SCRAPER_PROXY_PORT,
5509
+ username: SCRAPER_PROXY_USERNAME,
5510
+ endpoint: SCRAPER_API_ENDPOINT,
5511
+ hasProxyPassword: Boolean(SCRAPER_PROXY_PASSWORD),
5512
+ requestTlsRejectUnauthorized: SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED
5513
+ },
5514
+ cause: fetchCause && typeof fetchCause === "object" ? {
5515
+ name: fetchCause.name,
5516
+ code: fetchCause.code,
5517
+ message: fetchCause.message
5518
+ } : fetchCause ? String(fetchCause) : null
5012
5519
  })
5013
5520
  );
5014
5521
  return;
@@ -5039,7 +5546,7 @@ function createVisualEditorMiddleware(options) {
5039
5546
  }
5040
5547
  html = html.replace(/(href|src|action)="\/(?!\/)/g, `$1="${origin}/`);
5041
5548
  const escapedOrigin = origin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5042
- const proxyBase = `/api/conversion-proxy?__ve_reload=${Date.now()}&password=${encodeURIComponent(password)}&url=`;
5549
+ const proxyBase = `/api/conversion-proxy?password=${encodeURIComponent(password)}&url=`;
5043
5550
  html = html.replace(
5044
5551
  new RegExp(`(href|action)="${escapedOrigin}(/[^"]*)"`, "g"),
5045
5552
  (match, attr, urlPath) => {
@@ -5050,6 +5557,12 @@ function createVisualEditorMiddleware(options) {
5050
5557
  return `${attr}="${proxyBase}${encodeURIComponent(origin + urlPath)}"`;
5051
5558
  }
5052
5559
  );
5560
+ html = html.replace(
5561
+ /data-link="\/(?!\/)([^"]*)"/g,
5562
+ (_match, pathPart) => `data-link="/api/conversion-proxy?password=${encodeURIComponent(password)}&url=${encodeURIComponent(
5563
+ `${origin}/${pathPart}`
5564
+ )}"`
5565
+ );
5053
5566
  if (html.includes("</head>")) {
5054
5567
  html = html.replace(
5055
5568
  "</head>",
@@ -5071,10 +5584,18 @@ var TARGET_ORIGIN=${JSON.stringify(origin)};
5071
5584
  var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
5072
5585
  var PROXY_PASSWORD=${JSON.stringify(password)};
5073
5586
  var STRICT_OBSERVER_FREEZE=${JSON.stringify(strictObserverFreezeForRequest)};
5587
+ var PARENT_URL_CHANNEL="vvveb-proxy-url";
5074
5588
  window.__CONVERSION_EDITOR_ACTIVE__=true;
5589
+ function getEditorUrlPayload(){try{var u=new URL(window.location.href);var nested=u.searchParams.get("url");return{url:nested||u.toString(),password:u.searchParams.get("password")||undefined};}catch(_){return null;}}
5590
+ function notifyEditorUrlChanged(){try{var payload=getEditorUrlPayload();if(!payload)return;if(window.parent){window.parent.postMessage({channel:PARENT_URL_CHANNEL,type:"editor-url-changed",payload:payload},"*");}}catch(_){}}
5591
+ try{notifyEditorUrlChanged();}catch(_){}
5592
+ try{if(window.history&&typeof window.history.pushState==="function"){var nativePushState=window.history.pushState;window.history.pushState=function(){var ret=nativePushState.apply(window.history,arguments);setTimeout(notifyEditorUrlChanged,0);return ret;};}}catch(_){}
5593
+ try{if(window.history&&typeof window.history.replaceState==="function"){var nativeReplaceState=window.history.replaceState;window.history.replaceState=function(){var ret=nativeReplaceState.apply(window.history,arguments);setTimeout(notifyEditorUrlChanged,0);return ret;};}}catch(_){}
5594
+ try{window.addEventListener("popstate",notifyEditorUrlChanged,true);}catch(_){}
5595
+ try{window.addEventListener("hashchange",notifyEditorUrlChanged,true);}catch(_){}
5075
5596
  function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
5076
5597
  function toAbsolute(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")||raw.startsWith("//")?TARGET_ORIGIN:TARGET_PAGE_URL;return new URL(raw,base).toString();}catch(_){return raw;}}
5077
- function toProxy(raw){if(isSkippable(raw))return null;var abs=toAbsolute(raw);if(!abs||typeof abs!=="string")return null;try{var parsed=new URL(abs);if(parsed.origin!==TARGET_ORIGIN)return null;return "/api/conversion-proxy?__ve_reload="+Date.now()+"&password="+encodeURIComponent(PROXY_PASSWORD||"")+"&url="+encodeURIComponent(parsed.toString());}catch(_){return null;}}
5598
+ function toProxy(raw){if(isSkippable(raw))return null;var abs=toAbsolute(raw);if(!abs||typeof abs!=="string")return null;try{var parsed=new URL(abs);if(parsed.origin!==TARGET_ORIGIN)return null;return "/api/conversion-proxy?password="+encodeURIComponent(PROXY_PASSWORD||"")+"&url="+encodeURIComponent(parsed.toString());}catch(_){return null;}}
5078
5599
  var nativeAssign=window.location.assign?window.location.assign.bind(window.location):null;
5079
5600
  var nativeReplace=window.location.replace?window.location.replace.bind(window.location):null;
5080
5601
  function safeNavigate(raw,mode){var abs=toAbsolute(raw);var prox=toProxy(raw);if(!prox){try{console.warn("[conversion-proxy] redirect blocked",{mode:mode,requested:raw,resolved:abs,origin:TARGET_ORIGIN});}catch(_){}return false;}try{console.info("[conversion-proxy] redirect intercepted",{mode:mode,requested:raw,resolved:abs,proxied:prox});if(mode==="replace"&&nativeReplace){nativeReplace(prox);return true;}if(nativeAssign){nativeAssign(prox);return true;}window.location.href=prox;return true;}catch(err){try{console.warn("[conversion-proxy] redirect interception failed",{mode:mode,requested:raw,resolved:abs,proxied:prox,error:err&&err.message?err.message:String(err)});}catch(_){}return false;}}
@@ -5155,9 +5676,9 @@ if(window.fetch){
5155
5676
  }
5156
5677
  if(window.XMLHttpRequest&&window.XMLHttpRequest.prototype&&window.XMLHttpRequest.prototype.open){var _open=window.XMLHttpRequest.prototype.open;window.XMLHttpRequest.prototype.open=function(method,url){try{var u=resolveUrl(String(url));if(u&&isNestedMalformedProxy(u)){arguments[1]=EMPTY_JSON_DATA;}else{arguments[1]=toAbsoluteOriginUrl(url);}}catch(_){}return _open.apply(this,arguments);};}
5157
5678
  if(window.navigator&&typeof window.navigator.sendBeacon==="function"){var _beacon=window.navigator.sendBeacon.bind(window.navigator);window.navigator.sendBeacon=function(url,data){try{if(skipNestedProxyNetwork(String(url)))return true;}catch(_){}return _beacon(url,data);};}
5158
- function withReloadBust(urlRaw){try{var u=resolveUrl(String(urlRaw||""));if(!u)return null;var p=u.pathname||"";var isRootProxyPath=p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0;if(!isRootProxyPath)return null;u.searchParams.set("__ve_reload",String(Date.now()));return u.toString();}catch(_){return null;}}
5159
- document.addEventListener("click",function(ev){try{var t=ev&&ev.target;if(!t||!t.closest)return;var a=t.closest("a[href]");if(!a)return;var href=a.getAttribute("href")||a.href;var bust=withReloadBust(href);if(bust)a.setAttribute("href",bust);}catch(_){}},true);
5160
- document.addEventListener("submit",function(ev){try{var f=ev&&ev.target;if(!f||!f.getAttribute)return;var act=f.getAttribute("action")||"";var bust=withReloadBust(act);if(bust)f.setAttribute("action",bust);}catch(_){}},true);
5679
+ function withReloadBust(urlRaw){try{var u=resolveUrl(String(urlRaw||""));if(!u)return null;var p=u.pathname||"";var isRootProxyPath=p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0;if(!isRootProxyPath)return null;return u.toString();}catch(_){return null;}}
5680
+ document.addEventListener("click",function(ev){try{var t=ev&&ev.target;if(!t||!t.closest)return;var a=t.closest("a[href]");if(a){var href=a.getAttribute("href")||a.href||"";var hasModifier=!!(ev.metaKey||ev.ctrlKey||ev.shiftKey||ev.altKey);var targetAttr=(a.getAttribute("target")||"").toLowerCase();var isNewTab=targetAttr==="_blank";var isDownload=!!a.getAttribute("download");if(hasModifier||isNewTab||isDownload)return;var prox=toProxy(href);if(!prox)return;a.setAttribute("href",prox);if(ev&&typeof ev.preventDefault==="function")ev.preventDefault();safeNavigate(href,"assign");return;}var summary=t.closest("summary[data-link]");if(summary&&summary.getAttribute){var raw=summary.getAttribute("data-link")||"";if(raw){var prox2=toProxy(raw);if(prox2){summary.setAttribute("data-link",prox2);if(ev&&typeof ev.preventDefault==="function")ev.preventDefault();safeNavigate(raw,"assign");}}}}catch(_){}},true);
5681
+ document.addEventListener("submit",function(ev){try{var f=ev&&ev.target;if(!f||!f.getAttribute)return;var act=f.getAttribute("action")||window.location.href;var prox=toProxy(act);if(prox)f.setAttribute("action",prox);}catch(_){}},true);
5161
5682
  if(window.navigator&&window.navigator.serviceWorker&&typeof window.navigator.serviceWorker.register==="function"){window.navigator.serviceWorker.register=function(){return Promise.resolve({scope:"disabled-in-editor-proxy"});};}
5162
5683
  }catch(_){}})();</script>`;
5163
5684
  if (html.includes("</head>")) {