@accelerated-agency/visual-editor 0.2.6 → 0.2.8
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 +33 -2
- package/dist/vite.cjs +164 -5
- package/dist/vite.js +164 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1842,6 +1842,31 @@ function ElementIcon({ tag }) {
|
|
|
1842
1842
|
}
|
|
1843
1843
|
return /* @__PURE__ */ jsx("span", { className: "shrink-0 w-5 h-5 rounded flex items-center justify-center", style: { backgroundColor: bgColor }, children: /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx("rect", { x: "1.5", y: "1.5", width: "9", height: "9", rx: "1.5", stroke: iconColor, strokeWidth: "1.2" }) }) });
|
|
1844
1844
|
}
|
|
1845
|
+
|
|
1846
|
+
// src/lib/normalizeProxyBaseUrl.ts
|
|
1847
|
+
function normalizeProxyBaseUrl(proxyBaseUrl) {
|
|
1848
|
+
const raw = (proxyBaseUrl || "").trim().replace(/\/+$/, "");
|
|
1849
|
+
if (!raw) return "";
|
|
1850
|
+
const isAbsoluteHttpUrl = /^https?:\/\//i.test(raw);
|
|
1851
|
+
if (!isAbsoluteHttpUrl || typeof window === "undefined") {
|
|
1852
|
+
return raw;
|
|
1853
|
+
}
|
|
1854
|
+
try {
|
|
1855
|
+
const parsed = new URL(raw);
|
|
1856
|
+
const pageIsHttps = window.location.protocol === "https:";
|
|
1857
|
+
const targetIsHttp = parsed.protocol === "http:";
|
|
1858
|
+
if (pageIsHttps && targetIsHttp) {
|
|
1859
|
+
if (parsed.hostname === window.location.hostname) {
|
|
1860
|
+
const basePath = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/+$/, "");
|
|
1861
|
+
return `${window.location.origin}${basePath}`;
|
|
1862
|
+
}
|
|
1863
|
+
parsed.protocol = "https:";
|
|
1864
|
+
}
|
|
1865
|
+
return parsed.toString().replace(/\/+$/, "");
|
|
1866
|
+
} catch {
|
|
1867
|
+
return raw;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1845
1870
|
var CHANNEL = "conversion-editor";
|
|
1846
1871
|
function IframeCanvas({ url, password, proxyBaseUrl = "", onBridgeReady, onPong }) {
|
|
1847
1872
|
const iframeElRef = useRef(null);
|
|
@@ -1885,11 +1910,12 @@ function IframeCanvas({ url, password, proxyBaseUrl = "", onBridgeReady, onPong
|
|
|
1885
1910
|
window.addEventListener("message", handleMessage);
|
|
1886
1911
|
return () => window.removeEventListener("message", handleMessage);
|
|
1887
1912
|
}, [handleMessage]);
|
|
1913
|
+
const resolvedProxyBaseUrl = normalizeProxyBaseUrl(proxyBaseUrl);
|
|
1888
1914
|
let resolvedUrl;
|
|
1889
1915
|
if (url.toLowerCase() === "test") {
|
|
1890
1916
|
resolvedUrl = "/test";
|
|
1891
1917
|
} else if (url.startsWith("http")) {
|
|
1892
|
-
resolvedUrl = `${
|
|
1918
|
+
resolvedUrl = `${resolvedProxyBaseUrl}/api/conversion-proxy?password=${encodeURIComponent(password || "")}&url=${encodeURIComponent(url)}`;
|
|
1893
1919
|
} else {
|
|
1894
1920
|
resolvedUrl = url;
|
|
1895
1921
|
}
|
|
@@ -4777,6 +4803,7 @@ var VVVEB_CHANNEL = "vvveb-bridge";
|
|
|
4777
4803
|
function PlatformVisualEditorV2({
|
|
4778
4804
|
// channel kept for API compatibility; VvvebJs uses its own internal channel
|
|
4779
4805
|
embeddedGlobalKey = "__CONVERSION_EMBEDDED__",
|
|
4806
|
+
proxyBaseUrl = "",
|
|
4780
4807
|
className = "fixed inset-0 z-[9999] flex flex-col bg-white",
|
|
4781
4808
|
editorClassName = "flex-1 min-h-0",
|
|
4782
4809
|
showHeader = true,
|
|
@@ -4839,6 +4866,10 @@ function PlatformVisualEditorV2({
|
|
|
4839
4866
|
}),
|
|
4840
4867
|
[experiment]
|
|
4841
4868
|
);
|
|
4869
|
+
const editorSrc = useMemo(() => {
|
|
4870
|
+
const safeBaseUrl = normalizeProxyBaseUrl(proxyBaseUrl);
|
|
4871
|
+
return safeBaseUrl ? `${safeBaseUrl}/vvveb-editor` : "/vvveb-editor";
|
|
4872
|
+
}, [proxyBaseUrl]);
|
|
4842
4873
|
useEffect(() => {
|
|
4843
4874
|
if (!editorReady) return;
|
|
4844
4875
|
const key = JSON.stringify(loadPayload);
|
|
@@ -4981,7 +5012,7 @@ function PlatformVisualEditorV2({
|
|
|
4981
5012
|
"iframe",
|
|
4982
5013
|
{
|
|
4983
5014
|
ref: iframeRef,
|
|
4984
|
-
src:
|
|
5015
|
+
src: editorSrc,
|
|
4985
5016
|
className: "w-full h-full border-0",
|
|
4986
5017
|
title: "Vvveb Visual Editor",
|
|
4987
5018
|
allow: "same-origin"
|
package/dist/vite.cjs
CHANGED
|
@@ -995,6 +995,10 @@ var vvvebReady = false;
|
|
|
995
995
|
var currentMode = 'editor';
|
|
996
996
|
var currentDevice = 'desktop';
|
|
997
997
|
var selectedEl = null;
|
|
998
|
+
/** Stable selector fingerprint for resilient selection recovery after DOM churn. */
|
|
999
|
+
var selectedElFingerprint = '';
|
|
1000
|
+
var selectedElRecoverMisses = 0;
|
|
1001
|
+
var MAX_SELECTED_RECOVER_MISSES = 12;
|
|
998
1002
|
var suppressClickUntil = 0;
|
|
999
1003
|
var dragAttachDoc = null;
|
|
1000
1004
|
var currentMainTab = 'design';
|
|
@@ -1037,6 +1041,37 @@ function endSuppressIframeMutationDirty() {
|
|
|
1037
1041
|
suppressIframeMutationDirty = Math.max(0, suppressIframeMutationDirty - 1);
|
|
1038
1042
|
}
|
|
1039
1043
|
|
|
1044
|
+
function recoverSelectedElement(forceDeselectOnMiss) {
|
|
1045
|
+
if (selectedEl && selectedEl.ownerDocument && selectedEl.ownerDocument.contains(selectedEl)) {
|
|
1046
|
+
selectedElRecoverMisses = 0;
|
|
1047
|
+
return selectedEl;
|
|
1048
|
+
}
|
|
1049
|
+
if (!selectedElFingerprint) {
|
|
1050
|
+
if (forceDeselectOnMiss) deselectElement();
|
|
1051
|
+
return null;
|
|
1052
|
+
}
|
|
1053
|
+
var iframe = document.getElementById('iframeId');
|
|
1054
|
+
var doc = iframe && iframe.contentDocument;
|
|
1055
|
+
if (!doc) return null;
|
|
1056
|
+
var recovered = querySelectorResolved(doc, selectedElFingerprint);
|
|
1057
|
+
if (recovered) {
|
|
1058
|
+
beginSuppressIframeMutationDirty();
|
|
1059
|
+
try {
|
|
1060
|
+
selectedEl = recovered;
|
|
1061
|
+
if (selectedEl.classList) selectedEl.classList.add('vve-selected');
|
|
1062
|
+
} finally {
|
|
1063
|
+
endSuppressIframeMutationDirty();
|
|
1064
|
+
}
|
|
1065
|
+
selectedElRecoverMisses = 0;
|
|
1066
|
+
return recovered;
|
|
1067
|
+
}
|
|
1068
|
+
selectedElRecoverMisses += 1;
|
|
1069
|
+
if (forceDeselectOnMiss && selectedElRecoverMisses >= MAX_SELECTED_RECOVER_MISSES) {
|
|
1070
|
+
deselectElement();
|
|
1071
|
+
}
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1040
1075
|
/** Stable stringify of a variation's changesets field (string or array from API). */
|
|
1041
1076
|
function fingerprintChangesetsField(raw) {
|
|
1042
1077
|
if (raw == null) return '[]';
|
|
@@ -2640,6 +2675,8 @@ function selectElement(el) {
|
|
|
2640
2675
|
try {
|
|
2641
2676
|
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} }
|
|
2642
2677
|
selectedEl = el;
|
|
2678
|
+
selectedElFingerprint = buildSelector(el);
|
|
2679
|
+
selectedElRecoverMisses = 0;
|
|
2643
2680
|
try { el.classList.add('vve-selected'); } catch(_) {}
|
|
2644
2681
|
} finally {
|
|
2645
2682
|
endSuppressIframeMutationDirty();
|
|
@@ -2661,6 +2698,8 @@ function deselectElement() {
|
|
|
2661
2698
|
beginSuppressIframeMutationDirty();
|
|
2662
2699
|
try {
|
|
2663
2700
|
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} selectedEl = null; }
|
|
2701
|
+
selectedElFingerprint = '';
|
|
2702
|
+
selectedElRecoverMisses = 0;
|
|
2664
2703
|
} finally {
|
|
2665
2704
|
endSuppressIframeMutationDirty();
|
|
2666
2705
|
}
|
|
@@ -2714,7 +2753,13 @@ function positionSelectionToolbar() {
|
|
|
2714
2753
|
var bar = document.getElementById('selection-floater');
|
|
2715
2754
|
var iframe = document.getElementById('iframeId');
|
|
2716
2755
|
var panel = document.getElementById('iframe-panel');
|
|
2717
|
-
|
|
2756
|
+
var liveSelected = recoverSelectedElement(false);
|
|
2757
|
+
if (!bar || !liveSelected || !iframe || !iframe.contentWindow || !panel) return;
|
|
2758
|
+
if (selectedEl !== liveSelected) {
|
|
2759
|
+
selectedEl = liveSelected;
|
|
2760
|
+
renderRightPanel(liveSelected);
|
|
2761
|
+
syncDomTreeSelection();
|
|
2762
|
+
}
|
|
2718
2763
|
var elR = selectedEl.getBoundingClientRect();
|
|
2719
2764
|
var iframeR = iframe.getBoundingClientRect();
|
|
2720
2765
|
var panelR = panel.getBoundingClientRect();
|
|
@@ -2730,10 +2775,17 @@ function positionSelectionToolbar() {
|
|
|
2730
2775
|
function updateSelectionToolbar() {
|
|
2731
2776
|
var bar = document.getElementById('selection-floater');
|
|
2732
2777
|
if (!bar) return;
|
|
2733
|
-
if (
|
|
2778
|
+
if (currentMode !== 'editor') {
|
|
2779
|
+
bar.style.display = 'none';
|
|
2780
|
+
return;
|
|
2781
|
+
}
|
|
2782
|
+
var liveSelected = recoverSelectedElement(true);
|
|
2783
|
+
if (!liveSelected) {
|
|
2734
2784
|
bar.style.display = 'none';
|
|
2735
2785
|
return;
|
|
2736
2786
|
}
|
|
2787
|
+
if (selectedEl !== liveSelected) selectedEl = liveSelected;
|
|
2788
|
+
selectedElFingerprint = buildSelector(liveSelected);
|
|
2737
2789
|
bar.style.display = 'flex';
|
|
2738
2790
|
requestAnimationFrame(function() { positionSelectionToolbar(); });
|
|
2739
2791
|
}
|
|
@@ -3701,9 +3753,13 @@ function attachChangeObserver() {
|
|
|
3701
3753
|
// Page JS replaced body children; allow structural rows (insert/reorder) to apply again.
|
|
3702
3754
|
appliedStructuralChangesetKeys = {};
|
|
3703
3755
|
}
|
|
3756
|
+
// Host scripts can replace selected nodes every few frames (e.g. A/B tool observers).
|
|
3757
|
+
// Keep selection sticky by re-resolving from fingerprint.
|
|
3758
|
+
recoverSelectedElement(false);
|
|
3704
3759
|
scheduleDomTreeRefresh();
|
|
3705
3760
|
scheduleGranularChangesetReapply();
|
|
3706
3761
|
scheduleConsistencyReconcile();
|
|
3762
|
+
updateSelectionToolbar();
|
|
3707
3763
|
});
|
|
3708
3764
|
changeObserver.observe(doc.body, {
|
|
3709
3765
|
childList: true, subtree: true, attributes: true, characterData: true
|
|
@@ -3948,9 +4004,23 @@ function handleClose() {
|
|
|
3948
4004
|
}
|
|
3949
4005
|
|
|
3950
4006
|
// \u2500\u2500 Keyboard shortcuts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4007
|
+
function isNativeEditableTarget(target) {
|
|
4008
|
+
if (!target || target.nodeType !== 1) return false;
|
|
4009
|
+
if (target.isContentEditable) return true;
|
|
4010
|
+
if (target.closest && target.closest('[contenteditable=""],[contenteditable="true"],[contenteditable="plaintext-only"]')) {
|
|
4011
|
+
return true;
|
|
4012
|
+
}
|
|
4013
|
+
if (!target.tagName) return false;
|
|
4014
|
+
var tag = String(target.tagName).toLowerCase();
|
|
4015
|
+
return tag === 'input' || tag === 'textarea' || tag === 'select';
|
|
4016
|
+
}
|
|
4017
|
+
|
|
3951
4018
|
document.addEventListener('keydown', function(e) {
|
|
4019
|
+
// Keep native browser undo/redo inside text inputs/contenteditable fields.
|
|
4020
|
+
if (isNativeEditableTarget(e.target)) return;
|
|
3952
4021
|
var meta = e.metaKey || e.ctrlKey;
|
|
3953
|
-
|
|
4022
|
+
var k = (e.key || '').toLowerCase();
|
|
4023
|
+
if (meta && !e.shiftKey && k === 'z') {
|
|
3954
4024
|
e.preventDefault();
|
|
3955
4025
|
if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
|
|
3956
4026
|
Vvveb.Undo.undo();
|
|
@@ -3958,7 +4028,7 @@ document.addEventListener('keydown', function(e) {
|
|
|
3958
4028
|
recomputeEditorDirty();
|
|
3959
4029
|
}
|
|
3960
4030
|
}
|
|
3961
|
-
if (meta && e.shiftKey &&
|
|
4031
|
+
if (meta && e.shiftKey && k === 'z') {
|
|
3962
4032
|
e.preventDefault();
|
|
3963
4033
|
if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
|
|
3964
4034
|
Vvveb.Undo.redo();
|
|
@@ -4427,7 +4497,96 @@ function createVisualEditorMiddleware(options) {
|
|
|
4427
4497
|
html = html.replace("</head>", `${popupHideCss}
|
|
4428
4498
|
</head>`);
|
|
4429
4499
|
}
|
|
4430
|
-
|
|
4500
|
+
html = html.replace(
|
|
4501
|
+
/<meta[^>]+http-equiv=["']?\s*(x-frame-options|content-security-policy)\s*["']?[^>]*>/gi,
|
|
4502
|
+
""
|
|
4503
|
+
);
|
|
4504
|
+
html = html.replace(
|
|
4505
|
+
/<meta[^>]+name=["']?\s*content-security-policy\s*["']?[^>]*>/gi,
|
|
4506
|
+
""
|
|
4507
|
+
);
|
|
4508
|
+
const runtimePreflightScript = `<script>(function(){try{
|
|
4509
|
+
var TARGET_ORIGIN=${JSON.stringify(origin)};
|
|
4510
|
+
var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
|
|
4511
|
+
var PROXY_PASSWORD=${JSON.stringify(password)};
|
|
4512
|
+
window.__CONVERSION_EDITOR_ACTIVE__=true;
|
|
4513
|
+
function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
|
|
4514
|
+
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;}}
|
|
4515
|
+
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;}}
|
|
4516
|
+
var nativeAssign=window.location.assign?window.location.assign.bind(window.location):null;
|
|
4517
|
+
var nativeReplace=window.location.replace?window.location.replace.bind(window.location):null;
|
|
4518
|
+
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;}}
|
|
4519
|
+
try{if(nativeAssign){window.location.assign=function(url){return safeNavigate(url,"assign");};}}catch(_){}
|
|
4520
|
+
try{if(nativeReplace){window.location.replace=function(url){return safeNavigate(url,"replace");};}}catch(_){}
|
|
4521
|
+
try{var hrefDesc=Object.getOwnPropertyDescriptor(Location.prototype,"href");if(hrefDesc&&hrefDesc.configurable&&hrefDesc.get&&hrefDesc.set){Object.defineProperty(Location.prototype,"href",{configurable:true,enumerable:hrefDesc.enumerable,get:function(){return hrefDesc.get.call(this);},set:function(v){safeNavigate(v,"assign");}});}}catch(_){}
|
|
4522
|
+
try{
|
|
4523
|
+
var NativeMO=window.MutationObserver;
|
|
4524
|
+
if(NativeMO&&!window.__CONVERSION_MO_GUARDED__){
|
|
4525
|
+
window.__CONVERSION_MO_GUARDED__=true;
|
|
4526
|
+
window.MutationObserver=function(cb){
|
|
4527
|
+
var last=0;
|
|
4528
|
+
var wrapped=function(list,obs){
|
|
4529
|
+
try{
|
|
4530
|
+
if(!window.__CONVERSION_EDITOR_ACTIVE__)return cb(list,obs);
|
|
4531
|
+
var now=Date.now();
|
|
4532
|
+
if(now-last<120)return;
|
|
4533
|
+
last=now;
|
|
4534
|
+
return cb(list,obs);
|
|
4535
|
+
}catch(_){}
|
|
4536
|
+
};
|
|
4537
|
+
return new NativeMO(wrapped);
|
|
4538
|
+
};
|
|
4539
|
+
window.MutationObserver.prototype=NativeMO.prototype;
|
|
4540
|
+
}
|
|
4541
|
+
}catch(_){}
|
|
4542
|
+
}catch(_){}})();</script>`;
|
|
4543
|
+
html = html.replace(/<head([^>]*)>/i, `<head$1>
|
|
4544
|
+
${runtimePreflightScript}
|
|
4545
|
+
`);
|
|
4546
|
+
const runtimeProxyScript = `<script>(function(){try{
|
|
4547
|
+
var TARGET_ORIGIN=${JSON.stringify(origin)};
|
|
4548
|
+
var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
|
|
4549
|
+
var EMPTY_JSON_DATA="data:application/json;charset=utf-8,%7B%7D";
|
|
4550
|
+
function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#")||raw.startsWith("http")||raw.startsWith("//");}
|
|
4551
|
+
function toAbsoluteOriginUrl(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;var abs=new URL(raw,base);if(abs.origin!==TARGET_ORIGIN)return raw;return abs.toString();}catch(_){return raw;}}
|
|
4552
|
+
function resolveUrl(s){try{return new URL(s,window.location.href);}catch(_){return null;}}
|
|
4553
|
+
function isNestedMalformedProxy(u){if(!u)return false;var p=u.pathname||"";if(p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0)return false;return p.indexOf("api/conversion-proxy")!==-1;}
|
|
4554
|
+
function skipNestedProxyNetwork(s){var u=typeof s==="string"?resolveUrl(s):null;return u&&isNestedMalformedProxy(u);}
|
|
4555
|
+
function emptyJsonFetchResponse(){return Promise.resolve(new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}}));}
|
|
4556
|
+
if(window.fetch){
|
|
4557
|
+
var _fetch=window.fetch.bind(window);
|
|
4558
|
+
window.fetch=function(input,init){
|
|
4559
|
+
var afterUrl="";
|
|
4560
|
+
try{
|
|
4561
|
+
var rawUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");
|
|
4562
|
+
if(rawUrl&&skipNestedProxyNetwork(rawUrl))return emptyJsonFetchResponse();
|
|
4563
|
+
if(typeof input==="string"){
|
|
4564
|
+
input=toAbsoluteOriginUrl(input);
|
|
4565
|
+
}else if(input&&input.url){
|
|
4566
|
+
var next=toAbsoluteOriginUrl(input.url);
|
|
4567
|
+
if(next!==input.url){input=new Request(next,input);}
|
|
4568
|
+
}
|
|
4569
|
+
afterUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");
|
|
4570
|
+
if(afterUrl&&skipNestedProxyNetwork(afterUrl))return emptyJsonFetchResponse();
|
|
4571
|
+
}catch(_){}
|
|
4572
|
+
return _fetch(input,init).catch(function(err){
|
|
4573
|
+
try{
|
|
4574
|
+
var u=afterUrl?resolveUrl(afterUrl):null;
|
|
4575
|
+
var sameOrigin=!!(u&&u.origin===TARGET_ORIGIN);
|
|
4576
|
+
var likelyThirdPartyBg=!sameOrigin||!!(u&&/(^|\\/)apps?(\\/|$)|(^|\\/)a(\\/|$)/.test(u.pathname||""));
|
|
4577
|
+
if(window.__CONVERSION_EDITOR_ACTIVE__&&likelyThirdPartyBg){
|
|
4578
|
+
console.warn("[conversion-proxy] suppressed fetch failure",{url:afterUrl,error:err&&err.message?err.message:String(err)});
|
|
4579
|
+
return new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}});
|
|
4580
|
+
}
|
|
4581
|
+
}catch(_){}
|
|
4582
|
+
throw err;
|
|
4583
|
+
});
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
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);};}
|
|
4587
|
+
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);};}
|
|
4588
|
+
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"});};}
|
|
4589
|
+
}catch(_){}})();</script>`;
|
|
4431
4590
|
if (html.includes("</head>")) {
|
|
4432
4591
|
html = html.replace("</head>", `${runtimeProxyScript}
|
|
4433
4592
|
</head>`);
|
package/dist/vite.js
CHANGED
|
@@ -987,6 +987,10 @@ var vvvebReady = false;
|
|
|
987
987
|
var currentMode = 'editor';
|
|
988
988
|
var currentDevice = 'desktop';
|
|
989
989
|
var selectedEl = null;
|
|
990
|
+
/** Stable selector fingerprint for resilient selection recovery after DOM churn. */
|
|
991
|
+
var selectedElFingerprint = '';
|
|
992
|
+
var selectedElRecoverMisses = 0;
|
|
993
|
+
var MAX_SELECTED_RECOVER_MISSES = 12;
|
|
990
994
|
var suppressClickUntil = 0;
|
|
991
995
|
var dragAttachDoc = null;
|
|
992
996
|
var currentMainTab = 'design';
|
|
@@ -1029,6 +1033,37 @@ function endSuppressIframeMutationDirty() {
|
|
|
1029
1033
|
suppressIframeMutationDirty = Math.max(0, suppressIframeMutationDirty - 1);
|
|
1030
1034
|
}
|
|
1031
1035
|
|
|
1036
|
+
function recoverSelectedElement(forceDeselectOnMiss) {
|
|
1037
|
+
if (selectedEl && selectedEl.ownerDocument && selectedEl.ownerDocument.contains(selectedEl)) {
|
|
1038
|
+
selectedElRecoverMisses = 0;
|
|
1039
|
+
return selectedEl;
|
|
1040
|
+
}
|
|
1041
|
+
if (!selectedElFingerprint) {
|
|
1042
|
+
if (forceDeselectOnMiss) deselectElement();
|
|
1043
|
+
return null;
|
|
1044
|
+
}
|
|
1045
|
+
var iframe = document.getElementById('iframeId');
|
|
1046
|
+
var doc = iframe && iframe.contentDocument;
|
|
1047
|
+
if (!doc) return null;
|
|
1048
|
+
var recovered = querySelectorResolved(doc, selectedElFingerprint);
|
|
1049
|
+
if (recovered) {
|
|
1050
|
+
beginSuppressIframeMutationDirty();
|
|
1051
|
+
try {
|
|
1052
|
+
selectedEl = recovered;
|
|
1053
|
+
if (selectedEl.classList) selectedEl.classList.add('vve-selected');
|
|
1054
|
+
} finally {
|
|
1055
|
+
endSuppressIframeMutationDirty();
|
|
1056
|
+
}
|
|
1057
|
+
selectedElRecoverMisses = 0;
|
|
1058
|
+
return recovered;
|
|
1059
|
+
}
|
|
1060
|
+
selectedElRecoverMisses += 1;
|
|
1061
|
+
if (forceDeselectOnMiss && selectedElRecoverMisses >= MAX_SELECTED_RECOVER_MISSES) {
|
|
1062
|
+
deselectElement();
|
|
1063
|
+
}
|
|
1064
|
+
return null;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1032
1067
|
/** Stable stringify of a variation's changesets field (string or array from API). */
|
|
1033
1068
|
function fingerprintChangesetsField(raw) {
|
|
1034
1069
|
if (raw == null) return '[]';
|
|
@@ -2632,6 +2667,8 @@ function selectElement(el) {
|
|
|
2632
2667
|
try {
|
|
2633
2668
|
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} }
|
|
2634
2669
|
selectedEl = el;
|
|
2670
|
+
selectedElFingerprint = buildSelector(el);
|
|
2671
|
+
selectedElRecoverMisses = 0;
|
|
2635
2672
|
try { el.classList.add('vve-selected'); } catch(_) {}
|
|
2636
2673
|
} finally {
|
|
2637
2674
|
endSuppressIframeMutationDirty();
|
|
@@ -2653,6 +2690,8 @@ function deselectElement() {
|
|
|
2653
2690
|
beginSuppressIframeMutationDirty();
|
|
2654
2691
|
try {
|
|
2655
2692
|
if (selectedEl) { try { selectedEl.classList.remove('vve-selected'); } catch(_) {} selectedEl = null; }
|
|
2693
|
+
selectedElFingerprint = '';
|
|
2694
|
+
selectedElRecoverMisses = 0;
|
|
2656
2695
|
} finally {
|
|
2657
2696
|
endSuppressIframeMutationDirty();
|
|
2658
2697
|
}
|
|
@@ -2706,7 +2745,13 @@ function positionSelectionToolbar() {
|
|
|
2706
2745
|
var bar = document.getElementById('selection-floater');
|
|
2707
2746
|
var iframe = document.getElementById('iframeId');
|
|
2708
2747
|
var panel = document.getElementById('iframe-panel');
|
|
2709
|
-
|
|
2748
|
+
var liveSelected = recoverSelectedElement(false);
|
|
2749
|
+
if (!bar || !liveSelected || !iframe || !iframe.contentWindow || !panel) return;
|
|
2750
|
+
if (selectedEl !== liveSelected) {
|
|
2751
|
+
selectedEl = liveSelected;
|
|
2752
|
+
renderRightPanel(liveSelected);
|
|
2753
|
+
syncDomTreeSelection();
|
|
2754
|
+
}
|
|
2710
2755
|
var elR = selectedEl.getBoundingClientRect();
|
|
2711
2756
|
var iframeR = iframe.getBoundingClientRect();
|
|
2712
2757
|
var panelR = panel.getBoundingClientRect();
|
|
@@ -2722,10 +2767,17 @@ function positionSelectionToolbar() {
|
|
|
2722
2767
|
function updateSelectionToolbar() {
|
|
2723
2768
|
var bar = document.getElementById('selection-floater');
|
|
2724
2769
|
if (!bar) return;
|
|
2725
|
-
if (
|
|
2770
|
+
if (currentMode !== 'editor') {
|
|
2771
|
+
bar.style.display = 'none';
|
|
2772
|
+
return;
|
|
2773
|
+
}
|
|
2774
|
+
var liveSelected = recoverSelectedElement(true);
|
|
2775
|
+
if (!liveSelected) {
|
|
2726
2776
|
bar.style.display = 'none';
|
|
2727
2777
|
return;
|
|
2728
2778
|
}
|
|
2779
|
+
if (selectedEl !== liveSelected) selectedEl = liveSelected;
|
|
2780
|
+
selectedElFingerprint = buildSelector(liveSelected);
|
|
2729
2781
|
bar.style.display = 'flex';
|
|
2730
2782
|
requestAnimationFrame(function() { positionSelectionToolbar(); });
|
|
2731
2783
|
}
|
|
@@ -3693,9 +3745,13 @@ function attachChangeObserver() {
|
|
|
3693
3745
|
// Page JS replaced body children; allow structural rows (insert/reorder) to apply again.
|
|
3694
3746
|
appliedStructuralChangesetKeys = {};
|
|
3695
3747
|
}
|
|
3748
|
+
// Host scripts can replace selected nodes every few frames (e.g. A/B tool observers).
|
|
3749
|
+
// Keep selection sticky by re-resolving from fingerprint.
|
|
3750
|
+
recoverSelectedElement(false);
|
|
3696
3751
|
scheduleDomTreeRefresh();
|
|
3697
3752
|
scheduleGranularChangesetReapply();
|
|
3698
3753
|
scheduleConsistencyReconcile();
|
|
3754
|
+
updateSelectionToolbar();
|
|
3699
3755
|
});
|
|
3700
3756
|
changeObserver.observe(doc.body, {
|
|
3701
3757
|
childList: true, subtree: true, attributes: true, characterData: true
|
|
@@ -3940,9 +3996,23 @@ function handleClose() {
|
|
|
3940
3996
|
}
|
|
3941
3997
|
|
|
3942
3998
|
// \u2500\u2500 Keyboard shortcuts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3999
|
+
function isNativeEditableTarget(target) {
|
|
4000
|
+
if (!target || target.nodeType !== 1) return false;
|
|
4001
|
+
if (target.isContentEditable) return true;
|
|
4002
|
+
if (target.closest && target.closest('[contenteditable=""],[contenteditable="true"],[contenteditable="plaintext-only"]')) {
|
|
4003
|
+
return true;
|
|
4004
|
+
}
|
|
4005
|
+
if (!target.tagName) return false;
|
|
4006
|
+
var tag = String(target.tagName).toLowerCase();
|
|
4007
|
+
return tag === 'input' || tag === 'textarea' || tag === 'select';
|
|
4008
|
+
}
|
|
4009
|
+
|
|
3943
4010
|
document.addEventListener('keydown', function(e) {
|
|
4011
|
+
// Keep native browser undo/redo inside text inputs/contenteditable fields.
|
|
4012
|
+
if (isNativeEditableTarget(e.target)) return;
|
|
3944
4013
|
var meta = e.metaKey || e.ctrlKey;
|
|
3945
|
-
|
|
4014
|
+
var k = (e.key || '').toLowerCase();
|
|
4015
|
+
if (meta && !e.shiftKey && k === 'z') {
|
|
3946
4016
|
e.preventDefault();
|
|
3947
4017
|
if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
|
|
3948
4018
|
Vvveb.Undo.undo();
|
|
@@ -3950,7 +4020,7 @@ document.addEventListener('keydown', function(e) {
|
|
|
3950
4020
|
recomputeEditorDirty();
|
|
3951
4021
|
}
|
|
3952
4022
|
}
|
|
3953
|
-
if (meta && e.shiftKey &&
|
|
4023
|
+
if (meta && e.shiftKey && k === 'z') {
|
|
3954
4024
|
e.preventDefault();
|
|
3955
4025
|
if (typeof Vvveb !== 'undefined' && Vvveb.Undo) {
|
|
3956
4026
|
Vvveb.Undo.redo();
|
|
@@ -4419,7 +4489,96 @@ function createVisualEditorMiddleware(options) {
|
|
|
4419
4489
|
html = html.replace("</head>", `${popupHideCss}
|
|
4420
4490
|
</head>`);
|
|
4421
4491
|
}
|
|
4422
|
-
|
|
4492
|
+
html = html.replace(
|
|
4493
|
+
/<meta[^>]+http-equiv=["']?\s*(x-frame-options|content-security-policy)\s*["']?[^>]*>/gi,
|
|
4494
|
+
""
|
|
4495
|
+
);
|
|
4496
|
+
html = html.replace(
|
|
4497
|
+
/<meta[^>]+name=["']?\s*content-security-policy\s*["']?[^>]*>/gi,
|
|
4498
|
+
""
|
|
4499
|
+
);
|
|
4500
|
+
const runtimePreflightScript = `<script>(function(){try{
|
|
4501
|
+
var TARGET_ORIGIN=${JSON.stringify(origin)};
|
|
4502
|
+
var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
|
|
4503
|
+
var PROXY_PASSWORD=${JSON.stringify(password)};
|
|
4504
|
+
window.__CONVERSION_EDITOR_ACTIVE__=true;
|
|
4505
|
+
function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
|
|
4506
|
+
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;}}
|
|
4507
|
+
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;}}
|
|
4508
|
+
var nativeAssign=window.location.assign?window.location.assign.bind(window.location):null;
|
|
4509
|
+
var nativeReplace=window.location.replace?window.location.replace.bind(window.location):null;
|
|
4510
|
+
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;}}
|
|
4511
|
+
try{if(nativeAssign){window.location.assign=function(url){return safeNavigate(url,"assign");};}}catch(_){}
|
|
4512
|
+
try{if(nativeReplace){window.location.replace=function(url){return safeNavigate(url,"replace");};}}catch(_){}
|
|
4513
|
+
try{var hrefDesc=Object.getOwnPropertyDescriptor(Location.prototype,"href");if(hrefDesc&&hrefDesc.configurable&&hrefDesc.get&&hrefDesc.set){Object.defineProperty(Location.prototype,"href",{configurable:true,enumerable:hrefDesc.enumerable,get:function(){return hrefDesc.get.call(this);},set:function(v){safeNavigate(v,"assign");}});}}catch(_){}
|
|
4514
|
+
try{
|
|
4515
|
+
var NativeMO=window.MutationObserver;
|
|
4516
|
+
if(NativeMO&&!window.__CONVERSION_MO_GUARDED__){
|
|
4517
|
+
window.__CONVERSION_MO_GUARDED__=true;
|
|
4518
|
+
window.MutationObserver=function(cb){
|
|
4519
|
+
var last=0;
|
|
4520
|
+
var wrapped=function(list,obs){
|
|
4521
|
+
try{
|
|
4522
|
+
if(!window.__CONVERSION_EDITOR_ACTIVE__)return cb(list,obs);
|
|
4523
|
+
var now=Date.now();
|
|
4524
|
+
if(now-last<120)return;
|
|
4525
|
+
last=now;
|
|
4526
|
+
return cb(list,obs);
|
|
4527
|
+
}catch(_){}
|
|
4528
|
+
};
|
|
4529
|
+
return new NativeMO(wrapped);
|
|
4530
|
+
};
|
|
4531
|
+
window.MutationObserver.prototype=NativeMO.prototype;
|
|
4532
|
+
}
|
|
4533
|
+
}catch(_){}
|
|
4534
|
+
}catch(_){}})();</script>`;
|
|
4535
|
+
html = html.replace(/<head([^>]*)>/i, `<head$1>
|
|
4536
|
+
${runtimePreflightScript}
|
|
4537
|
+
`);
|
|
4538
|
+
const runtimeProxyScript = `<script>(function(){try{
|
|
4539
|
+
var TARGET_ORIGIN=${JSON.stringify(origin)};
|
|
4540
|
+
var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
|
|
4541
|
+
var EMPTY_JSON_DATA="data:application/json;charset=utf-8,%7B%7D";
|
|
4542
|
+
function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#")||raw.startsWith("http")||raw.startsWith("//");}
|
|
4543
|
+
function toAbsoluteOriginUrl(raw){if(isSkippable(raw))return raw;try{var base=raw.startsWith("/")?TARGET_ORIGIN:TARGET_PAGE_URL;var abs=new URL(raw,base);if(abs.origin!==TARGET_ORIGIN)return raw;return abs.toString();}catch(_){return raw;}}
|
|
4544
|
+
function resolveUrl(s){try{return new URL(s,window.location.href);}catch(_){return null;}}
|
|
4545
|
+
function isNestedMalformedProxy(u){if(!u)return false;var p=u.pathname||"";if(p==="/api/conversion-proxy"||p.indexOf("/api/conversion-proxy/")===0)return false;return p.indexOf("api/conversion-proxy")!==-1;}
|
|
4546
|
+
function skipNestedProxyNetwork(s){var u=typeof s==="string"?resolveUrl(s):null;return u&&isNestedMalformedProxy(u);}
|
|
4547
|
+
function emptyJsonFetchResponse(){return Promise.resolve(new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}}));}
|
|
4548
|
+
if(window.fetch){
|
|
4549
|
+
var _fetch=window.fetch.bind(window);
|
|
4550
|
+
window.fetch=function(input,init){
|
|
4551
|
+
var afterUrl="";
|
|
4552
|
+
try{
|
|
4553
|
+
var rawUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");
|
|
4554
|
+
if(rawUrl&&skipNestedProxyNetwork(rawUrl))return emptyJsonFetchResponse();
|
|
4555
|
+
if(typeof input==="string"){
|
|
4556
|
+
input=toAbsoluteOriginUrl(input);
|
|
4557
|
+
}else if(input&&input.url){
|
|
4558
|
+
var next=toAbsoluteOriginUrl(input.url);
|
|
4559
|
+
if(next!==input.url){input=new Request(next,input);}
|
|
4560
|
+
}
|
|
4561
|
+
afterUrl=typeof input==="string"?input:(input&&input.url?String(input.url):"");
|
|
4562
|
+
if(afterUrl&&skipNestedProxyNetwork(afterUrl))return emptyJsonFetchResponse();
|
|
4563
|
+
}catch(_){}
|
|
4564
|
+
return _fetch(input,init).catch(function(err){
|
|
4565
|
+
try{
|
|
4566
|
+
var u=afterUrl?resolveUrl(afterUrl):null;
|
|
4567
|
+
var sameOrigin=!!(u&&u.origin===TARGET_ORIGIN);
|
|
4568
|
+
var likelyThirdPartyBg=!sameOrigin||!!(u&&/(^|\\/)apps?(\\/|$)|(^|\\/)a(\\/|$)/.test(u.pathname||""));
|
|
4569
|
+
if(window.__CONVERSION_EDITOR_ACTIVE__&&likelyThirdPartyBg){
|
|
4570
|
+
console.warn("[conversion-proxy] suppressed fetch failure",{url:afterUrl,error:err&&err.message?err.message:String(err)});
|
|
4571
|
+
return new Response("{}",{status:200,headers:{"Content-Type":"application/json; charset=utf-8"}});
|
|
4572
|
+
}
|
|
4573
|
+
}catch(_){}
|
|
4574
|
+
throw err;
|
|
4575
|
+
});
|
|
4576
|
+
};
|
|
4577
|
+
}
|
|
4578
|
+
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);};}
|
|
4579
|
+
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);};}
|
|
4580
|
+
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"});};}
|
|
4581
|
+
}catch(_){}})();</script>`;
|
|
4423
4582
|
if (html.includes("</head>")) {
|
|
4424
4583
|
html = html.replace("</head>", `${runtimeProxyScript}
|
|
4425
4584
|
</head>`);
|