@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.js CHANGED
@@ -3,6 +3,42 @@ import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
 
5
5
  // src/visualEditorProxyPlugin.ts
6
+ var SCRAPER_PROXY_HOST = process.env.SCRAPERAPI_PROXY_HOST || "proxy-server.scraperapi.com";
7
+ var SCRAPER_PROXY_PORT = Number(process.env.SCRAPERAPI_PROXY_PORT || "8001");
8
+ var SCRAPER_PROXY_USERNAME_BASE = process.env.SCRAPERAPI_PROXY_USERNAME_BASE || "scraperapi";
9
+ var SCRAPER_PROXY_USERNAME_PARAMS = process.env.SCRAPERAPI_PROXY_USERNAME_PARAMS || "render=true.wait_for_selector=body.follow_redirect=false.keep_headers=true";
10
+ var SCRAPER_PROXY_USERNAME = process.env.SCRAPERAPI_PROXY_USERNAME || [
11
+ SCRAPER_PROXY_USERNAME_BASE,
12
+ SCRAPER_PROXY_USERNAME_PARAMS
13
+ ].filter(Boolean).join(".");
14
+ var SCRAPER_PROXY_PASSWORD = process.env.SCRAPERAPI_PROXY_PASSWORD || process.env.SCRAPERAPI_API_KEY || "e0252333bde7cbf61d2d388e8c4a962a";
15
+ var SCRAPER_API_ENDPOINT = process.env.SCRAPERAPI_ENDPOINT || "https://api.scraperapi.com/";
16
+ var SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED_RAW = process.env.SCRAPERAPI_REQUEST_TLS_REJECT_UNAUTHORIZED || "false";
17
+ var SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED = !["0", "false", "no"].includes(
18
+ SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED_RAW.toLowerCase()
19
+ );
20
+ var scraperProxyClientPromise = null;
21
+ async function getScraperProxyClient() {
22
+ if (scraperProxyClientPromise) return scraperProxyClientPromise;
23
+ scraperProxyClientPromise = (async () => {
24
+ try {
25
+ const undici = await import('undici');
26
+ const proxyUrl = "http://" + encodeURIComponent(SCRAPER_PROXY_USERNAME) + ":" + encodeURIComponent(SCRAPER_PROXY_PASSWORD) + "@" + SCRAPER_PROXY_HOST + ":" + String(SCRAPER_PROXY_PORT);
27
+ return {
28
+ dispatcher: new undici.ProxyAgent({
29
+ uri: proxyUrl,
30
+ requestTls: {
31
+ rejectUnauthorized: SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED
32
+ }
33
+ }),
34
+ fetchFn: undici.fetch
35
+ };
36
+ } catch (_) {
37
+ return null;
38
+ }
39
+ })();
40
+ return scraperProxyClientPromise;
41
+ }
6
42
  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>`;
7
43
  var iframeAlwaysShowCssGuardScript = `<script id="__ce_force_show_guard">(function(){try{
8
44
  function ensureForceShowStyleLast(){
@@ -347,21 +383,21 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
347
383
  .lp-sec{border-bottom:1px solid var(--border);flex-shrink:0;padding: 24px;}
348
384
  .lp-sec-no-border{border-bottom:none!important}
349
385
  .lp-sec-hd{
350
- padding:10px 12px 8px;font-size:14px;font-weight:600;
386
+ margin-bottom: 12px;
387
+ font-size:14px;font-weight:600;
351
388
  color:#404040;display:flex;align-items:center;justify-content:space-between;gap:5px
352
389
  }
353
390
  .lp-sec-hd-left{display:flex;align-items:center;gap:5px}
354
391
  #active-var-label{display:none;color:var(--accent-txt);font-size:10px;font-weight:500}
355
392
  .lp-info-icon{font-size:11px;color:var(--text-3);cursor:default;opacity:.7}
356
393
  .lp-add-btn{
357
- display:none;
358
394
  background:none;border:none;color:var(--text);font-size:14px;font-weight:500;
359
395
  cursor:pointer;padding:2px 5px;border-radius:4px;transition:all .12s;flex-shrink:0
360
396
  }
361
397
  .lp-add-btn:hover{background:var(--bg-hover);color:var(--accent-txt)}
362
398
 
363
399
  /* \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 */
364
- #variation-tabs{padding:2px 0 6px;display:flex;flex-direction:column; gap:8px;}
400
+ #variation-tabs{display:flex;flex-direction:column; gap:8px;}
365
401
  .var-tab{
366
402
  border-radius: 4px;
367
403
  border: 1px solid #e5e7eb;
@@ -392,7 +428,7 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
392
428
  #comp-search:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(99,102,241,.12)}
393
429
 
394
430
  /* \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 */
395
- .lp-tabs{display:flex;border-bottom:1px solid var(--border);flex-shrink:0;background:#fff}
431
+ .lp-tabs, .section-components-tabs{display:flex;border-bottom:1px solid var(--border);flex-shrink:0;background:#fff}
396
432
  .lp-tab{
397
433
  flex:1;padding:8px 2px;text-align:center;font-size:10px;color:var(--text-3);
398
434
  cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;font-weight:600;line-height:1.15
@@ -402,10 +438,10 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
402
438
  .future-hidden{display:none!important}
403
439
 
404
440
  /* \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 */
405
- .lp-body{flex:1;overflow-y:auto}
406
- .lp-body::-webkit-scrollbar{width:3px}
407
- .lp-body::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:2px}
408
- .tab-pane{display:none}.tab-pane.active{display:block}
441
+ .lp-body, .section-components-body{flex:1;overflow-y:auto}
442
+ .lp-body::-webkit-scrollbar, .section-components-body::-webkit-scrollbar{width:3px}
443
+ .lp-body::-webkit-scrollbar-thumb, .section-components-body::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:2px}
444
+ .tab-pane, .section-components-tab-pane{display:none}.tab-pane.active, .section-components-tab-pane.active{display:block}
409
445
 
410
446
  /* \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 */
411
447
  .cg-hdr{padding:8px 10px 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-3)}
@@ -617,12 +653,83 @@ select.pr-inp{cursor:pointer;background:#fff}
617
653
  }
618
654
  #states-clear:hover{border-color:#fca5a5;color:#ef4444;background:#fef2f2}
619
655
  #history-clear{
620
- display:block;width:calc(100% - 24px);margin:10px 12px;
656
+ display:block;width:calc(100% - 24px);margin:10px 12px 8px;
621
657
  background:none;border:1px solid var(--border);border-radius:6px;
622
658
  padding:6px;font-size:11px;color:var(--text-2);cursor:pointer;font-family:inherit;
623
659
  transition:all .15s
624
660
  }
625
661
  #history-clear:hover{border-color:#fca5a5;color:#ef4444;background:#fef2f2}
662
+ .history-timeline{
663
+ position:relative;
664
+ margin:8px 0 12px;
665
+ padding:0 12px 0 36px;
666
+ }
667
+ .history-timeline::before{
668
+ content:'';
669
+ position:absolute;
670
+ left:20px;
671
+ top:4px;
672
+ bottom:4px;
673
+ width:2px;
674
+ background:#eceff3;
675
+ border-radius:2px;
676
+ }
677
+ .history-item{
678
+ position:relative;
679
+ display:flex;
680
+ align-items:flex-start;
681
+ gap:10px;
682
+ padding:10px 8px 10px 0;
683
+ cursor:pointer;
684
+ }
685
+ .history-dot{
686
+ position:absolute;
687
+ left:-20px;
688
+ top:18px;
689
+ width:10px;
690
+ height:10px;
691
+ border-radius:50%;
692
+ background:#fff;
693
+ border:2px solid #d7dde6;
694
+ }
695
+ .history-card{
696
+ flex:1;
697
+ min-width:0;
698
+ }
699
+ .history-title{
700
+ font-size:14px;
701
+ font-weight:600;
702
+ color:var(--text);
703
+ line-height:1.3;
704
+ }
705
+ .history-meta{
706
+ margin-top:4px;
707
+ display:flex;
708
+ align-items:center;
709
+ gap:6px;
710
+ color:var(--text-2);
711
+ font-size:11px;
712
+ }
713
+ .history-avatar{
714
+ width:18px;
715
+ height:18px;
716
+ border-radius:50%;
717
+ background:#e9e5ff;
718
+ color:#5b47d6;
719
+ display:flex;
720
+ align-items:center;
721
+ justify-content:center;
722
+ font-size:10px;
723
+ font-weight:700;
724
+ }
725
+ .history-time{
726
+ margin-top:3px;
727
+ font-size:11px;
728
+ color:var(--text-3);
729
+ }
730
+ .history-remove{
731
+ margin-top:2px;
732
+ }
626
733
  </style>
627
734
  </head>
628
735
  <body class="mode-editor">
@@ -704,7 +811,7 @@ select.pr-inp{cursor:pointer;background:#fff}
704
811
  <div class="lp-sec">
705
812
  <div class="lp-sec-hd">
706
813
  <span class="lp-sec-hd-left">Variations <span id="active-var-label"></span></span>
707
- <button class="lp-add-btn" title="Add variation">+ Add</button>
814
+ <button class="lp-add-btn" style="display:none" title="Add variation">+ Add</button>
708
815
  </div>
709
816
  <div id="variation-tabs"></div>
710
817
  </div>
@@ -729,27 +836,39 @@ select.pr-inp{cursor:pointer;background:#fff}
729
836
  <span class="lp-sec-hd-left">Elements <i class="bi bi-info-circle lp-info-icon" title="Page elements"></i></span>
730
837
  <button class="lp-add-btn" title="Add element">+ Add</button>
731
838
  </div>
732
- </div>
733
839
 
734
- <!-- Search (hidden, kept for JS) -->
735
- <div style="display:none">
840
+ <!-- Search (hidden, kept for JS) -->
841
+ <div>
736
842
  <input type="search" id="comp-search" placeholder="Search layers\u2026" autocomplete="off">
737
843
  </div>
738
844
 
845
+
739
846
  <!-- Tabs (hidden, kept for JS) -->
740
- <div class="lp-tabs" style="display:none">
741
- <div class="lp-tab active" onclick="switchLeftTab('elements')">Elements</div>
742
- <div class="lp-tab future-hidden" onclick="switchLeftTab('components')">Components</div>
743
- <div class="lp-tab future-hidden" onclick="switchLeftTab('sections')">Sections</div>
847
+ <div class="lp-tabs" >
848
+ <div class="lp-tab active" onclick="switchLeftTab('elements')">Elements</div>
849
+ <div class="lp-tab" onclick="switchLeftTab('dom-tree')">DOM Tree</div>
850
+ </div>
851
+
744
852
  </div>
745
853
 
746
854
  <!-- Tab content -->
747
855
  <div class="lp-body">
748
- <div id="tab-elements" class="tab-pane active"><div id="dom-tree-root" class="dt-tree"></div></div>
749
- <div id="tab-components" class="tab-pane future-hidden"></div>
750
- <div id="tab-sections" class="tab-pane future-hidden"></div>
856
+ <div id="tab-dom-tree" class="tab-pane">
857
+ <div id="dom-tree-root" class="dt-tree">
858
+ </div>
859
+ </div>
860
+ <div id="tab-elements" class="tab-pane active">
861
+ <div id="elements-root" class="elements-tree">
862
+ </div>
863
+ </div>
751
864
  </div>
752
865
 
866
+
867
+
868
+
869
+
870
+
871
+
753
872
  </div><!-- #left-panel -->
754
873
 
755
874
  <!-- Center / iframe panel -->
@@ -757,7 +876,8 @@ select.pr-inp{cursor:pointer;background:#fff}
757
876
 
758
877
  <!-- Floating toolbar for selected element (positioned over iframe) -->
759
878
  <div id="selection-floater" aria-label="Selection actions">
760
- <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>
879
+ <button type="button" class="sf-btn" id="sf-move-up" title="Move up"><i class="bi bi-arrow-up"></i></button>
880
+ <button type="button" class="sf-btn" id="sf-move-down" title="Move down"><i class="bi bi-arrow-down"></i></button>
761
881
  <span class="sf-sep"></span>
762
882
  <button type="button" class="sf-btn" id="sf-resize" disabled title="Resize (coming soon)"><i class="bi bi-arrows-angle-expand"></i></button>
763
883
  <button type="button" class="sf-btn" id="sf-rotate" disabled title="Rotate (coming soon)"><i class="bi bi-arrow-repeat"></i></button>
@@ -784,13 +904,20 @@ select.pr-inp{cursor:pointer;background:#fff}
784
904
 
785
905
  <!-- Right panel -->
786
906
  <div id="right-panel">
787
-
907
+ <!-- Left-tab controls moved here -->
908
+ <div class="section-components-tabs">
909
+ <div class="lp-tab" onclick="switchSectionComponentsTab('components')">Components</div>
910
+ <div class="lp-tab" onclick="switchSectionComponentsTab('sections')">Sections</div>
911
+ </div>
912
+ <div class="lp-body">
913
+ <div id="tab-components" class="tab-pane"></div>
914
+ <div id="tab-sections" class="tab-pane"></div>
915
+ </div>
788
916
  <!-- Element badge (hidden until selection) -->
789
917
  <div id="el-info" style="display:none">
790
918
  <div id="el-info-tag"></div>
791
919
  <div id="el-info-sel"></div>
792
920
  </div>
793
-
794
921
  <!-- \u2500\u2500 3 main tabs \u2500\u2500 -->
795
922
  <div id="main-tabs">
796
923
  <button class="main-tab active" onclick="switchMainTab('design')">Design</button>
@@ -872,12 +999,7 @@ select.pr-inp{cursor:pointer;background:#fff}
872
999
 
873
1000
  <!-- \u2500\u2500 States pane \u2500\u2500 -->
874
1001
  <div id="tab-states" class="rp-pane">
875
- <div id="states-list">
876
- <div class="states-empty">
877
- <i class="bi bi-layers"></i>
878
- No changes yet \u2014 edit elements on the page to see states here
879
- </div>
880
- </div>
1002
+ <div id="states-list"></div>
881
1003
  </div><!-- #tab-states -->
882
1004
 
883
1005
  <!-- \u2500\u2500 History pane (saved DB changesets for active variation) \u2500\u2500 -->
@@ -1125,6 +1247,7 @@ var suppressClickUntil = 0;
1125
1247
  var dragAttachDoc = null;
1126
1248
  var currentMainTab = 'design';
1127
1249
  var currentLeftTab = 'elements';
1250
+ var currentSectionComponentsTab = 'components';
1128
1251
  var dragHandleActive = false;
1129
1252
  var domTreeCollapsed = {};
1130
1253
  var domTreeRefreshTimer = null;
@@ -1156,6 +1279,13 @@ var stateChangesByVarId = {};
1156
1279
  var appliedChangesetSnapshots = {};
1157
1280
  /** Canonical JSON fingerprints of persisted changesets per variation (last load / finalize) */
1158
1281
  var baselineChangesetsByVarId = {};
1282
+ /** Monotonic timestamp key for ordering mixed live + saved history rows. */
1283
+ var vveHistorySeq = 0;
1284
+
1285
+ function nextHistoryTimestamp() {
1286
+ vveHistorySeq += 1;
1287
+ return Date.now() * 1000 + vveHistorySeq;
1288
+ }
1159
1289
 
1160
1290
  // \u2500\u2500 Dirty tracking (compare DB baseline + session stateChanges vs current export) \u2500\u2500
1161
1291
  function beginSuppressIframeMutationDirty() {
@@ -1371,27 +1501,45 @@ function setDevice(device) {
1371
1501
 
1372
1502
  // \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
1373
1503
  function switchLeftTab(tab) {
1504
+ if (tab !== 'elements' && tab !== 'dom-tree') return;
1374
1505
  currentLeftTab = tab;
1375
- var tabs = document.querySelectorAll('.lp-tab');
1376
- tabs[0].classList.toggle('active', tab === 'elements');
1377
- tabs[1].classList.toggle('active', tab === 'components');
1378
- tabs[2].classList.toggle('active', tab === 'sections');
1379
- document.getElementById('tab-elements').classList.toggle('active', tab === 'elements');
1380
- document.getElementById('tab-components').classList.toggle('active', tab === 'components');
1381
- document.getElementById('tab-sections').classList.toggle('active', tab === 'sections');
1506
+ var tabs = document.querySelectorAll('.lp-tabs .lp-tab');
1507
+ for (var i = 0; i < tabs.length; i++) {
1508
+ var oc = tabs[i].getAttribute('onclick') || '';
1509
+ tabs[i].classList.toggle('active', oc.indexOf("switchLeftTab('" + tab + "')") >= 0);
1510
+ }
1511
+ var paneNames = ['elements', 'dom-tree'];
1512
+ for (var p = 0; p < paneNames.length; p++) {
1513
+ var pane = document.getElementById('tab-' + paneNames[p]);
1514
+ if (pane) pane.classList.toggle('active', paneNames[p] === tab);
1515
+ }
1382
1516
  var inp = document.getElementById('comp-search');
1383
1517
  if (tab === 'elements') {
1518
+ inp.placeholder = 'Search elements\u2026';
1519
+ renderElementsTree(inp.value);
1520
+ } else if (tab === 'dom-tree') {
1384
1521
  inp.placeholder = 'Search layers\u2026';
1385
1522
  renderDomTree(inp.value);
1386
- } else if (tab === 'sections') {
1387
- inp.placeholder = 'Search sections\u2026';
1388
- renderSidebar(inp.value);
1389
- } else {
1390
- inp.placeholder = 'Search components\u2026';
1391
- renderSidebar(inp.value);
1392
1523
  }
1393
1524
  }
1394
1525
 
1526
+ function switchSectionComponentsTab(tab) {
1527
+ if (tab !== 'components' && tab !== 'sections') return;
1528
+ currentSectionComponentsTab = tab;
1529
+ var tabs = document.querySelectorAll('.section-components-tabs .lp-tab');
1530
+ for (var i = 0; i < tabs.length; i++) {
1531
+ var oc = tabs[i].getAttribute('onclick') || '';
1532
+ tabs[i].classList.toggle('active', oc.indexOf("switchSectionComponentsTab('" + tab + "')") >= 0);
1533
+ }
1534
+ var compPane = document.getElementById('tab-components');
1535
+ var secPane = document.getElementById('tab-sections');
1536
+ if (compPane) compPane.classList.toggle('active', tab === 'components');
1537
+ if (secPane) secPane.classList.toggle('active', tab === 'sections');
1538
+ var inp = document.getElementById('comp-search');
1539
+ if (inp) inp.placeholder = tab === 'sections' ? 'Search sections\u2026' : 'Search components\u2026';
1540
+ renderSidebar(inp ? inp.value : '');
1541
+ }
1542
+
1395
1543
  // \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
1396
1544
  function toggleAcc(name) {
1397
1545
  var sec = document.getElementById('acc-' + name);
@@ -1514,42 +1662,20 @@ function logChange(selector, inputId, value, targetEl, originalValue) {
1514
1662
  : (originalValue != null ? originalValue : '');
1515
1663
  var entry = {
1516
1664
  selector: selector, inputId: inputId, label: meta.label,
1517
- cssProp: meta.cssProp, value: value, targetEl: targetEl, originalValue: orig
1665
+ cssProp: meta.cssProp, value: value, targetEl: targetEl, originalValue: orig, vveTs: nextHistoryTimestamp()
1518
1666
  };
1519
1667
  if (idx >= 0) { stateChanges[idx] = entry; } else { stateChanges.push(entry); }
1520
1668
  }
1521
1669
  if (currentMainTab === 'states') renderStatesTab();
1670
+ if (currentMainTab === 'history') renderHistoryTab();
1522
1671
  commitStateChangesForActiveVariation();
1523
1672
  recomputeEditorDirty();
1524
1673
  }
1525
1674
 
1526
1675
  function renderStatesTab() {
1527
1676
  var container = document.getElementById('states-list');
1528
- if (!stateChanges.length) {
1529
- 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>';
1530
- return;
1531
- }
1532
- // Group by selector
1533
- var groups = {};
1534
- var order = [];
1535
- stateChanges.forEach(function(c) {
1536
- if (!groups[c.selector]) { groups[c.selector] = []; order.push(c.selector); }
1537
- groups[c.selector].push(c);
1538
- });
1539
- var html = '<button id="states-clear" onclick="clearAllStates()"><i class="bi bi-trash3"></i> Clear all changes</button>';
1540
- order.forEach(function(sel) {
1541
- html += '<div class="state-group"><div class="state-group-sel">'+esc(sel)+'</div>';
1542
- groups[sel].forEach(function(c) {
1543
- var idx = stateChanges.indexOf(c);
1544
- html += '<div class="state-item">' +
1545
- '<span class="state-item-label">'+esc(c.label)+'</span>' +
1546
- '<span class="state-item-val" title="'+esc(c.value)+'">'+esc(c.value)+'</span>' +
1547
- '<button class="state-remove" title="Remove this change" onclick="removeStateChange('+idx+')">&#x2715;</button>' +
1548
- '</div>';
1549
- });
1550
- html += '</div>';
1551
- });
1552
- container.innerHTML = html;
1677
+ if (!container) return;
1678
+ container.innerHTML = '';
1553
1679
  }
1554
1680
 
1555
1681
  // Resolve a live DOM element for a state-change entry.
@@ -1628,7 +1754,9 @@ function removeStateChange(idx) {
1628
1754
  stateChanges.splice(idx, 1);
1629
1755
  commitStateChangesForActiveVariation();
1630
1756
  renderStatesTab();
1757
+ if (currentMainTab === 'history') renderHistoryTab();
1631
1758
  recomputeEditorDirty();
1759
+ scheduleDomTreeRefresh();
1632
1760
  }
1633
1761
 
1634
1762
  function clearAllStates() {
@@ -1639,7 +1767,9 @@ function clearAllStates() {
1639
1767
  stateChanges = [];
1640
1768
  commitStateChangesForActiveVariation();
1641
1769
  renderStatesTab();
1770
+ if (currentMainTab === 'history') renderHistoryTab();
1642
1771
  recomputeEditorDirty();
1772
+ scheduleDomTreeRefresh();
1643
1773
  }
1644
1774
 
1645
1775
  // \u2500\u2500 History tab (saved changesets from DB for active variation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -1812,61 +1942,169 @@ function historyEntryValuePreview(entry) {
1812
1942
  return '';
1813
1943
  }
1814
1944
 
1945
+ function getHistoryTimestampValue(raw, fallback) {
1946
+ var n = Number(raw);
1947
+ if (Number.isFinite(n) && n > 0) return n;
1948
+ return fallback;
1949
+ }
1950
+
1951
+ function historyTimestampForChangeset(entry, idx) {
1952
+ var base = idx + 1;
1953
+ if (!entry) return base;
1954
+ return getHistoryTimestampValue(
1955
+ entry.vveTs != null ? entry.vveTs : (entry.timestamp != null ? entry.timestamp : entry.ts),
1956
+ base,
1957
+ );
1958
+ }
1959
+
1960
+ function historyTimestampForStateChange(change, idx) {
1961
+ return getHistoryTimestampValue(change && change.vveTs, idx + 1);
1962
+ }
1963
+
1964
+ function formatHistoryTimestamp(ts) {
1965
+ if (!Number.isFinite(ts) || ts <= 0) return '';
1966
+ var ms = ts > 9999999999999 ? Math.floor(ts / 1000) : ts;
1967
+ var d = new Date(ms);
1968
+ if (isNaN(d.getTime())) return '';
1969
+ return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
1970
+ }
1971
+
1972
+ function formatHistoryRelativeTime(ts) {
1973
+ if (!Number.isFinite(ts) || ts <= 0) return '';
1974
+ var ms = ts > 9999999999999 ? Math.floor(ts / 1000) : ts;
1975
+ var diff = Math.max(0, Date.now() - ms);
1976
+ var sec = Math.floor(diff / 1000);
1977
+ if (sec < 5) return 'just now';
1978
+ if (sec < 60) return sec + ' seconds ago';
1979
+ var min = Math.floor(sec / 60);
1980
+ if (min < 60) return min + ' minute' + (min === 1 ? '' : 's') + ' ago';
1981
+ var hr = Math.floor(min / 60);
1982
+ if (hr < 24) return hr + ' hour' + (hr === 1 ? '' : 's') + ' ago';
1983
+ var day = Math.floor(hr / 24);
1984
+ return day + ' day' + (day === 1 ? '' : 's') + ' ago';
1985
+ }
1986
+
1987
+ function getUnifiedHistoryItems() {
1988
+ var out = [];
1989
+ var v = getActiveVariationForHistory();
1990
+ var saved = v ? parseVariationChangesets(v) : [];
1991
+ for (var i = 0; i < saved.length; i++) {
1992
+ var e = saved[i];
1993
+ out.push({
1994
+ source: 'saved',
1995
+ idx: i,
1996
+ selector: (e && e.selector) || '(unknown)',
1997
+ label: historyEntryTypeLabel(e),
1998
+ value: historyEntryValuePreview(e),
1999
+ ts: historyTimestampForChangeset(e, i),
2000
+ tsLabel: formatHistoryTimestamp(historyTimestampForChangeset(e, i)),
2001
+ actor: 'Saved changeset',
2002
+ });
2003
+ }
2004
+ var live = stateChanges || [];
2005
+ for (var j = 0; j < live.length; j++) {
2006
+ var c = live[j];
2007
+ if (!c) continue;
2008
+ var ts = historyTimestampForStateChange(c, j + saved.length);
2009
+ out.push({
2010
+ source: 'live',
2011
+ idx: j,
2012
+ selector: c.selector || '(unknown)',
2013
+ label: c.label || 'Live change',
2014
+ value: c.value != null ? String(c.value).slice(0, 120) : '',
2015
+ ts: ts,
2016
+ tsLabel: formatHistoryTimestamp(ts),
2017
+ actor: 'You',
2018
+ });
2019
+ }
2020
+ out.sort(function(a, b) {
2021
+ if (b.ts !== a.ts) return b.ts - a.ts;
2022
+ if (a.source !== b.source) return a.source === 'live' ? -1 : 1;
2023
+ return b.idx - a.idx;
2024
+ });
2025
+ return out;
2026
+ }
2027
+
1815
2028
  function renderHistoryTab() {
1816
2029
  var container = document.getElementById('history-list');
1817
2030
  if (!container) return;
1818
- var v = getActiveVariationForHistory();
1819
- var arr = v ? parseVariationChangesets(v) : [];
1820
- if (!arr.length) {
2031
+ var items = getUnifiedHistoryItems();
2032
+ if (!items.length) {
1821
2033
  container.innerHTML =
1822
- '<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>';
2034
+ '<div class="states-empty"><i class="bi bi-clock-history"></i>No changes yet</div>';
1823
2035
  return;
1824
2036
  }
1825
- var groups = {};
1826
- var order = [];
1827
- for (var gi = 0; gi < arr.length; gi++) {
1828
- var entry = arr[gi];
1829
- var sel = entry.selector || '(unknown)';
1830
- if (!groups[sel]) {
1831
- groups[sel] = [];
1832
- order.push(sel);
1833
- }
1834
- groups[sel].push({ entry: entry, idx: gi });
1835
- }
1836
2037
  var html =
1837
- '<button type="button" id="history-clear" onclick="clearAllHistoryChangesets()"><i class="bi bi-trash3"></i> Clear all saved changes</button>';
1838
- order.forEach(function(sel) {
1839
- html += '<div class="state-group"><div class="state-group-sel">' + esc(sel) + '</div>';
1840
- groups[sel].forEach(function(item) {
1841
- var lab = historyEntryTypeLabel(item.entry);
1842
- var val = historyEntryValuePreview(item.entry);
1843
- html +=
1844
- '<div class="state-item" role="button" tabindex="0" title="Jump to element in iframe" onclick="focusHistoryChangeset(' +
1845
- item.idx +
1846
- ')">' +
1847
- '<span class="state-item-idx" style="opacity:0.55;font-size:10px;min-width:2.2em;display:inline-block" title="Order in saved changesets">#' +
1848
- item.idx +
1849
- '</span>' +
1850
- '<span class="state-item-label">' +
1851
- esc(lab) +
1852
- '</span>' +
1853
- '<span class="state-item-val" title="' +
1854
- esc(val) +
1855
- '">' +
1856
- esc(val) +
1857
- '</span>' +
1858
- '<button type="button" class="state-remove" title="Remove this saved row (#' +
1859
- item.idx +
1860
- ')" onclick="removeHistoryChangeset(' +
1861
- item.idx +
1862
- ', event)">&#x2715;</button>' +
1863
- '</div>';
1864
- });
1865
- html += '</div>';
1866
- });
2038
+ '<button type="button" id="history-clear" onclick="clearAllUnifiedHistory()"><i class="bi bi-trash3"></i> Clear all changes</button>';
2039
+ html += '<div class="history-timeline">';
2040
+ for (var i = 0; i < items.length; i++) {
2041
+ var it = items[i];
2042
+ var title = 'Edit - ' + (it.label || 'Change');
2043
+ var avatarLabel = it.source === 'live' ? 'Y' : 'S';
2044
+ var timeText = formatHistoryRelativeTime(it.ts) || (it.tsLabel || '');
2045
+ html +=
2046
+ '<div class="history-item" role="button" tabindex="0" title="Jump to element in iframe" onclick="focusHistoryItem(&quot;' +
2047
+ esc(it.source) +
2048
+ '&quot;,' +
2049
+ it.idx +
2050
+ ')">' +
2051
+ '<span class="history-dot"></span>' +
2052
+ '<div class="history-card">' +
2053
+ '<div class="history-title">' + esc(title) + '</div>' +
2054
+ '<div class="history-meta">' +
2055
+ '<span class="history-avatar">' + esc(avatarLabel) + '</span>' +
2056
+ '<span>' + esc(it.actor || 'Editor') + '</span>' +
2057
+ '</div>' +
2058
+ '<div class="history-time">' + esc(timeText || 'n/a') + '</div>' +
2059
+ '</div>' +
2060
+ '<button type="button" class="state-remove history-remove" title="Remove this change" onclick="removeHistoryItem(&quot;' +
2061
+ esc(it.source) +
2062
+ '&quot;,' +
2063
+ it.idx +
2064
+ ', event)">&#x2715;</button>' +
2065
+ '</div>';
2066
+ }
2067
+ html += '</div>';
1867
2068
  container.innerHTML = html;
1868
2069
  }
1869
2070
 
2071
+ function focusHistoryItem(source, idx) {
2072
+ if (source === 'live') {
2073
+ var change = stateChanges[idx];
2074
+ if (!change || !change.selector) return;
2075
+ try {
2076
+ var iframe = document.getElementById('iframeId');
2077
+ var iframeDoc = iframe && iframe.contentDocument;
2078
+ if (!iframeDoc) return;
2079
+ var el = querySelectorResolved(iframeDoc, change.selector);
2080
+ if (!el) return;
2081
+ selectElement(el);
2082
+ try {
2083
+ el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
2084
+ } catch(_) {
2085
+ el.scrollIntoView();
2086
+ }
2087
+ } catch(_) {}
2088
+ return;
2089
+ }
2090
+ focusHistoryChangeset(idx);
2091
+ }
2092
+
2093
+ function removeHistoryItem(source, idx, evt) {
2094
+ if (source === 'live') {
2095
+ if (evt && evt.stopPropagation) evt.stopPropagation();
2096
+ removeStateChange(idx);
2097
+ if (currentMainTab === 'history') renderHistoryTab();
2098
+ return;
2099
+ }
2100
+ removeHistoryChangeset(idx, evt);
2101
+ }
2102
+
2103
+ function getLatestHistoryUndoTarget() {
2104
+ var list = getUnifiedHistoryItems();
2105
+ return list.length ? list[0] : null;
2106
+ }
2107
+
1870
2108
  function changesetListHasStructural(arr) {
1871
2109
  if (!arr || !arr.length) return false;
1872
2110
  for (var i = 0; i < arr.length; i++) {
@@ -1978,6 +2216,12 @@ function clearAllHistoryChangesets() {
1978
2216
  softReloadEditorIframe();
1979
2217
  }
1980
2218
 
2219
+ function clearAllUnifiedHistory() {
2220
+ clearAllStates();
2221
+ clearAllHistoryChangesets();
2222
+ if (currentMainTab === 'history') renderHistoryTab();
2223
+ }
2224
+
1981
2225
  // \u2500\u2500 Persisted active variation (survives iframe / full page reload) \u2500\u2500\u2500\u2500\u2500\u2500\u2500
1982
2226
  /** All Visual Editor iframe keys in localStorage use this prefix \u2014 cleared on close. */
1983
2227
  var VVE_LOCAL_STORAGE_PREFIX = 'vve:';
@@ -2243,13 +2487,7 @@ function granularAnySelectorMatches(doc, cs) {
2243
2487
 
2244
2488
  /** Bust bfcache / same-URL no-op reloads so the iframe actually re-parses (loading \u2192 interactive). */
2245
2489
  function appendIframeReloadBust(url) {
2246
- if (!url || url === 'about:blank') return url;
2247
- var stamp = Date.now();
2248
- if (url.indexOf('__ve_reload=') !== -1) {
2249
- return url.replace(/([&?])__ve_reload=[0-9]+/, '$1__ve_reload=' + stamp);
2250
- }
2251
- var sep = url.indexOf('?') !== -1 ? '&' : '?';
2252
- return url + sep + '__ve_reload=' + stamp;
2490
+ return url;
2253
2491
  }
2254
2492
 
2255
2493
  // True when the iframe contentDocument belongs to the current iframe.src navigation.
@@ -2269,13 +2507,7 @@ function iframeDocMatchesNavigatedSrc(iframe, doc) {
2269
2507
  try {
2270
2508
  var base = window.location.href;
2271
2509
  var su = new URL(src, base);
2272
- if (su.searchParams && su.searchParams.has('__ve_reload')) {
2273
- su.searchParams.delete('__ve_reload');
2274
- }
2275
2510
  var du = new URL(loc, base);
2276
- if (du.searchParams && du.searchParams.has('__ve_reload')) {
2277
- du.searchParams.delete('__ve_reload');
2278
- }
2279
2511
  // Same-origin proxy that keeps document address aligned with iframe src
2280
2512
  if (su.origin === du.origin && su.pathname + su.search === du.pathname + du.search) {
2281
2513
  return true;
@@ -2331,7 +2563,7 @@ function runConsistencyReconcile() {
2331
2563
  var doc = iframe && iframe.contentDocument;
2332
2564
  if (!doc || !doc.body) return;
2333
2565
  var variation = variations.find(function(v) { return v._id === activeVarId; });
2334
- var cs = parseVariationChangesets(variation);
2566
+ var cs = buildPersistedChainSetsForVariation(variation);
2335
2567
  if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
2336
2568
  var granular = filterGranularChangesetEntries(cs);
2337
2569
  var unresolved = countUnresolvedGranularSelectors(doc, granular);
@@ -2405,6 +2637,43 @@ function isIframeDomReady(iframe, doc) {
2405
2637
  return true;
2406
2638
  }
2407
2639
 
2640
+ function parseEditorUrlPayload(rawUrl) {
2641
+ if (!rawUrl) return null;
2642
+ try {
2643
+ var parsed = new URL(String(rawUrl), window.location.href);
2644
+ var nestedUrl = parsed.searchParams.get('url');
2645
+ var nestedPassword = parsed.searchParams.get('password');
2646
+ if (nestedUrl || nestedPassword) {
2647
+ return {
2648
+ url: nestedUrl || undefined,
2649
+ password: nestedPassword || undefined,
2650
+ };
2651
+ }
2652
+ return {
2653
+ url: parsed.toString(),
2654
+ password:
2655
+ (experimentData && experimentData.editorPassword)
2656
+ ? String(experimentData.editorPassword)
2657
+ : undefined,
2658
+ };
2659
+ } catch(_) {
2660
+ return null;
2661
+ }
2662
+ }
2663
+
2664
+ var lastEditorUrlPayloadKey = '';
2665
+ function emitEditorUrlChangedPayload(payload) {
2666
+ if (!payload) return;
2667
+ var key = String(payload.url || '') + '|' + String(payload.password || '');
2668
+ if (key === lastEditorUrlPayloadKey) return;
2669
+ lastEditorUrlPayloadKey = key;
2670
+ send('editor-url-changed', payload);
2671
+ }
2672
+ function emitEditorUrlChanged(rawUrl) {
2673
+ var payload = parseEditorUrlPayload(rawUrl);
2674
+ emitEditorUrlChangedPayload(payload);
2675
+ }
2676
+
2408
2677
  function loadPage(proxyUrl) {
2409
2678
  showNoUrl(false);
2410
2679
  lastLoadedProxyUrl = proxyUrl;
@@ -2414,6 +2683,7 @@ function loadPage(proxyUrl) {
2414
2683
  iframe.style.display = 'block';
2415
2684
  setIframePageLoadingUi(true);
2416
2685
  iframe.src = appendIframeReloadBust(proxyUrl);
2686
+ emitEditorUrlChanged(proxyUrl);
2417
2687
  startIframeContentApplyWatcher(navGen, iframe.contentDocument || null);
2418
2688
  scheduleDomTreeRefresh();
2419
2689
  }
@@ -2591,6 +2861,7 @@ function mergeGranularChainSets(baseList, overlayList) {
2591
2861
  function appendSessionStructuralChainRow(varId, row) {
2592
2862
  if (!varId || !row) return;
2593
2863
  if (!sessionStructuralChainRowsByVarId[varId]) sessionStructuralChainRowsByVarId[varId] = [];
2864
+ if (row.vveTs == null) row.vveTs = nextHistoryTimestamp();
2594
2865
  sessionStructuralChainRowsByVarId[varId].push(row);
2595
2866
  }
2596
2867
 
@@ -2598,33 +2869,33 @@ function appendSessionStructuralChainRow(varId, row) {
2598
2869
  function stateChangeToChainSet(c) {
2599
2870
  if (!c || !c.selector) return null;
2600
2871
  if (c.cssProp) {
2601
- return { selector: c.selector, type: 'style', property: c.cssProp, value: c.value };
2872
+ return { selector: c.selector, type: 'style', property: c.cssProp, value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2602
2873
  }
2603
2874
  switch (c.inputId) {
2604
2875
  case 'pp-text':
2605
- return { selector: c.selector, type: 'content', value: c.value };
2876
+ return { selector: c.selector, type: 'content', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2606
2877
  case 'pp-html':
2607
- return { selector: c.selector, type: 'content', html: c.value };
2878
+ return { selector: c.selector, type: 'content', html: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2608
2879
  case 'pp-cls':
2609
- return { selector: c.selector, type: 'attribute', attribute: 'class', value: c.value };
2880
+ return { selector: c.selector, type: 'attribute', attribute: 'class', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2610
2881
  case 'pp-id':
2611
- return { selector: c.selector, type: 'attribute', attribute: 'id', value: c.value };
2882
+ return { selector: c.selector, type: 'attribute', attribute: 'id', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2612
2883
  case 'pp-href':
2613
- return { selector: c.selector, type: 'attribute', attribute: 'href', value: c.value };
2884
+ return { selector: c.selector, type: 'attribute', attribute: 'href', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2614
2885
  case 'pp-target':
2615
- return { selector: c.selector, type: 'attribute', attribute: 'target', value: c.value };
2886
+ return { selector: c.selector, type: 'attribute', attribute: 'target', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2616
2887
  case 'pp-src':
2617
- return { selector: c.selector, type: 'attribute', attribute: 'src', value: c.value };
2888
+ return { selector: c.selector, type: 'attribute', attribute: 'src', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2618
2889
  case 'pp-alt':
2619
- return { selector: c.selector, type: 'attribute', attribute: 'alt', value: c.value };
2890
+ return { selector: c.selector, type: 'attribute', attribute: 'alt', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2620
2891
  case 'pp-ph':
2621
- return { selector: c.selector, type: 'attribute', attribute: 'placeholder', value: c.value };
2892
+ return { selector: c.selector, type: 'attribute', attribute: 'placeholder', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2622
2893
  case 'pp-css':
2623
- return { selector: c.selector, type: 'attribute', attribute: 'style', value: c.value };
2894
+ return { selector: c.selector, type: 'attribute', attribute: 'style', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2624
2895
  case 'pp-mob-css':
2625
- return { selector: c.selector, type: 'attribute', attribute: 'data-mobile-css', value: c.value };
2896
+ return { selector: c.selector, type: 'attribute', attribute: 'data-mobile-css', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2626
2897
  case 'pp-tab-css':
2627
- return { selector: c.selector, type: 'attribute', attribute: 'data-tablet-css', value: c.value };
2898
+ return { selector: c.selector, type: 'attribute', attribute: 'data-tablet-css', value: c.value, vveTs: c.vveTs || nextHistoryTimestamp() };
2628
2899
  default:
2629
2900
  return null;
2630
2901
  }
@@ -2912,7 +3183,7 @@ function applyActiveVariationHtml() {
2912
3183
  if (!iframeDoc || !iframeDoc.body) return;
2913
3184
 
2914
3185
  var variation = variations.find(function(v) { return v._id === activeVarId; });
2915
- var cs = parseVariationChangesets(variation);
3186
+ var cs = buildPersistedChainSetsForVariation(variation);
2916
3187
  refreshPersistentChangesetStyleTagForActiveVariation();
2917
3188
 
2918
3189
  beginSuppressIframeMutationDirty();
@@ -2970,7 +3241,7 @@ function applyVariationGranularOnly(iframeDoc) {
2970
3241
  if (!activeVarId || !iframeDoc || !iframeDoc.body) return;
2971
3242
  if (varHtmlCache[activeVarId]) return;
2972
3243
  var variation = variations.find(function(v) { return v._id === activeVarId; });
2973
- var cs = parseVariationChangesets(variation);
3244
+ var cs = buildPersistedChainSetsForVariation(variation);
2974
3245
  if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
2975
3246
  beginSuppressIframeMutationDirty();
2976
3247
  try {
@@ -2989,7 +3260,7 @@ function reapplyActiveVariationGranular(iframeDoc) {
2989
3260
  if (!activeVarId || !iframeDoc || !iframeDoc.body) return;
2990
3261
  if (varHtmlCache[activeVarId]) return;
2991
3262
  var variation = variations.find(function(v) { return v._id === activeVarId; });
2992
- var cs = parseVariationChangesets(variation);
3263
+ var cs = buildPersistedChainSetsForVariation(variation);
2993
3264
  if (!cs.length || changesetsHaveBodySnapshot(cs)) return;
2994
3265
  beginSuppressIframeMutationDirty();
2995
3266
  try {
@@ -3033,7 +3304,7 @@ function startIframeContentApplyWatcher(navGen, prevDocRef) {
3033
3304
 
3034
3305
  if (doc.readyState === 'loading') {
3035
3306
  var variation = variations.find(function(v) { return v._id === activeVarId; });
3036
- var cs0 = parseVariationChangesets(variation);
3307
+ var cs0 = buildPersistedChainSetsForVariation(variation);
3037
3308
  if (!cs0.length || changesetsHaveBodySnapshot(cs0)) return;
3038
3309
  var granular = filterGranularChangesetEntries(cs0);
3039
3310
  if (!granular.length) return;
@@ -3079,8 +3350,9 @@ function selectElement(el) {
3079
3350
  document.getElementById('no-sel').style.display = 'none';
3080
3351
  renderRightPanel(el);
3081
3352
  updateSelectionToolbar();
3082
- if (currentLeftTab === 'elements') {
3083
- var dr = document.getElementById('dom-tree-root');
3353
+ if (currentLeftTab === 'elements' || currentLeftTab === 'dom-tree') {
3354
+ var treeRootId = currentLeftTab === 'elements' ? 'elements-root' : 'dom-tree-root';
3355
+ var dr = document.getElementById(treeRootId);
3084
3356
  if (dr && dr.querySelector('.dt-row')) syncDomTreeSelection();
3085
3357
  else scheduleDomTreeRefresh();
3086
3358
  }
@@ -3343,27 +3615,32 @@ function deleteSelectedEl() {
3343
3615
  }
3344
3616
 
3345
3617
  function syncDomTreeSelection() {
3346
- var root = document.getElementById('dom-tree-root');
3347
- if (!root) return;
3348
- var rows = root.querySelectorAll('.dt-row');
3349
- for (var i = 0; i < rows.length; i++) {
3350
- rows[i].classList.toggle('dt-selected', !!(selectedEl && rows[i]._dtEl === selectedEl));
3351
- }
3352
- if (!selectedEl) return;
3353
- var found = null;
3354
- for (var j = 0; j < rows.length; j++) {
3355
- if (rows[j]._dtEl === selectedEl) { found = rows[j]; break; }
3618
+ var roots = ['dom-tree-root', 'elements-root'];
3619
+ for (var r = 0; r < roots.length; r++) {
3620
+ var root = document.getElementById(roots[r]);
3621
+ if (!root) continue;
3622
+ var rows = root.querySelectorAll('.dt-row');
3623
+ for (var i = 0; i < rows.length; i++) {
3624
+ rows[i].classList.toggle('dt-selected', !!(selectedEl && rows[i]._dtEl === selectedEl));
3625
+ }
3626
+ if (!selectedEl) continue;
3627
+ var found = null;
3628
+ for (var j = 0; j < rows.length; j++) {
3629
+ if (rows[j]._dtEl === selectedEl) { found = rows[j]; break; }
3630
+ }
3631
+ if (found) found.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
3356
3632
  }
3357
- if (found) found.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
3358
3633
  }
3359
3634
 
3360
3635
  function scheduleDomTreeRefresh() {
3361
- if (currentLeftTab !== 'elements') return;
3636
+ if (currentLeftTab !== 'elements' && currentLeftTab !== 'dom-tree') return;
3362
3637
  if (domTreeRefreshTimer) clearTimeout(domTreeRefreshTimer);
3363
3638
  domTreeRefreshTimer = setTimeout(function() {
3364
3639
  domTreeRefreshTimer = null;
3365
3640
  var inp = document.getElementById('comp-search');
3366
- renderDomTree(inp ? inp.value : '');
3641
+ var q = inp ? inp.value : '';
3642
+ if (currentLeftTab === 'elements') renderElementsTree(q);
3643
+ else if (currentLeftTab === 'dom-tree') renderDomTree(q);
3367
3644
  }, 150);
3368
3645
  }
3369
3646
 
@@ -3386,6 +3663,129 @@ function domTreePathSegment(el) {
3386
3663
  return tag + '[' + idx + ']';
3387
3664
  }
3388
3665
 
3666
+ function renderElementsTree(filterRaw) {
3667
+ var filterText = (filterRaw || '').toLowerCase().trim();
3668
+ var root = document.getElementById('elements-root');
3669
+ if (!root) return;
3670
+ var iframe = document.getElementById('iframeId');
3671
+ var doc = iframe && iframe.contentDocument;
3672
+ if (!isIframeDomReady(iframe, doc)) {
3673
+ root.innerHTML = '<div class="dt-muted">Load a page to see the elements.</div>';
3674
+ return;
3675
+ }
3676
+
3677
+ function skippable(el) {
3678
+ return isDomTreeSkippableTagName(el.tagName);
3679
+ }
3680
+
3681
+ function nodeIcon(tag) {
3682
+ tag = (tag || '').toLowerCase();
3683
+ if (/^h[1-6]$/.test(tag)) return 'bi bi-type-h1';
3684
+ if (tag === 'a') return 'bi bi-link-45deg';
3685
+ if (tag === 'img') return 'bi bi-image';
3686
+ if (tag === 'section' || tag === 'main' || tag === 'article' || tag === 'header' || tag === 'footer' || tag === 'nav') return 'bi bi-layout-three-columns';
3687
+ if (tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea') return 'bi bi-ui-radios';
3688
+ if (tag === 'ul' || tag === 'ol') return 'bi bi-list-ul';
3689
+ if (tag === 'li') return 'bi bi-dot';
3690
+ if (tag === 'svg') return 'bi bi-bezier2';
3691
+ if (tag === 'p' || tag === 'span') return 'bi bi-text-left';
3692
+ return 'bi bi-square';
3693
+ }
3694
+
3695
+ function labelFor(el) {
3696
+ var tag = (el.tagName || '').toLowerCase();
3697
+ return tag.toUpperCase();
3698
+ }
3699
+
3700
+ function isListableNode(el) {
3701
+ if (!el || el.nodeType !== 1) return false;
3702
+ if (skippable(el)) return false;
3703
+ var tag = (el.tagName || '').toLowerCase();
3704
+ if (tag !== 'svg') {
3705
+ var p = el.parentElement;
3706
+ while (p) {
3707
+ if ((p.tagName || '').toLowerCase() === 'svg') return false;
3708
+ p = p.parentElement;
3709
+ }
3710
+ }
3711
+ return true;
3712
+ }
3713
+
3714
+ var nodes = [];
3715
+ var i, cursor;
3716
+ try {
3717
+ cursor = doc.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT, null);
3718
+ } catch(_) {
3719
+ cursor = null;
3720
+ }
3721
+ if (cursor) {
3722
+ while (cursor.nextNode()) {
3723
+ var node = cursor.currentNode;
3724
+ if (isListableNode(node)) nodes.push(node);
3725
+ if (nodes.length > 4000) break;
3726
+ }
3727
+ } else {
3728
+ function collectFlat(el) {
3729
+ if (!el || !el.children) return;
3730
+ for (var j = 0; j < el.children.length; j++) {
3731
+ var c = el.children[j];
3732
+ if (!isListableNode(c)) continue;
3733
+ nodes.push(c);
3734
+ if (nodes.length > 4000) return;
3735
+ collectFlat(c);
3736
+ if (nodes.length > 4000) return;
3737
+ }
3738
+ }
3739
+ collectFlat(doc.body);
3740
+ }
3741
+
3742
+ root.innerHTML = '';
3743
+ for (i = 0; i < nodes.length; i++) {
3744
+ var el = nodes[i];
3745
+ var lblText = labelFor(el);
3746
+ if (filterText && lblText.toLowerCase().indexOf(filterText) < 0) continue;
3747
+
3748
+ var row = document.createElement('div');
3749
+ row.className = 'dt-row';
3750
+ row._dtEl = el;
3751
+ if (el === selectedEl) row.classList.add('dt-selected');
3752
+ row.style.paddingLeft = '4px';
3753
+
3754
+ var spacer = document.createElement('button');
3755
+ spacer.type = 'button';
3756
+ spacer.className = 'dt-chev dt-spacer';
3757
+
3758
+ var ico = document.createElement('div');
3759
+ ico.className = 'dt-ico';
3760
+ ico.innerHTML = '<i class="' + nodeIcon(el.tagName) + '"></i>';
3761
+
3762
+ var lbl = document.createElement('div');
3763
+ lbl.className = 'dt-lbl';
3764
+ lbl.textContent = lblText;
3765
+ lbl.title = buildSelector(el);
3766
+
3767
+ row.appendChild(spacer);
3768
+ row.appendChild(ico);
3769
+ row.appendChild(lbl);
3770
+ row.onclick = (function(targetEl) {
3771
+ return function() { selectElementFromTree(targetEl); };
3772
+ })(el);
3773
+ row.onmouseenter = (function(targetEl) {
3774
+ return function() { setTreeHoverHighlight(targetEl); };
3775
+ })(el);
3776
+ root.appendChild(row);
3777
+ }
3778
+
3779
+ if (!root.querySelector('.dt-row')) {
3780
+ root.innerHTML = filterText
3781
+ ? '<div class="dt-muted">No elements match your search.</div>'
3782
+ : '<div class="dt-muted">No elements found.</div>';
3783
+ }
3784
+ root.onmouseleave = function() {
3785
+ clearTreeHoverHighlight();
3786
+ };
3787
+ }
3788
+
3389
3789
  function renderDomTree(filterRaw) {
3390
3790
  var filterText = (filterRaw || '').toLowerCase().trim();
3391
3791
  var root = document.getElementById('dom-tree-root');
@@ -4124,6 +4524,20 @@ function recordReorderAfterDrag(movedEl) {
4124
4524
  }
4125
4525
  }
4126
4526
 
4527
+ function moveSelectedElByDirection(direction) {
4528
+ if (!selectedEl || !selectedEl.parentElement) return;
4529
+ var p = selectedEl.parentElement;
4530
+ var sibling = direction < 0 ? selectedEl.previousElementSibling : selectedEl.nextElementSibling;
4531
+ if (!sibling) return;
4532
+ if (direction < 0) p.insertBefore(selectedEl, sibling);
4533
+ else p.insertBefore(sibling, selectedEl);
4534
+ recordReorderAfterDrag(selectedEl);
4535
+ saveCurrentVariationHtml();
4536
+ recomputeEditorDirty();
4537
+ scheduleDomTreeRefresh();
4538
+ updateSelectionToolbar();
4539
+ }
4540
+
4127
4541
  function attachDragReposition() {
4128
4542
  try {
4129
4543
  var iframe = document.getElementById('iframeId');
@@ -4310,7 +4724,9 @@ function syncIframeInteractions(reason) {
4310
4724
  scheduleConsistencyReconcile();
4311
4725
  bindSelectionToolbarScroll();
4312
4726
  var inp = document.getElementById('comp-search');
4313
- renderDomTree(inp ? inp.value : '');
4727
+ var q = inp ? inp.value : '';
4728
+ if (currentLeftTab === 'elements') renderElementsTree(q);
4729
+ else if (currentLeftTab === 'dom-tree') renderDomTree(q);
4314
4730
  updateSelectionToolbar();
4315
4731
  recomputeEditorDirty();
4316
4732
  } catch(_) {}
@@ -4474,8 +4890,11 @@ function renderSidebar(filter) {
4474
4890
  }
4475
4891
 
4476
4892
  document.getElementById('comp-search').addEventListener('input', function() {
4477
- if (currentLeftTab === 'elements') renderDomTree(this.value);
4478
- else renderSidebar(this.value);
4893
+ if (currentLeftTab === 'elements') renderElementsTree(this.value);
4894
+ else if (currentLeftTab === 'dom-tree') renderDomTree(this.value);
4895
+ if (currentSectionComponentsTab === 'components' || currentSectionComponentsTab === 'sections') {
4896
+ renderSidebar(this.value);
4897
+ }
4479
4898
  });
4480
4899
 
4481
4900
  // \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
@@ -4541,19 +4960,11 @@ document.addEventListener('keydown', function(e) {
4541
4960
  var k = (e.key || '').toLowerCase();
4542
4961
  if (meta && !e.shiftKey && k === 'z') {
4543
4962
  e.preventDefault();
4544
- if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
4545
- Vvveb.Undo.undo();
4546
- saveCurrentVariationHtml();
4547
- recomputeEditorDirty();
4548
- }
4963
+ runEditorUndo();
4549
4964
  }
4550
4965
  if (meta && e.shiftKey && k === 'z') {
4551
4966
  e.preventDefault();
4552
- if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
4553
- Vvveb.Undo.redo();
4554
- saveCurrentVariationHtml();
4555
- recomputeEditorDirty();
4556
- }
4967
+ runEditorRedo();
4557
4968
  }
4558
4969
  if (meta && e.key === 's') { e.preventDefault(); handleSave(); }
4559
4970
  if (e.key === 'Escape') {
@@ -4573,8 +4984,39 @@ document.addEventListener('keydown', function(e) {
4573
4984
  if (selectedEl) deselectElement();
4574
4985
  }
4575
4986
  });
4576
- document.getElementById('btn-undo').addEventListener('click', function() { if (typeof Vvveb !== 'undefined' && Vvveb.Undo) Vvveb.Undo.undo(); });
4577
- document.getElementById('btn-redo').addEventListener('click', function() { if (typeof Vvveb !== 'undefined' && Vvveb.Undo) Vvveb.Undo.redo(); });
4987
+ function runEditorUndo() {
4988
+ var target = getLatestHistoryUndoTarget();
4989
+ if (target) {
4990
+ removeHistoryItem(target.source, target.idx);
4991
+ return;
4992
+ }
4993
+ if (!(typeof Vvveb !== 'undefined' && Vvveb.Undo)) return;
4994
+ Vvveb.Undo.undo();
4995
+ saveCurrentVariationHtml();
4996
+ recomputeEditorDirty();
4997
+ scheduleDomTreeRefresh();
4998
+ updateSelectionToolbar();
4999
+ }
5000
+
5001
+ function runEditorRedo() {
5002
+ if (!(typeof Vvveb !== 'undefined' && Vvveb.Undo)) return;
5003
+ Vvveb.Undo.redo();
5004
+ saveCurrentVariationHtml();
5005
+ recomputeEditorDirty();
5006
+ scheduleDomTreeRefresh();
5007
+ updateSelectionToolbar();
5008
+ }
5009
+
5010
+ document.getElementById('btn-undo').addEventListener('click', function(e) {
5011
+ e.preventDefault();
5012
+ e.stopPropagation();
5013
+ runEditorUndo();
5014
+ });
5015
+ document.getElementById('btn-redo').addEventListener('click', function(e) {
5016
+ e.preventDefault();
5017
+ e.stopPropagation();
5018
+ runEditorRedo();
5019
+ });
4578
5020
 
4579
5021
  function layoutLoadingTooltip(host) {
4580
5022
  var tip = host.querySelector('.ve-pl-tooltip');
@@ -4650,8 +5092,8 @@ function registerCROSections() {
4650
5092
 
4651
5093
  window.addEventListener('load', function() {
4652
5094
  registerCROSections();
4653
- renderSidebar();
4654
- renderDomTree(document.getElementById('comp-search').value);
5095
+ switchSectionComponentsTab(currentSectionComponentsTab);
5096
+ renderElementsTree(document.getElementById('comp-search').value);
4655
5097
  vvvebReady = true;
4656
5098
  bindLoadingTooltipPositioning();
4657
5099
 
@@ -4660,6 +5102,18 @@ window.addEventListener('load', function() {
4660
5102
 
4661
5103
  // After each iframe load: apply variation, wire click+mutation handlers
4662
5104
  var iframe = document.getElementById('iframeId');
5105
+ window.addEventListener('message', function(ev) {
5106
+ try {
5107
+ var d = ev && ev.data;
5108
+ if (!d || d.channel !== 'vvveb-proxy-url' || d.type !== 'editor-url-changed') return;
5109
+ if (!iframe || !iframe.contentWindow || ev.source !== iframe.contentWindow) return;
5110
+ var payload = d.payload || {};
5111
+ emitEditorUrlChangedPayload({
5112
+ url: payload.url || undefined,
5113
+ password: payload.password || undefined,
5114
+ });
5115
+ } catch(_) {}
5116
+ });
4663
5117
  iframe.addEventListener('load', function() {
4664
5118
  if (!iframe.src || iframe.src === 'about:blank' || iframe.src === window.location.href) return;
4665
5119
  var doc = iframe.contentDocument;
@@ -4670,6 +5124,7 @@ window.addEventListener('load', function() {
4670
5124
  }
4671
5125
  var docUrl = '';
4672
5126
  try { docUrl = String(doc.URL || ''); } catch(_) {}
5127
+ emitEditorUrlChanged(iframe.src || docUrl);
4673
5128
  // Stale events: src may already be the proxy URL while the document is still
4674
5129
  // about:blank (e.g. src cleared then reset to force reload). Ask sync path to retry.
4675
5130
  if (docUrl === 'about:blank') {
@@ -4704,12 +5159,22 @@ window.addEventListener('load', function() {
4704
5159
  syncIframeInteractions('iframe-load');
4705
5160
  });
4706
5161
 
4707
- document.getElementById('sf-drag').addEventListener('click', function(e) {
4708
- e.preventDefault();
4709
- e.stopPropagation();
4710
- if (!selectedEl) return;
4711
- setDragHandleActive(!dragHandleActive);
4712
- });
5162
+ var sfMoveUp = document.getElementById('sf-move-up');
5163
+ if (sfMoveUp) {
5164
+ sfMoveUp.addEventListener('click', function(e) {
5165
+ e.preventDefault();
5166
+ e.stopPropagation();
5167
+ moveSelectedElByDirection(-1);
5168
+ });
5169
+ }
5170
+ var sfMoveDown = document.getElementById('sf-move-down');
5171
+ if (sfMoveDown) {
5172
+ sfMoveDown.addEventListener('click', function(e) {
5173
+ e.preventDefault();
5174
+ e.stopPropagation();
5175
+ moveSelectedElByDirection(1);
5176
+ });
5177
+ }
4713
5178
  document.getElementById('sf-dup').addEventListener('click', function(e) {
4714
5179
  e.preventDefault();
4715
5180
  e.stopPropagation();
@@ -4924,6 +5389,8 @@ function createVisualEditorMiddleware(options) {
4924
5389
  const targetUrl = url.searchParams.get("url");
4925
5390
  const password = url.searchParams.get("password") || "";
4926
5391
  const strictFreezeParam = (url.searchParams.get("strictObserverFreeze") || "").toLowerCase();
5392
+ const proxyParam = (url.searchParams.get("proxy") || "").toLowerCase();
5393
+ const useScraperProxy = proxyParam === "1" || proxyParam === "true" || proxyParam === "yes";
4927
5394
  const strictObserverFreezeForRequest = strictFreezeParam === "1" || strictFreezeParam === "true" || strictFreezeParam === "yes" ? true : strictFreezeParam === "0" || strictFreezeParam === "false" || strictFreezeParam === "no" ? false : strictObserverFreeze;
4928
5395
  if (!targetUrl) {
4929
5396
  res.statusCode = 400;
@@ -4933,13 +5400,37 @@ function createVisualEditorMiddleware(options) {
4933
5400
  const parsed = new URL(targetUrl);
4934
5401
  const origin = parsed.origin;
4935
5402
  const method = (req.method || "GET").toUpperCase();
5403
+ const scraperProxyClient = useScraperProxy ? await getScraperProxyClient() : null;
5404
+ if (useScraperProxy && !scraperProxyClient) {
5405
+ res.statusCode = 500;
5406
+ res.setHeader("Content-Type", "application/json");
5407
+ res.end(
5408
+ JSON.stringify({
5409
+ error: "ScraperAPI proxy is not configured. Set SCRAPERAPI_PROXY_PASSWORD or SCRAPERAPI_API_KEY."
5410
+ })
5411
+ );
5412
+ return;
5413
+ }
5414
+ if (!SCRAPER_PROXY_PASSWORD) ;
5415
+ const upstreamFetch = (input, init = {}) => {
5416
+ if (!useScraperProxy || !scraperProxyClient) {
5417
+ const scraperUrl = new URL(SCRAPER_API_ENDPOINT);
5418
+ scraperUrl.searchParams.set("api_key", SCRAPER_PROXY_PASSWORD);
5419
+ scraperUrl.searchParams.set("url", input);
5420
+ return fetch(scraperUrl.toString(), init);
5421
+ }
5422
+ return scraperProxyClient.fetchFn(input, {
5423
+ ...init,
5424
+ dispatcher: scraperProxyClient.dispatcher
5425
+ });
5426
+ };
4936
5427
  const headers = {
4937
5428
  "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
4938
5429
  Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
4939
5430
  };
4940
5431
  let cookieHeader = "";
4941
5432
  if (password) {
4942
- const passResp = await fetch(`${origin}/password`, {
5433
+ const passResp = await upstreamFetch(`${origin}/password`, {
4943
5434
  method: "POST",
4944
5435
  headers: {
4945
5436
  "Content-Type": "application/x-www-form-urlencoded",
@@ -4986,7 +5477,7 @@ function createVisualEditorMiddleware(options) {
4986
5477
  const timeoutId = setTimeout(() => ac.abort(), upstreamTimeoutMs);
4987
5478
  let upstream;
4988
5479
  try {
4989
- upstream = await fetch(targetUrl, {
5480
+ upstream = await upstreamFetch(targetUrl, {
4990
5481
  method,
4991
5482
  headers: fetchHeaders,
4992
5483
  body: requestBody ? Buffer.from(requestBody) : null,
@@ -4996,11 +5487,27 @@ function createVisualEditorMiddleware(options) {
4996
5487
  } catch (fetchErr) {
4997
5488
  clearTimeout(timeoutId);
4998
5489
  const aborted = fetchErr?.name === "AbortError";
5490
+ const fetchCause = fetchErr?.cause;
4999
5491
  res.statusCode = aborted ? 504 : 502;
5000
5492
  res.setHeader("Content-Type", "application/json");
5001
5493
  res.end(
5002
5494
  JSON.stringify({
5003
- error: aborted ? `Upstream request timed out after ${upstreamTimeoutMs / 1e3}s` : fetchErr?.message || "Upstream fetch failed"
5495
+ error: aborted ? `Upstream request timed out after ${upstreamTimeoutMs / 1e3}s` : fetchErr?.message || "Upstream fetch failed",
5496
+ phase: useScraperProxy ? "scraperapi-proxy-fetch" : "scraperapi-simple-fetch",
5497
+ fetchMode: useScraperProxy ? "proxy" : "simple",
5498
+ scraperapi: {
5499
+ host: SCRAPER_PROXY_HOST,
5500
+ port: SCRAPER_PROXY_PORT,
5501
+ username: SCRAPER_PROXY_USERNAME,
5502
+ endpoint: SCRAPER_API_ENDPOINT,
5503
+ hasProxyPassword: Boolean(SCRAPER_PROXY_PASSWORD),
5504
+ requestTlsRejectUnauthorized: SCRAPER_REQUEST_TLS_REJECT_UNAUTHORIZED
5505
+ },
5506
+ cause: fetchCause && typeof fetchCause === "object" ? {
5507
+ name: fetchCause.name,
5508
+ code: fetchCause.code,
5509
+ message: fetchCause.message
5510
+ } : fetchCause ? String(fetchCause) : null
5004
5511
  })
5005
5512
  );
5006
5513
  return;
@@ -5031,7 +5538,7 @@ function createVisualEditorMiddleware(options) {
5031
5538
  }
5032
5539
  html = html.replace(/(href|src|action)="\/(?!\/)/g, `$1="${origin}/`);
5033
5540
  const escapedOrigin = origin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5034
- const proxyBase = `/api/conversion-proxy?__ve_reload=${Date.now()}&password=${encodeURIComponent(password)}&url=`;
5541
+ const proxyBase = `/api/conversion-proxy?password=${encodeURIComponent(password)}&url=`;
5035
5542
  html = html.replace(
5036
5543
  new RegExp(`(href|action)="${escapedOrigin}(/[^"]*)"`, "g"),
5037
5544
  (match, attr, urlPath) => {
@@ -5042,6 +5549,12 @@ function createVisualEditorMiddleware(options) {
5042
5549
  return `${attr}="${proxyBase}${encodeURIComponent(origin + urlPath)}"`;
5043
5550
  }
5044
5551
  );
5552
+ html = html.replace(
5553
+ /data-link="\/(?!\/)([^"]*)"/g,
5554
+ (_match, pathPart) => `data-link="/api/conversion-proxy?password=${encodeURIComponent(password)}&url=${encodeURIComponent(
5555
+ `${origin}/${pathPart}`
5556
+ )}"`
5557
+ );
5045
5558
  if (html.includes("</head>")) {
5046
5559
  html = html.replace(
5047
5560
  "</head>",
@@ -5063,10 +5576,18 @@ var TARGET_ORIGIN=${JSON.stringify(origin)};
5063
5576
  var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
5064
5577
  var PROXY_PASSWORD=${JSON.stringify(password)};
5065
5578
  var STRICT_OBSERVER_FREEZE=${JSON.stringify(strictObserverFreezeForRequest)};
5579
+ var PARENT_URL_CHANNEL="vvveb-proxy-url";
5066
5580
  window.__CONVERSION_EDITOR_ACTIVE__=true;
5581
+ 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;}}
5582
+ 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(_){}}
5583
+ try{notifyEditorUrlChanged();}catch(_){}
5584
+ 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(_){}
5585
+ 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(_){}
5586
+ try{window.addEventListener("popstate",notifyEditorUrlChanged,true);}catch(_){}
5587
+ try{window.addEventListener("hashchange",notifyEditorUrlChanged,true);}catch(_){}
5067
5588
  function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
5068
5589
  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;}}
5069
- 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;}}
5590
+ 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;}}
5070
5591
  var nativeAssign=window.location.assign?window.location.assign.bind(window.location):null;
5071
5592
  var nativeReplace=window.location.replace?window.location.replace.bind(window.location):null;
5072
5593
  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;}}
@@ -5147,9 +5668,9 @@ if(window.fetch){
5147
5668
  }
5148
5669
  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);};}
5149
5670
  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);};}
5150
- 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;}}
5151
- 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);
5152
- 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);
5671
+ 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;}}
5672
+ 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);
5673
+ 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);
5153
5674
  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"});};}
5154
5675
  }catch(_){}})();</script>`;
5155
5676
  if (html.includes("</head>")) {