@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/index.js +5 -0
- package/dist/vite.cjs +711 -190
- package/dist/vite.js +711 -190
- package/package.json +2 -1
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
|
-
|
|
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{
|
|
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
|
-
|
|
743
|
-
<div
|
|
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"
|
|
749
|
-
|
|
750
|
-
<div class="lp-tab
|
|
751
|
-
|
|
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-
|
|
757
|
-
<div id="
|
|
758
|
-
|
|
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-
|
|
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
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
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 (!
|
|
1537
|
-
|
|
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+')">✕</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
|
|
1827
|
-
|
|
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
|
|
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="
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
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("' +
|
|
2055
|
+
esc(it.source) +
|
|
2056
|
+
'",' +
|
|
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("' +
|
|
2069
|
+
esc(it.source) +
|
|
2070
|
+
'",' +
|
|
2071
|
+
it.idx +
|
|
2072
|
+
', event)">✕</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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
rows
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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')
|
|
4486
|
-
else
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4585
|
-
|
|
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
|
-
|
|
4662
|
-
|
|
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-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
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
|
|
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
|
|
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?
|
|
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?
|
|
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;
|
|
5159
|
-
document.addEventListener("click",function(ev){try{var t=ev&&ev.target;if(!t||!t.closest)return;var a=t.closest("a[href]");if(
|
|
5160
|
-
document.addEventListener("submit",function(ev){try{var f=ev&&ev.target;if(!f||!f.getAttribute)return;var act=f.getAttribute("action")||
|
|
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>")) {
|