@accelerated-agency/visual-editor 0.3.2 → 0.3.4
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 +14 -55
- package/dist/vite.cjs +195 -22
- package/dist/vite.js +195 -22
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -4807,6 +4807,7 @@ function PlatformVisualEditorV2({
|
|
|
4807
4807
|
// channel kept for API compatibility; VvvebJs uses its own internal channel
|
|
4808
4808
|
embeddedGlobalKey = "__CONVERSION_EMBEDDED__",
|
|
4809
4809
|
proxyBaseUrl = "",
|
|
4810
|
+
strictObserverFreeze = false,
|
|
4810
4811
|
className = "fixed inset-0 z-[9999] flex flex-col bg-white",
|
|
4811
4812
|
editorClassName = "flex-1 min-h-0",
|
|
4812
4813
|
showHeader = true,
|
|
@@ -4867,9 +4868,10 @@ function PlatformVisualEditorV2({
|
|
|
4867
4868
|
status: experiment?.status,
|
|
4868
4869
|
pageUrl: experiment?.pageUrl,
|
|
4869
4870
|
editorPassword: experiment?.editorPassword,
|
|
4871
|
+
strictObserverFreeze: !!strictObserverFreeze,
|
|
4870
4872
|
variations: experiment?.variations ?? []
|
|
4871
4873
|
}),
|
|
4872
|
-
[experiment]
|
|
4874
|
+
[experiment, strictObserverFreeze]
|
|
4873
4875
|
);
|
|
4874
4876
|
console.log("loadPayload", loadPayload);
|
|
4875
4877
|
const editorSrc = useMemo(() => {
|
|
@@ -4960,7 +4962,7 @@ function PlatformVisualEditorV2({
|
|
|
4960
4962
|
window.addEventListener("message", listener);
|
|
4961
4963
|
return () => window.removeEventListener("message", listener);
|
|
4962
4964
|
}, [handleMessage]);
|
|
4963
|
-
|
|
4965
|
+
useCallback(
|
|
4964
4966
|
(tab) => onTabChange?.(tab),
|
|
4965
4967
|
[onTabChange]
|
|
4966
4968
|
);
|
|
@@ -4972,59 +4974,16 @@ function PlatformVisualEditorV2({
|
|
|
4972
4974
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-red-400", children: error })
|
|
4973
4975
|
] }) });
|
|
4974
4976
|
}
|
|
4975
|
-
return /* @__PURE__ */
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
}
|
|
4984
|
-
|
|
4985
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 min-w-0", children: [
|
|
4986
|
-
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold tracking-wide px-1.5 py-0.5 rounded bg-indigo-50 text-indigo-600 border border-indigo-100 shrink-0", children: "V2" }),
|
|
4987
|
-
/* @__PURE__ */ jsx("span", { className: "font-semibold text-sm text-slate-800 truncate", children: title ?? experiment?.name ?? "Visual Editor" }),
|
|
4988
|
-
status ? /* @__PURE__ */ jsx("span", { className: "hidden sm:inline-flex text-[10px] px-2 py-0.5 rounded-full border border-slate-200 text-slate-500 font-medium bg-slate-50 capitalize", children: status }) : null,
|
|
4989
|
-
dirty ? /* @__PURE__ */ jsx(
|
|
4990
|
-
"span",
|
|
4991
|
-
{
|
|
4992
|
-
className: "w-1.5 h-1.5 rounded-full bg-amber-400 shrink-0",
|
|
4993
|
-
title: "Unsaved changes"
|
|
4994
|
-
}
|
|
4995
|
-
) : null
|
|
4996
|
-
] }),
|
|
4997
|
-
tabs.length > 0 ? /* @__PURE__ */ jsx("div", { className: "hidden md:flex items-center gap-1 absolute left-1/2 -translate-x-1/2", children: tabs.map((tab) => /* @__PURE__ */ jsx(
|
|
4998
|
-
"button",
|
|
4999
|
-
{
|
|
5000
|
-
type: "button",
|
|
5001
|
-
onClick: () => onTabClick(tab),
|
|
5002
|
-
className: `text-[11px] font-semibold px-3 py-1 rounded-full border transition-all ${tab.label === activeTab ? "bg-indigo-600 text-white border-indigo-600 shadow-sm" : "bg-white text-slate-500 border-slate-200 hover:border-slate-300 hover:text-slate-700"}`,
|
|
5003
|
-
children: tab.label
|
|
5004
|
-
},
|
|
5005
|
-
tab.hash
|
|
5006
|
-
)) }) : null,
|
|
5007
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: showCloseButton ? /* @__PURE__ */ jsx(
|
|
5008
|
-
"button",
|
|
5009
|
-
{
|
|
5010
|
-
type: "button",
|
|
5011
|
-
className: "text-[11px] font-medium px-3 py-1.5 rounded-md border border-slate-200 text-slate-600 hover:bg-slate-50 hover:border-slate-300 transition-all",
|
|
5012
|
-
onClick: () => sendToVvveb("close-editor", {}),
|
|
5013
|
-
children: closeLabel
|
|
5014
|
-
}
|
|
5015
|
-
) : null })
|
|
5016
|
-
] }) : null,
|
|
5017
|
-
/* @__PURE__ */ jsx("div", { className: editorClassName, children: /* @__PURE__ */ jsx(
|
|
5018
|
-
"iframe",
|
|
5019
|
-
{
|
|
5020
|
-
ref: iframeRef,
|
|
5021
|
-
src: editorSrc,
|
|
5022
|
-
className: "w-full h-full border-0",
|
|
5023
|
-
title: "Vvveb Visual Editor",
|
|
5024
|
-
allow: "same-origin"
|
|
5025
|
-
}
|
|
5026
|
-
) })
|
|
5027
|
-
] });
|
|
4977
|
+
return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx("div", { className: editorClassName, children: /* @__PURE__ */ jsx(
|
|
4978
|
+
"iframe",
|
|
4979
|
+
{
|
|
4980
|
+
ref: iframeRef,
|
|
4981
|
+
src: editorSrc,
|
|
4982
|
+
className: "w-full h-full border-0",
|
|
4983
|
+
title: "Vvveb Visual Editor",
|
|
4984
|
+
allow: "same-origin"
|
|
4985
|
+
}
|
|
4986
|
+
) }) });
|
|
5028
4987
|
}
|
|
5029
4988
|
var AI_EDITOR_CHANNEL = "ve-ai-editor";
|
|
5030
4989
|
var AI_VIEWPORT_WIDTH = {
|
package/dist/vite.cjs
CHANGED
|
@@ -11,8 +11,20 @@ var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
|
11
11
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
12
12
|
|
|
13
13
|
// src/visualEditorProxyPlugin.ts
|
|
14
|
-
var
|
|
15
|
-
var
|
|
14
|
+
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
|
+
var iframeAlwaysShowCssGuardScript = `<script id="__ce_force_show_guard">(function(){try{
|
|
16
|
+
function ensureForceShowStyleLast(){
|
|
17
|
+
var style=document.getElementById("__ce_force_show");
|
|
18
|
+
if(!style||!document.head)return;
|
|
19
|
+
if(document.head.lastElementChild!==style){
|
|
20
|
+
document.head.appendChild(style);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
ensureForceShowStyleLast();
|
|
24
|
+
var mo=new MutationObserver(function(){ensureForceShowStyleLast();});
|
|
25
|
+
if(document.head){mo.observe(document.head,{childList:true});}
|
|
26
|
+
window.addEventListener("beforeunload",function(){try{mo.disconnect();}catch(_){}});}
|
|
27
|
+
catch(_){}})();</script>`;
|
|
16
28
|
var AI_SYSTEM_PROMPT = `You are a CRO expert. Return JSON only with: hypothesis, mutations, summary. Keep 2-6 precise mutations and only valid selectors from snapshot.`;
|
|
17
29
|
var BRIDGE_SCRIPT = `(function(){if(window.__CONVERSION_BRIDGE_LOADED__)return;window.__CONVERSION_BRIDGE_LOADED__=true;var CHANNEL='conversion-editor';var highlightEl=null;function send(payload){window.parent.postMessage({channel:CHANNEL,payload:payload},'*');}function qs(selector){try{return document.querySelector(selector);}catch(_){return null;}}function getSelector(el){if(!el||el.nodeType!==1)return'';if(el.id)return'#'+CSS.escape(el.id);var parts=[];var current=el;var depth=0;while(current&¤t.nodeType===1&&depth<5){var part=current.tagName.toLowerCase();if(current.classList&¤t.classList.length){part+='.'+Array.from(current.classList).slice(0,2).map(function(c){return CSS.escape(c);}).join('.');}var parent=current.parentElement;if(parent){var siblings=Array.from(parent.children).filter(function(s){return s.tagName===current.tagName;});if(siblings.length>1)part+=':nth-of-type('+(siblings.indexOf(current)+1)+')';}parts.unshift(part);current=parent;depth+=1;}return parts.join(' > ');}function payloadOf(el){var rect=el.getBoundingClientRect();var style=window.getComputedStyle(el);return{selector:getSelector(el),tagName:el.tagName.toLowerCase(),textContent:(el.textContent||'').trim().slice(0,500),computedStyles:{color:style.color,backgroundColor:style.backgroundColor,fontSize:style.fontSize,fontWeight:style.fontWeight,lineHeight:style.lineHeight,display:style.display},rect:{top:rect.top,left:rect.left,width:rect.width,height:rect.height}};}function applyMutation(m){var el=qs(m.selector);if(!el)return;switch(m.action){case'setStyle':if(m.property)el.style[m.property]=m.value;break;case'setText':el.textContent=m.value;break;case'setHTML':el.innerHTML=m.value;break;case'setAttribute':if(m.property)el.setAttribute(m.property,m.value);break;case'hide':el.style.display='none';break;case'show':el.style.display='';break;case'insertHTML':if(m.position)el.insertAdjacentHTML(m.position,m.value||'');break;case'insertSection':if(m.position)el.insertAdjacentHTML(m.position,m.sectionHtml||m.value||'');break;case'reorderElement':if(!m.targetSelector)break;var target=qs(m.targetSelector);if(!target||!target.parentNode||!el.parentNode)break;if(m.insertPosition==='before')target.parentNode.insertBefore(el,target);else target.parentNode.insertBefore(el,target.nextSibling);break;default:break;}}function revertMutation(m){var el=qs(m.selector);if(!el)return;switch(m.action){case'setStyle':if(m.property)el.style[m.property]=m.previous||'';break;case'setText':el.textContent=m.previous||'';break;case'setHTML':el.innerHTML=m.previous||'';break;case'setAttribute':if(m.property){if(m.previous!=null)el.setAttribute(m.property,m.previous);else el.removeAttribute(m.property);}break;case'hide':case'show':el.style.display=m.previous||'';break;default:break;}}function captureSnapshot(){var elements=Array.from(document.querySelectorAll('h1,h2,h3,p,button,a,input,select,img,section,div')).slice(0,700).map(function(el){var rect=el.getBoundingClientRect();var cs=window.getComputedStyle(el);return{selector:getSelector(el),parentSelector:el.parentElement?getSelector(el.parentElement):null,tagName:el.tagName.toLowerCase(),textContent:(el.textContent||'').trim().slice(0,300),attributes:Array.from(el.attributes||[]).reduce(function(acc,a){acc[a.name]=a.value;return acc;},{}),computedStyles:{color:cs.color,backgroundColor:cs.backgroundColor,fontSize:cs.fontSize,fontWeight:cs.fontWeight,display:cs.display},childrenCount:el.children?el.children.length:0,aboveFold:rect.top<window.innerHeight,depth:(function(){var d=0,p=el.parentElement;while(p&&d<25){d++;p=p.parentElement;}return d;})()};});send({type:'snapshotCaptured',snapshot:{url:window.location.href,title:document.title,viewport:{width:window.innerWidth,height:window.innerHeight},elements:elements}});}function captureTree(){function toNode(el,depth){var rect=el.getBoundingClientRect();var tag=el.tagName.toLowerCase();var type='generic';if(tag==='section')type='section';else if(tag==='img')type='image';else if(tag==='a')type='link';else if(tag==='button')type='button';else if(tag==='form')type='form';else if(['p','h1','h2','h3','h4','h5','h6','span'].indexOf(tag)>=0)type='text';else if(['div','main','article','aside','header','footer','nav'].indexOf(tag)>=0)type='container';var children=Array.from(el.children||[]).slice(0,50).map(function(c){return toNode(c,depth+1);});return{id:getSelector(el),selector:getSelector(el),tagName:tag,label:(el.getAttribute('aria-label')||el.getAttribute('id')||el.className||tag).toString().slice(0,80),type:type,children:children,depth:depth,isAboveFold:rect.top<window.innerHeight,rect:{top:rect.top,left:rect.left,width:rect.width,height:rect.height}};}send({type:'pageTreeCaptured',tree:[toNode(document.body,0)]});}window.addEventListener('message',function(e){var msg=e.data;if(!msg||msg.channel!==CHANNEL||!msg.payload)return;var p=msg.payload;switch(p.type){case'ping':send({type:'pong'});break;case'applyMutation':applyMutation(p.mutation);break;case'applyMutationBatch':(p.mutations||[]).forEach(applyMutation);break;case'revert':revertMutation(p.mutation);break;case'clearAllMutations':window.location.reload();break;case'captureSnapshot':captureSnapshot();break;case'validateSelectors':send({type:'selectorsValidated',results:(p.selectors||[]).map(function(s){var el=qs(s);return{selector:s,found:!!el,tagName:el?el.tagName.toLowerCase():null};})});break;case'scrollToElement':case'selectElement':var target=qs(p.selector);if(target){target.scrollIntoView({behavior:'smooth',block:'center'});send({type:'elementSelected',element:payloadOf(target)});}break;case'hoverElement':var h=qs(p.selector);if(h){if(highlightEl)highlightEl.style.outline='';h.style.outline='2px solid #3b82f6';highlightEl=h;}break;case'capturePageTree':captureTree();break;default:break;}});document.addEventListener('click',function(e){var el=e.target;if(!(el instanceof Element))return;send({type:'elementSelected',element:payloadOf(el)});},true);send({type:'bridgeReady'});})();`;
|
|
18
30
|
function buildVvvebEditorHtml() {
|
|
@@ -254,7 +266,7 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
254
266
|
/* \u2500\u2500 DOM tree (Elements tab) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
255
267
|
.dt-tree{font-size:11px;padding:0px 0 0px 20px;user-select:none}
|
|
256
268
|
.dt-row{
|
|
257
|
-
display:flex;align-items:center;gap:2px;min-height:26px;padding:2px 8px 2px 4px;
|
|
269
|
+
width:fit-content;display:flex;align-items:center;gap:2px;min-height:26px;padding:2px 8px 2px 4px;
|
|
258
270
|
cursor:pointer;color:var(--text-2);border-radius:4px;margin:0 4px
|
|
259
271
|
}
|
|
260
272
|
.dt-row:hover{background:var(--bg-hover);color:var(--text)}
|
|
@@ -680,10 +692,10 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
680
692
|
<button class="tb-dk-btn" id="btn-mode-nav" onclick="setMode('navigate')" title="Navigate mode \u2014 interact with page normally"><i class="bi bi-magic"></i></button>
|
|
681
693
|
<button class="tb-dk-btn" title="Comments"><i class="bi bi-chat-dots"></i></button>
|
|
682
694
|
</div>
|
|
683
|
-
<!-- btn-close: hidden visually, kept for JS event listener -->
|
|
684
|
-
<button id="btn-close" style="display:none" title="Close editor"></button>
|
|
685
695
|
<button class="tb-sim-btn" id="btn-simulate" onclick="simulateExperiment()"><i class="bi bi-lightning-charge-fill"></i> Simulate</button>
|
|
686
|
-
<button class="tb-fin-btn" id="btn-save">
|
|
696
|
+
<button class="tb-fin-btn" id="btn-save">Save Changes</button>
|
|
697
|
+
<!-- btn-close: kept for JS event listener -->
|
|
698
|
+
<button class="tb-fin-btn" id="btn-close">Close</button>
|
|
687
699
|
</div>
|
|
688
700
|
|
|
689
701
|
<!-- url-bar: hidden, kept for JS compatibility -->
|
|
@@ -1102,6 +1114,8 @@ var iframeContentNavGen = 0;
|
|
|
1102
1114
|
var iframeContentApplyTimer = null;
|
|
1103
1115
|
var iframeEarlyGranularPrimedForGen = null;
|
|
1104
1116
|
var iframeEarlySyncPrimedForGen = null;
|
|
1117
|
+
var iframeEarlyDomSignature = '';
|
|
1118
|
+
var iframeEarlyDomSignatureNavGen = 0;
|
|
1105
1119
|
/** insert/reorder entries are applied from early granular + full apply \u2014 skip exact duplicates per iframe nav */
|
|
1106
1120
|
var appliedStructuralChangesetKeys = {};
|
|
1107
1121
|
var isDirty = false;
|
|
@@ -1113,6 +1127,7 @@ var selectedEl = null;
|
|
|
1113
1127
|
var selectedElFingerprint = '';
|
|
1114
1128
|
var selectedElRecoverMisses = 0;
|
|
1115
1129
|
var MAX_SELECTED_RECOVER_MISSES = 12;
|
|
1130
|
+
var hoveredTreeEl = null;
|
|
1116
1131
|
var isDeselectingSelection = false;
|
|
1117
1132
|
var suppressClickUntil = 0;
|
|
1118
1133
|
var dragAttachDoc = null;
|
|
@@ -1126,6 +1141,7 @@ var iframeSyncAttempts = 0;
|
|
|
1126
1141
|
var selectionScrollWin = null;
|
|
1127
1142
|
var selectionResizeBound = false;
|
|
1128
1143
|
var clickAttachDoc = null;
|
|
1144
|
+
var hoverAttachDoc = null;
|
|
1129
1145
|
var changeObserver = null;
|
|
1130
1146
|
var changeObserverDoc = null;
|
|
1131
1147
|
/** Incremented while applying changesets / selection chrome so MutationObserver does not mark dirty */
|
|
@@ -1338,6 +1354,7 @@ function setMode(mode) {
|
|
|
1338
1354
|
document.getElementById('btn-mode-editor').classList.toggle('active', mode === 'editor');
|
|
1339
1355
|
document.getElementById('btn-mode-nav').classList.toggle('active', mode === 'navigate');
|
|
1340
1356
|
if (mode === 'navigate') {
|
|
1357
|
+
clearTreeHoverHighlight();
|
|
1341
1358
|
setDragHandleActive(false);
|
|
1342
1359
|
deselectElement();
|
|
1343
1360
|
} else if (mode === 'editor') {
|
|
@@ -1721,7 +1738,7 @@ function softReloadEditorIframe() {
|
|
|
1721
1738
|
resetIframeBindings();
|
|
1722
1739
|
setIframePageLoadingUi(true);
|
|
1723
1740
|
iframe.src = appendIframeReloadBust(src);
|
|
1724
|
-
startIframeContentApplyWatcher(navGen);
|
|
1741
|
+
startIframeContentApplyWatcher(navGen, iframe.contentDocument || null);
|
|
1725
1742
|
scheduleDomTreeRefresh();
|
|
1726
1743
|
}
|
|
1727
1744
|
|
|
@@ -2049,7 +2066,8 @@ function handleLoadExperiment(data) {
|
|
|
2049
2066
|
return;
|
|
2050
2067
|
}
|
|
2051
2068
|
var proxyUrl = '/api/conversion-proxy?password=' + encodeURIComponent(data.editorPassword || '') +
|
|
2052
|
-
'&url=' + encodeURIComponent(pageUrl)
|
|
2069
|
+
'&url=' + encodeURIComponent(pageUrl) +
|
|
2070
|
+
'&strictObserverFreeze=' + encodeURIComponent(data && data.strictObserverFreeze ? '1' : '0');
|
|
2053
2071
|
|
|
2054
2072
|
// Parent often re-posts load-experiment when React re-renders (new object identity) or
|
|
2055
2073
|
// after mutations-changed. Reloading the iframe again wipes variant changesets mid-session.
|
|
@@ -2200,6 +2218,27 @@ function bodyHasFirstPaintChild(body) {
|
|
|
2200
2218
|
return false;
|
|
2201
2219
|
}
|
|
2202
2220
|
|
|
2221
|
+
function computeIframeDomSignature(doc) {
|
|
2222
|
+
if (!doc || !doc.body) return '';
|
|
2223
|
+
var body = doc.body;
|
|
2224
|
+
var tags = [];
|
|
2225
|
+
var walker = null;
|
|
2226
|
+
try {
|
|
2227
|
+
walker = doc.createTreeWalker(body, NodeFilter.SHOW_ELEMENT, null);
|
|
2228
|
+
} catch(_) {
|
|
2229
|
+
walker = null;
|
|
2230
|
+
}
|
|
2231
|
+
var count = 0;
|
|
2232
|
+
if (walker) {
|
|
2233
|
+
while (walker.nextNode() && count < 400) {
|
|
2234
|
+
var node = walker.currentNode;
|
|
2235
|
+
tags.push((node && node.tagName) ? String(node.tagName).toLowerCase() : '');
|
|
2236
|
+
count += 1;
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
return String(body.children ? body.children.length : 0) + '|' + String(count) + '|' + tags.join(',');
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2203
2242
|
/** True when at least one granular changeset selector already matches (nested content painted). */
|
|
2204
2243
|
function granularAnySelectorMatches(doc, cs) {
|
|
2205
2244
|
if (!doc || !cs || !cs.length) return false;
|
|
@@ -2354,6 +2393,7 @@ function resetIframeBindings() {
|
|
|
2354
2393
|
appliedStructuralChangesetKeys = {};
|
|
2355
2394
|
clickAttachDoc = null;
|
|
2356
2395
|
dragAttachDoc = null;
|
|
2396
|
+
hoverAttachDoc = null;
|
|
2357
2397
|
changeObserverDoc = null;
|
|
2358
2398
|
clearPendingGranularChangesets();
|
|
2359
2399
|
if (changeObserver) {
|
|
@@ -2382,7 +2422,7 @@ function loadPage(proxyUrl) {
|
|
|
2382
2422
|
iframe.style.display = 'block';
|
|
2383
2423
|
setIframePageLoadingUi(true);
|
|
2384
2424
|
iframe.src = appendIframeReloadBust(proxyUrl);
|
|
2385
|
-
startIframeContentApplyWatcher(navGen);
|
|
2425
|
+
startIframeContentApplyWatcher(navGen, iframe.contentDocument || null);
|
|
2386
2426
|
scheduleDomTreeRefresh();
|
|
2387
2427
|
}
|
|
2388
2428
|
|
|
@@ -2454,7 +2494,7 @@ function switchVariation(varId) {
|
|
|
2454
2494
|
iframe.src = appendIframeReloadBust(src);
|
|
2455
2495
|
// Do not sync here: the document is still the previous navigation until the
|
|
2456
2496
|
// iframe load event; an eager sync attached observers / DOM tree to the wrong document.
|
|
2457
|
-
startIframeContentApplyWatcher(navGen);
|
|
2497
|
+
startIframeContentApplyWatcher(navGen, iframe.contentDocument || null);
|
|
2458
2498
|
scheduleDomTreeRefresh();
|
|
2459
2499
|
}
|
|
2460
2500
|
} catch(_) {}
|
|
@@ -2971,10 +3011,12 @@ function reapplyActiveVariationGranular(iframeDoc) {
|
|
|
2971
3011
|
}
|
|
2972
3012
|
|
|
2973
3013
|
/** Poll iframe document during navigation; apply granular edits as soon as DOM can match selectors. */
|
|
2974
|
-
function startIframeContentApplyWatcher(navGen) {
|
|
3014
|
+
function startIframeContentApplyWatcher(navGen, prevDocRef) {
|
|
2975
3015
|
stopIframeContentApplyWatcher();
|
|
2976
3016
|
iframeEarlyGranularPrimedForGen = null;
|
|
2977
3017
|
iframeEarlySyncPrimedForGen = null;
|
|
3018
|
+
iframeEarlyDomSignature = '';
|
|
3019
|
+
iframeEarlyDomSignatureNavGen = navGen;
|
|
2978
3020
|
var iframe = document.getElementById('iframeId');
|
|
2979
3021
|
iframeContentApplyTimer = setInterval(function() {
|
|
2980
3022
|
if (navGen !== iframeContentNavGen) {
|
|
@@ -2984,6 +3026,7 @@ function startIframeContentApplyWatcher(navGen) {
|
|
|
2984
3026
|
try {
|
|
2985
3027
|
var doc = iframe.contentDocument;
|
|
2986
3028
|
if (!doc || !doc.body) return;
|
|
3029
|
+
if (prevDocRef && doc === prevDocRef) return;
|
|
2987
3030
|
var docUrl = '';
|
|
2988
3031
|
try { docUrl = String(doc.URL || ''); } catch(_) {}
|
|
2989
3032
|
if (docUrl === 'about:blank') return;
|
|
@@ -3018,6 +3061,7 @@ function startIframeContentApplyWatcher(navGen) {
|
|
|
3018
3061
|
}
|
|
3019
3062
|
if (iframeEarlySyncPrimedForGen !== navGen) {
|
|
3020
3063
|
iframeEarlySyncPrimedForGen = navGen;
|
|
3064
|
+
iframeEarlyDomSignature = computeIframeDomSignature(doc);
|
|
3021
3065
|
syncIframeInteractions('iframe-early-paint');
|
|
3022
3066
|
}
|
|
3023
3067
|
}
|
|
@@ -3087,6 +3131,8 @@ function injectIframeSelectionStyles(doc) {
|
|
|
3087
3131
|
st.textContent =
|
|
3088
3132
|
'.vve-selected{outline:2px solid #6366f1!important;outline-offset:2px!important;' +
|
|
3089
3133
|
'box-shadow:0 0 0 2px rgba(99,102,241,.28),inset 0 0 0 1px rgba(99,102,241,.18)!important;}' +
|
|
3134
|
+
'.vve-tree-hover{outline:2px solid #6366f1!important;outline-offset:2px!important;' +
|
|
3135
|
+
'box-shadow:0 0 0 2px rgba(99,102,241,.22),inset 0 0 0 1px rgba(99,102,241,.12)!important;}' +
|
|
3090
3136
|
'html.vve-drag-armed .vve-selected{cursor:grab!important;}' +
|
|
3091
3137
|
'.vve-dragging{opacity:0.92!important;outline:2px dashed #f59e0b!important;' +
|
|
3092
3138
|
'outline-offset:2px!important;cursor:grabbing!important;box-shadow:none!important;}';
|
|
@@ -3098,6 +3144,47 @@ function injectIframeSelectionStyles(doc) {
|
|
|
3098
3144
|
}
|
|
3099
3145
|
}
|
|
3100
3146
|
|
|
3147
|
+
function clearTreeHoverHighlight() {
|
|
3148
|
+
if (!hoveredTreeEl) return;
|
|
3149
|
+
beginSuppressIframeMutationDirty();
|
|
3150
|
+
try {
|
|
3151
|
+
if (hoveredTreeEl.classList) hoveredTreeEl.classList.remove('vve-tree-hover');
|
|
3152
|
+
} catch(_) {
|
|
3153
|
+
} finally {
|
|
3154
|
+
endSuppressIframeMutationDirty();
|
|
3155
|
+
hoveredTreeEl = null;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
function setTreeHoverHighlight(el) {
|
|
3160
|
+
if (!el || el.nodeType !== 1) {
|
|
3161
|
+
clearTreeHoverHighlight();
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
if (hoveredTreeEl === el) return;
|
|
3165
|
+
clearTreeHoverHighlight();
|
|
3166
|
+
beginSuppressIframeMutationDirty();
|
|
3167
|
+
try {
|
|
3168
|
+
if (el.classList) el.classList.add('vve-tree-hover');
|
|
3169
|
+
hoveredTreeEl = el;
|
|
3170
|
+
} catch(_) {
|
|
3171
|
+
hoveredTreeEl = null;
|
|
3172
|
+
} finally {
|
|
3173
|
+
endSuppressIframeMutationDirty();
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
function isTreeHoverOnlyClassMutation(mutation) {
|
|
3178
|
+
if (!mutation || mutation.type !== 'attributes' || mutation.attributeName !== 'class') return false;
|
|
3179
|
+
var oldClass = String(mutation.oldValue || '');
|
|
3180
|
+
var target = mutation.target;
|
|
3181
|
+
var nextClass = '';
|
|
3182
|
+
try {
|
|
3183
|
+
nextClass = target && typeof target.className === 'string' ? target.className : '';
|
|
3184
|
+
} catch(_) {}
|
|
3185
|
+
return oldClass.indexOf('vve-tree-hover') >= 0 || String(nextClass).indexOf('vve-tree-hover') >= 0;
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3101
3188
|
function setDragHandleActive(on) {
|
|
3102
3189
|
dragHandleActive = !!on;
|
|
3103
3190
|
var b = document.getElementById('sf-drag');
|
|
@@ -3418,6 +3505,9 @@ function renderDomTree(filterRaw) {
|
|
|
3418
3505
|
if (e.target.closest && e.target.closest('.dt-chev')) return;
|
|
3419
3506
|
selectElementFromTree(el);
|
|
3420
3507
|
};
|
|
3508
|
+
row.onmouseenter = function() {
|
|
3509
|
+
setTreeHoverHighlight(el);
|
|
3510
|
+
};
|
|
3421
3511
|
root.appendChild(row);
|
|
3422
3512
|
|
|
3423
3513
|
if (!hasKids || collapsed) return;
|
|
@@ -3436,6 +3526,9 @@ function renderDomTree(filterRaw) {
|
|
|
3436
3526
|
? '<div class="dt-muted">No elements match your search.</div>'
|
|
3437
3527
|
: '<div class="dt-muted">No visible elements yet.</div>';
|
|
3438
3528
|
}
|
|
3529
|
+
root.onmouseleave = function() {
|
|
3530
|
+
clearTreeHoverHighlight();
|
|
3531
|
+
};
|
|
3439
3532
|
}
|
|
3440
3533
|
|
|
3441
3534
|
// \u2500\u2500 Utility helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -3855,12 +3948,25 @@ function renderRightPanel(el) {
|
|
|
3855
3948
|
var sel = buildSelector(el);
|
|
3856
3949
|
bindings.forEach(function(b){
|
|
3857
3950
|
var inp = document.getElementById(b[0]);
|
|
3858
|
-
if (inp)
|
|
3951
|
+
if (inp) {
|
|
3952
|
+
var onValueChange = function() {
|
|
3859
3953
|
// Read the original value BEFORE applying the change so we can revert later
|
|
3860
3954
|
var orig = getOriginalValue(b[0], el);
|
|
3861
3955
|
b[1](inp.value);
|
|
3862
|
-
|
|
3863
|
-
|
|
3956
|
+
var valueToLog = inp.value;
|
|
3957
|
+
try {
|
|
3958
|
+
console.log('[V2] input changed', {
|
|
3959
|
+
inputId: b[0],
|
|
3960
|
+
rawValue: inp.value,
|
|
3961
|
+
appliedValue: valueToLog,
|
|
3962
|
+
selector: sel,
|
|
3963
|
+
});
|
|
3964
|
+
} catch(_) {}
|
|
3965
|
+
logChange(sel, b[0], valueToLog, el, orig);
|
|
3966
|
+
};
|
|
3967
|
+
inp.addEventListener('input', onValueChange);
|
|
3968
|
+
inp.addEventListener('change', onValueChange);
|
|
3969
|
+
}
|
|
3864
3970
|
});
|
|
3865
3971
|
}
|
|
3866
3972
|
|
|
@@ -4123,6 +4229,32 @@ function attachClickHandler() {
|
|
|
4123
4229
|
} catch(_) {}
|
|
4124
4230
|
}
|
|
4125
4231
|
|
|
4232
|
+
function attachIframeHoverHandler() {
|
|
4233
|
+
try {
|
|
4234
|
+
var iframe = document.getElementById('iframeId');
|
|
4235
|
+
var doc = iframe && iframe.contentDocument;
|
|
4236
|
+
if (!doc || !doc.body) return;
|
|
4237
|
+
if (hoverAttachDoc === doc) return;
|
|
4238
|
+
hoverAttachDoc = doc;
|
|
4239
|
+
doc.addEventListener('mousemove', function(e) {
|
|
4240
|
+
if (currentMode !== 'editor') {
|
|
4241
|
+
clearTreeHoverHighlight();
|
|
4242
|
+
return;
|
|
4243
|
+
}
|
|
4244
|
+
var target = e.target;
|
|
4245
|
+
if (!target || target === doc.body || target === doc.documentElement) {
|
|
4246
|
+
clearTreeHoverHighlight();
|
|
4247
|
+
return;
|
|
4248
|
+
}
|
|
4249
|
+
setTreeHoverHighlight(target);
|
|
4250
|
+
}, true);
|
|
4251
|
+
doc.addEventListener('mouseout', function(e) {
|
|
4252
|
+
if (e.relatedTarget) return;
|
|
4253
|
+
clearTreeHoverHighlight();
|
|
4254
|
+
}, true);
|
|
4255
|
+
} catch(_) {}
|
|
4256
|
+
}
|
|
4257
|
+
|
|
4126
4258
|
function attachChangeObserver() {
|
|
4127
4259
|
try {
|
|
4128
4260
|
var iframe = document.getElementById('iframeId');
|
|
@@ -4135,6 +4267,14 @@ function attachChangeObserver() {
|
|
|
4135
4267
|
changeObserverDoc = null;
|
|
4136
4268
|
}
|
|
4137
4269
|
changeObserver = new MutationObserver(function(mutations) {
|
|
4270
|
+
var hasMeaningfulMutation = false;
|
|
4271
|
+
for (var mi = 0; mi < mutations.length; mi++) {
|
|
4272
|
+
if (!isTreeHoverOnlyClassMutation(mutations[mi])) {
|
|
4273
|
+
hasMeaningfulMutation = true;
|
|
4274
|
+
break;
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
if (!hasMeaningfulMutation) return;
|
|
4138
4278
|
// Dirty state is derived from changesets baseline + stateChanges (not raw DOM mutations).
|
|
4139
4279
|
// Host scripts can replace selected nodes every few frames (e.g. A/B tool observers).
|
|
4140
4280
|
// Keep selection sticky by re-resolving from fingerprint.
|
|
@@ -4145,7 +4285,7 @@ function attachChangeObserver() {
|
|
|
4145
4285
|
updateSelectionToolbar();
|
|
4146
4286
|
});
|
|
4147
4287
|
changeObserver.observe(doc.body, {
|
|
4148
|
-
childList: true, subtree: true, attributes: true, characterData: true
|
|
4288
|
+
childList: true, subtree: true, attributes: true, characterData: true, attributeOldValue: true
|
|
4149
4289
|
});
|
|
4150
4290
|
changeObserverDoc = doc;
|
|
4151
4291
|
} catch(_) {}
|
|
@@ -4171,6 +4311,7 @@ function syncIframeInteractions(reason) {
|
|
|
4171
4311
|
injectIframeSelectionStyles(doc);
|
|
4172
4312
|
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
4173
4313
|
attachClickHandler();
|
|
4314
|
+
attachIframeHoverHandler();
|
|
4174
4315
|
attachDragReposition();
|
|
4175
4316
|
attachChangeObserver();
|
|
4176
4317
|
startConsistencyWatchdog(doc);
|
|
@@ -4529,10 +4670,9 @@ window.addEventListener('load', function() {
|
|
|
4529
4670
|
var iframe = document.getElementById('iframeId');
|
|
4530
4671
|
iframe.addEventListener('load', function() {
|
|
4531
4672
|
if (!iframe.src || iframe.src === 'about:blank' || iframe.src === window.location.href) return;
|
|
4532
|
-
// New iframe navigation: always drop bindings tied to the previous document.
|
|
4533
|
-
resetIframeBindings();
|
|
4534
4673
|
var doc = iframe.contentDocument;
|
|
4535
4674
|
if (!doc) {
|
|
4675
|
+
resetIframeBindings();
|
|
4536
4676
|
syncIframeInteractions('iframe-load-no-doc');
|
|
4537
4677
|
return;
|
|
4538
4678
|
}
|
|
@@ -4541,14 +4681,32 @@ window.addEventListener('load', function() {
|
|
|
4541
4681
|
// Stale events: src may already be the proxy URL while the document is still
|
|
4542
4682
|
// about:blank (e.g. src cleared then reset to force reload). Ask sync path to retry.
|
|
4543
4683
|
if (docUrl === 'about:blank') {
|
|
4684
|
+
resetIframeBindings();
|
|
4544
4685
|
syncIframeInteractions('iframe-load-about-blank');
|
|
4545
4686
|
return;
|
|
4546
4687
|
}
|
|
4688
|
+
// If early-paint and final load DOM signatures match, avoid a second full apply/reset
|
|
4689
|
+
// that steals focus from sidebar controls while the page is still stabilizing.
|
|
4690
|
+
var shouldRefreshOnFinalLoad = true;
|
|
4691
|
+
if (
|
|
4692
|
+
iframeEarlyDomSignatureNavGen === iframeContentNavGen &&
|
|
4693
|
+
iframeEarlySyncPrimedForGen === iframeContentNavGen &&
|
|
4694
|
+
iframeEarlyDomSignature
|
|
4695
|
+
) {
|
|
4696
|
+
var finalDomSignature = computeIframeDomSignature(doc);
|
|
4697
|
+
if (finalDomSignature && finalDomSignature === iframeEarlyDomSignature) {
|
|
4698
|
+
shouldRefreshOnFinalLoad = false;
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4701
|
+
if (clickAttachDoc !== doc || dragAttachDoc !== doc || changeObserverDoc !== doc || hoverAttachDoc !== doc) {
|
|
4702
|
+
resetIframeBindings();
|
|
4703
|
+
}
|
|
4547
4704
|
attachIframeLoadingUntilComplete(iframe);
|
|
4548
4705
|
if (doc.body && iframeDocMatchesNavigatedSrc(iframe, doc)) {
|
|
4549
4706
|
stopIframeContentApplyWatcher();
|
|
4550
|
-
|
|
4551
|
-
|
|
4707
|
+
if (shouldRefreshOnFinalLoad) {
|
|
4708
|
+
applyActiveVariationHtml();
|
|
4709
|
+
}
|
|
4552
4710
|
}
|
|
4553
4711
|
// Always attempt sync; it has its own readiness checks + retry loop.
|
|
4554
4712
|
syncIframeInteractions('iframe-load');
|
|
@@ -4605,6 +4763,7 @@ var getDefaultAnthropicApiKey = () => {
|
|
|
4605
4763
|
function createVisualEditorMiddleware(options) {
|
|
4606
4764
|
const anthropicApiKey = options?.anthropicApiKey || getDefaultAnthropicApiKey();
|
|
4607
4765
|
const enableGenerateTestApi = options?.enableGenerateTestApi ?? true;
|
|
4766
|
+
const strictObserverFreeze = options?.strictObserverFreeze === true;
|
|
4608
4767
|
const allowedFrameOrigins = options?.allowedFrameOrigins ?? ["*"];
|
|
4609
4768
|
function setFrameHeaders(req, res) {
|
|
4610
4769
|
res.removeHeader("X-Frame-Options");
|
|
@@ -4772,6 +4931,8 @@ function createVisualEditorMiddleware(options) {
|
|
|
4772
4931
|
const url = new URL(req.url || "", "http://localhost");
|
|
4773
4932
|
const targetUrl = url.searchParams.get("url");
|
|
4774
4933
|
const password = url.searchParams.get("password") || "";
|
|
4934
|
+
const strictFreezeParam = (url.searchParams.get("strictObserverFreeze") || "").toLowerCase();
|
|
4935
|
+
const strictObserverFreezeForRequest = strictFreezeParam === "1" || strictFreezeParam === "true" || strictFreezeParam === "yes" ? true : strictFreezeParam === "0" || strictFreezeParam === "false" || strictFreezeParam === "no" ? false : strictObserverFreeze;
|
|
4775
4936
|
if (!targetUrl) {
|
|
4776
4937
|
res.statusCode = 400;
|
|
4777
4938
|
res.end(JSON.stringify({ error: "Missing url parameter" }));
|
|
@@ -4890,9 +5051,12 @@ function createVisualEditorMiddleware(options) {
|
|
|
4890
5051
|
}
|
|
4891
5052
|
);
|
|
4892
5053
|
if (html.includes("</head>")) {
|
|
4893
|
-
html = html.replace(
|
|
4894
|
-
|
|
4895
|
-
|
|
5054
|
+
html = html.replace(
|
|
5055
|
+
"</head>",
|
|
5056
|
+
`${iframeAlwaysShowCss}
|
|
5057
|
+
${iframeAlwaysShowCssGuardScript}
|
|
5058
|
+
</head>`
|
|
5059
|
+
);
|
|
4896
5060
|
}
|
|
4897
5061
|
html = html.replace(
|
|
4898
5062
|
/<meta[^>]+http-equiv=["']?\s*(x-frame-options|content-security-policy)\s*["']?[^>]*>/gi,
|
|
@@ -4906,6 +5070,7 @@ ${consentAllowCss}
|
|
|
4906
5070
|
var TARGET_ORIGIN=${JSON.stringify(origin)};
|
|
4907
5071
|
var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
|
|
4908
5072
|
var PROXY_PASSWORD=${JSON.stringify(password)};
|
|
5073
|
+
var STRICT_OBSERVER_FREEZE=${JSON.stringify(strictObserverFreezeForRequest)};
|
|
4909
5074
|
window.__CONVERSION_EDITOR_ACTIVE__=true;
|
|
4910
5075
|
function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
|
|
4911
5076
|
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;}}
|
|
@@ -4925,6 +5090,9 @@ try{
|
|
|
4925
5090
|
var wrapped=function(list,obs){
|
|
4926
5091
|
try{
|
|
4927
5092
|
if(!window.__CONVERSION_EDITOR_ACTIVE__)return cb(list,obs);
|
|
5093
|
+
if(STRICT_OBSERVER_FREEZE){
|
|
5094
|
+
return;
|
|
5095
|
+
}
|
|
4928
5096
|
var now=Date.now();
|
|
4929
5097
|
if(now-last<120)return;
|
|
4930
5098
|
last=now;
|
|
@@ -4934,6 +5102,11 @@ try{
|
|
|
4934
5102
|
return new NativeMO(wrapped);
|
|
4935
5103
|
};
|
|
4936
5104
|
window.MutationObserver.prototype=NativeMO.prototype;
|
|
5105
|
+
try{
|
|
5106
|
+
if(STRICT_OBSERVER_FREEZE){
|
|
5107
|
+
console.info("[conversion-proxy] strict MutationObserver freeze active");
|
|
5108
|
+
}
|
|
5109
|
+
}catch(_){}
|
|
4937
5110
|
}
|
|
4938
5111
|
}catch(_){}
|
|
4939
5112
|
}catch(_){}})();</script>`;
|
package/dist/vite.js
CHANGED
|
@@ -3,8 +3,20 @@ import path from 'path';
|
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
|
|
5
5
|
// src/visualEditorProxyPlugin.ts
|
|
6
|
-
var
|
|
7
|
-
var
|
|
6
|
+
var iframeAlwaysShowCss = `<style id="__ce_force_show">#CybotCookiebotDialog,#CybotCookiebotDialogBodyUnderlay,#onetrust-consent-sdk,#onetrust-banner-sdk,.cc-window,.cc-banner,.cc-overlay,#cookie-notice,#cookie-banner,#cookie-consent,.cookie-notice,.cookie-banner,.cookie-consent,.cookie-popup,.cookie-bar,.cookie-message,.cookie-alert,.gdpr-banner,.gdpr-consent,.gdpr-popup,.gdpr-overlay,#gdpr-consent,#gdpr-banner,.consent-banner,.consent-popup,.consent-overlay,#consent-banner,#consent-popup,[class*="cookie-consent"],[class*="cookie-banner"],[class*="cookie-notice"],[class*="CookieConsent"],[class*="CookieBanner"],[id*="cookie-consent"],[id*="cookie-banner"],[id*="cookie-notice"],[aria-label*="cookie" i],[aria-label*="consent" i],.klaro,.klaro .cookie-modal,#usercentrics-root,.trustarc-banner,#truste-consent-track,#hs-eu-cookie-confirmation,.osano-cm-window,.osano-cm-dialog,.evidon-banner,#_evidon_banner,.js-cookie-consent,.cookie-disclaimer,.shopify-section-cookies,#shopify-section-cookies,#shopify-pc__banner,#shopify-pc__modal,.privacy-banner,.privacy-popup,[data-testid="cookie-banner"],[data-testid="consent-banner"],.amgdprcookie-bar-container,[data-amcookie-js="bar"],.amgdprjs-bar-template,.amgdprcookie-modal-container,.amgdprcookie-modal-overlay,#cmplz-cookiebanner-container,.cmplz-cookiebanner,#iubenda-cs-banner,.iubenda-cs-container,#qc-cmp2-container,.qc-cmp2-consent-info,#didomi-host,.didomi-popup-container,.didomi-notice,#termly-code-snippet-support,[class*="termly"],[class*="gdprcookie"],[class*="amgdpr"],[id*="gdpr-cookie"],[class*="cookie-modal"],[id*="cookie-modal"],[class*="cookieConsent"],[id*="cookieConsent"]{display:revert!important;visibility:visible!important;opacity:1!important;pointer-events:auto!important;height:auto!important;max-height:none!important;overflow:visible!important;}</style>`;
|
|
7
|
+
var iframeAlwaysShowCssGuardScript = `<script id="__ce_force_show_guard">(function(){try{
|
|
8
|
+
function ensureForceShowStyleLast(){
|
|
9
|
+
var style=document.getElementById("__ce_force_show");
|
|
10
|
+
if(!style||!document.head)return;
|
|
11
|
+
if(document.head.lastElementChild!==style){
|
|
12
|
+
document.head.appendChild(style);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
ensureForceShowStyleLast();
|
|
16
|
+
var mo=new MutationObserver(function(){ensureForceShowStyleLast();});
|
|
17
|
+
if(document.head){mo.observe(document.head,{childList:true});}
|
|
18
|
+
window.addEventListener("beforeunload",function(){try{mo.disconnect();}catch(_){}});}
|
|
19
|
+
catch(_){}})();</script>`;
|
|
8
20
|
var AI_SYSTEM_PROMPT = `You are a CRO expert. Return JSON only with: hypothesis, mutations, summary. Keep 2-6 precise mutations and only valid selectors from snapshot.`;
|
|
9
21
|
var BRIDGE_SCRIPT = `(function(){if(window.__CONVERSION_BRIDGE_LOADED__)return;window.__CONVERSION_BRIDGE_LOADED__=true;var CHANNEL='conversion-editor';var highlightEl=null;function send(payload){window.parent.postMessage({channel:CHANNEL,payload:payload},'*');}function qs(selector){try{return document.querySelector(selector);}catch(_){return null;}}function getSelector(el){if(!el||el.nodeType!==1)return'';if(el.id)return'#'+CSS.escape(el.id);var parts=[];var current=el;var depth=0;while(current&¤t.nodeType===1&&depth<5){var part=current.tagName.toLowerCase();if(current.classList&¤t.classList.length){part+='.'+Array.from(current.classList).slice(0,2).map(function(c){return CSS.escape(c);}).join('.');}var parent=current.parentElement;if(parent){var siblings=Array.from(parent.children).filter(function(s){return s.tagName===current.tagName;});if(siblings.length>1)part+=':nth-of-type('+(siblings.indexOf(current)+1)+')';}parts.unshift(part);current=parent;depth+=1;}return parts.join(' > ');}function payloadOf(el){var rect=el.getBoundingClientRect();var style=window.getComputedStyle(el);return{selector:getSelector(el),tagName:el.tagName.toLowerCase(),textContent:(el.textContent||'').trim().slice(0,500),computedStyles:{color:style.color,backgroundColor:style.backgroundColor,fontSize:style.fontSize,fontWeight:style.fontWeight,lineHeight:style.lineHeight,display:style.display},rect:{top:rect.top,left:rect.left,width:rect.width,height:rect.height}};}function applyMutation(m){var el=qs(m.selector);if(!el)return;switch(m.action){case'setStyle':if(m.property)el.style[m.property]=m.value;break;case'setText':el.textContent=m.value;break;case'setHTML':el.innerHTML=m.value;break;case'setAttribute':if(m.property)el.setAttribute(m.property,m.value);break;case'hide':el.style.display='none';break;case'show':el.style.display='';break;case'insertHTML':if(m.position)el.insertAdjacentHTML(m.position,m.value||'');break;case'insertSection':if(m.position)el.insertAdjacentHTML(m.position,m.sectionHtml||m.value||'');break;case'reorderElement':if(!m.targetSelector)break;var target=qs(m.targetSelector);if(!target||!target.parentNode||!el.parentNode)break;if(m.insertPosition==='before')target.parentNode.insertBefore(el,target);else target.parentNode.insertBefore(el,target.nextSibling);break;default:break;}}function revertMutation(m){var el=qs(m.selector);if(!el)return;switch(m.action){case'setStyle':if(m.property)el.style[m.property]=m.previous||'';break;case'setText':el.textContent=m.previous||'';break;case'setHTML':el.innerHTML=m.previous||'';break;case'setAttribute':if(m.property){if(m.previous!=null)el.setAttribute(m.property,m.previous);else el.removeAttribute(m.property);}break;case'hide':case'show':el.style.display=m.previous||'';break;default:break;}}function captureSnapshot(){var elements=Array.from(document.querySelectorAll('h1,h2,h3,p,button,a,input,select,img,section,div')).slice(0,700).map(function(el){var rect=el.getBoundingClientRect();var cs=window.getComputedStyle(el);return{selector:getSelector(el),parentSelector:el.parentElement?getSelector(el.parentElement):null,tagName:el.tagName.toLowerCase(),textContent:(el.textContent||'').trim().slice(0,300),attributes:Array.from(el.attributes||[]).reduce(function(acc,a){acc[a.name]=a.value;return acc;},{}),computedStyles:{color:cs.color,backgroundColor:cs.backgroundColor,fontSize:cs.fontSize,fontWeight:cs.fontWeight,display:cs.display},childrenCount:el.children?el.children.length:0,aboveFold:rect.top<window.innerHeight,depth:(function(){var d=0,p=el.parentElement;while(p&&d<25){d++;p=p.parentElement;}return d;})()};});send({type:'snapshotCaptured',snapshot:{url:window.location.href,title:document.title,viewport:{width:window.innerWidth,height:window.innerHeight},elements:elements}});}function captureTree(){function toNode(el,depth){var rect=el.getBoundingClientRect();var tag=el.tagName.toLowerCase();var type='generic';if(tag==='section')type='section';else if(tag==='img')type='image';else if(tag==='a')type='link';else if(tag==='button')type='button';else if(tag==='form')type='form';else if(['p','h1','h2','h3','h4','h5','h6','span'].indexOf(tag)>=0)type='text';else if(['div','main','article','aside','header','footer','nav'].indexOf(tag)>=0)type='container';var children=Array.from(el.children||[]).slice(0,50).map(function(c){return toNode(c,depth+1);});return{id:getSelector(el),selector:getSelector(el),tagName:tag,label:(el.getAttribute('aria-label')||el.getAttribute('id')||el.className||tag).toString().slice(0,80),type:type,children:children,depth:depth,isAboveFold:rect.top<window.innerHeight,rect:{top:rect.top,left:rect.left,width:rect.width,height:rect.height}};}send({type:'pageTreeCaptured',tree:[toNode(document.body,0)]});}window.addEventListener('message',function(e){var msg=e.data;if(!msg||msg.channel!==CHANNEL||!msg.payload)return;var p=msg.payload;switch(p.type){case'ping':send({type:'pong'});break;case'applyMutation':applyMutation(p.mutation);break;case'applyMutationBatch':(p.mutations||[]).forEach(applyMutation);break;case'revert':revertMutation(p.mutation);break;case'clearAllMutations':window.location.reload();break;case'captureSnapshot':captureSnapshot();break;case'validateSelectors':send({type:'selectorsValidated',results:(p.selectors||[]).map(function(s){var el=qs(s);return{selector:s,found:!!el,tagName:el?el.tagName.toLowerCase():null};})});break;case'scrollToElement':case'selectElement':var target=qs(p.selector);if(target){target.scrollIntoView({behavior:'smooth',block:'center'});send({type:'elementSelected',element:payloadOf(target)});}break;case'hoverElement':var h=qs(p.selector);if(h){if(highlightEl)highlightEl.style.outline='';h.style.outline='2px solid #3b82f6';highlightEl=h;}break;case'capturePageTree':captureTree();break;default:break;}});document.addEventListener('click',function(e){var el=e.target;if(!(el instanceof Element))return;send({type:'elementSelected',element:payloadOf(el)});},true);send({type:'bridgeReady'});})();`;
|
|
10
22
|
function buildVvvebEditorHtml() {
|
|
@@ -246,7 +258,7 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
|
|
|
246
258
|
/* \u2500\u2500 DOM tree (Elements tab) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
247
259
|
.dt-tree{font-size:11px;padding:0px 0 0px 20px;user-select:none}
|
|
248
260
|
.dt-row{
|
|
249
|
-
display:flex;align-items:center;gap:2px;min-height:26px;padding:2px 8px 2px 4px;
|
|
261
|
+
width:fit-content;display:flex;align-items:center;gap:2px;min-height:26px;padding:2px 8px 2px 4px;
|
|
250
262
|
cursor:pointer;color:var(--text-2);border-radius:4px;margin:0 4px
|
|
251
263
|
}
|
|
252
264
|
.dt-row:hover{background:var(--bg-hover);color:var(--text)}
|
|
@@ -672,10 +684,10 @@ select.pr-inp{cursor:pointer;background:#fff}
|
|
|
672
684
|
<button class="tb-dk-btn" id="btn-mode-nav" onclick="setMode('navigate')" title="Navigate mode \u2014 interact with page normally"><i class="bi bi-magic"></i></button>
|
|
673
685
|
<button class="tb-dk-btn" title="Comments"><i class="bi bi-chat-dots"></i></button>
|
|
674
686
|
</div>
|
|
675
|
-
<!-- btn-close: hidden visually, kept for JS event listener -->
|
|
676
|
-
<button id="btn-close" style="display:none" title="Close editor"></button>
|
|
677
687
|
<button class="tb-sim-btn" id="btn-simulate" onclick="simulateExperiment()"><i class="bi bi-lightning-charge-fill"></i> Simulate</button>
|
|
678
|
-
<button class="tb-fin-btn" id="btn-save">
|
|
688
|
+
<button class="tb-fin-btn" id="btn-save">Save Changes</button>
|
|
689
|
+
<!-- btn-close: kept for JS event listener -->
|
|
690
|
+
<button class="tb-fin-btn" id="btn-close">Close</button>
|
|
679
691
|
</div>
|
|
680
692
|
|
|
681
693
|
<!-- url-bar: hidden, kept for JS compatibility -->
|
|
@@ -1094,6 +1106,8 @@ var iframeContentNavGen = 0;
|
|
|
1094
1106
|
var iframeContentApplyTimer = null;
|
|
1095
1107
|
var iframeEarlyGranularPrimedForGen = null;
|
|
1096
1108
|
var iframeEarlySyncPrimedForGen = null;
|
|
1109
|
+
var iframeEarlyDomSignature = '';
|
|
1110
|
+
var iframeEarlyDomSignatureNavGen = 0;
|
|
1097
1111
|
/** insert/reorder entries are applied from early granular + full apply \u2014 skip exact duplicates per iframe nav */
|
|
1098
1112
|
var appliedStructuralChangesetKeys = {};
|
|
1099
1113
|
var isDirty = false;
|
|
@@ -1105,6 +1119,7 @@ var selectedEl = null;
|
|
|
1105
1119
|
var selectedElFingerprint = '';
|
|
1106
1120
|
var selectedElRecoverMisses = 0;
|
|
1107
1121
|
var MAX_SELECTED_RECOVER_MISSES = 12;
|
|
1122
|
+
var hoveredTreeEl = null;
|
|
1108
1123
|
var isDeselectingSelection = false;
|
|
1109
1124
|
var suppressClickUntil = 0;
|
|
1110
1125
|
var dragAttachDoc = null;
|
|
@@ -1118,6 +1133,7 @@ var iframeSyncAttempts = 0;
|
|
|
1118
1133
|
var selectionScrollWin = null;
|
|
1119
1134
|
var selectionResizeBound = false;
|
|
1120
1135
|
var clickAttachDoc = null;
|
|
1136
|
+
var hoverAttachDoc = null;
|
|
1121
1137
|
var changeObserver = null;
|
|
1122
1138
|
var changeObserverDoc = null;
|
|
1123
1139
|
/** Incremented while applying changesets / selection chrome so MutationObserver does not mark dirty */
|
|
@@ -1330,6 +1346,7 @@ function setMode(mode) {
|
|
|
1330
1346
|
document.getElementById('btn-mode-editor').classList.toggle('active', mode === 'editor');
|
|
1331
1347
|
document.getElementById('btn-mode-nav').classList.toggle('active', mode === 'navigate');
|
|
1332
1348
|
if (mode === 'navigate') {
|
|
1349
|
+
clearTreeHoverHighlight();
|
|
1333
1350
|
setDragHandleActive(false);
|
|
1334
1351
|
deselectElement();
|
|
1335
1352
|
} else if (mode === 'editor') {
|
|
@@ -1713,7 +1730,7 @@ function softReloadEditorIframe() {
|
|
|
1713
1730
|
resetIframeBindings();
|
|
1714
1731
|
setIframePageLoadingUi(true);
|
|
1715
1732
|
iframe.src = appendIframeReloadBust(src);
|
|
1716
|
-
startIframeContentApplyWatcher(navGen);
|
|
1733
|
+
startIframeContentApplyWatcher(navGen, iframe.contentDocument || null);
|
|
1717
1734
|
scheduleDomTreeRefresh();
|
|
1718
1735
|
}
|
|
1719
1736
|
|
|
@@ -2041,7 +2058,8 @@ function handleLoadExperiment(data) {
|
|
|
2041
2058
|
return;
|
|
2042
2059
|
}
|
|
2043
2060
|
var proxyUrl = '/api/conversion-proxy?password=' + encodeURIComponent(data.editorPassword || '') +
|
|
2044
|
-
'&url=' + encodeURIComponent(pageUrl)
|
|
2061
|
+
'&url=' + encodeURIComponent(pageUrl) +
|
|
2062
|
+
'&strictObserverFreeze=' + encodeURIComponent(data && data.strictObserverFreeze ? '1' : '0');
|
|
2045
2063
|
|
|
2046
2064
|
// Parent often re-posts load-experiment when React re-renders (new object identity) or
|
|
2047
2065
|
// after mutations-changed. Reloading the iframe again wipes variant changesets mid-session.
|
|
@@ -2192,6 +2210,27 @@ function bodyHasFirstPaintChild(body) {
|
|
|
2192
2210
|
return false;
|
|
2193
2211
|
}
|
|
2194
2212
|
|
|
2213
|
+
function computeIframeDomSignature(doc) {
|
|
2214
|
+
if (!doc || !doc.body) return '';
|
|
2215
|
+
var body = doc.body;
|
|
2216
|
+
var tags = [];
|
|
2217
|
+
var walker = null;
|
|
2218
|
+
try {
|
|
2219
|
+
walker = doc.createTreeWalker(body, NodeFilter.SHOW_ELEMENT, null);
|
|
2220
|
+
} catch(_) {
|
|
2221
|
+
walker = null;
|
|
2222
|
+
}
|
|
2223
|
+
var count = 0;
|
|
2224
|
+
if (walker) {
|
|
2225
|
+
while (walker.nextNode() && count < 400) {
|
|
2226
|
+
var node = walker.currentNode;
|
|
2227
|
+
tags.push((node && node.tagName) ? String(node.tagName).toLowerCase() : '');
|
|
2228
|
+
count += 1;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
return String(body.children ? body.children.length : 0) + '|' + String(count) + '|' + tags.join(',');
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2195
2234
|
/** True when at least one granular changeset selector already matches (nested content painted). */
|
|
2196
2235
|
function granularAnySelectorMatches(doc, cs) {
|
|
2197
2236
|
if (!doc || !cs || !cs.length) return false;
|
|
@@ -2346,6 +2385,7 @@ function resetIframeBindings() {
|
|
|
2346
2385
|
appliedStructuralChangesetKeys = {};
|
|
2347
2386
|
clickAttachDoc = null;
|
|
2348
2387
|
dragAttachDoc = null;
|
|
2388
|
+
hoverAttachDoc = null;
|
|
2349
2389
|
changeObserverDoc = null;
|
|
2350
2390
|
clearPendingGranularChangesets();
|
|
2351
2391
|
if (changeObserver) {
|
|
@@ -2374,7 +2414,7 @@ function loadPage(proxyUrl) {
|
|
|
2374
2414
|
iframe.style.display = 'block';
|
|
2375
2415
|
setIframePageLoadingUi(true);
|
|
2376
2416
|
iframe.src = appendIframeReloadBust(proxyUrl);
|
|
2377
|
-
startIframeContentApplyWatcher(navGen);
|
|
2417
|
+
startIframeContentApplyWatcher(navGen, iframe.contentDocument || null);
|
|
2378
2418
|
scheduleDomTreeRefresh();
|
|
2379
2419
|
}
|
|
2380
2420
|
|
|
@@ -2446,7 +2486,7 @@ function switchVariation(varId) {
|
|
|
2446
2486
|
iframe.src = appendIframeReloadBust(src);
|
|
2447
2487
|
// Do not sync here: the document is still the previous navigation until the
|
|
2448
2488
|
// iframe load event; an eager sync attached observers / DOM tree to the wrong document.
|
|
2449
|
-
startIframeContentApplyWatcher(navGen);
|
|
2489
|
+
startIframeContentApplyWatcher(navGen, iframe.contentDocument || null);
|
|
2450
2490
|
scheduleDomTreeRefresh();
|
|
2451
2491
|
}
|
|
2452
2492
|
} catch(_) {}
|
|
@@ -2963,10 +3003,12 @@ function reapplyActiveVariationGranular(iframeDoc) {
|
|
|
2963
3003
|
}
|
|
2964
3004
|
|
|
2965
3005
|
/** Poll iframe document during navigation; apply granular edits as soon as DOM can match selectors. */
|
|
2966
|
-
function startIframeContentApplyWatcher(navGen) {
|
|
3006
|
+
function startIframeContentApplyWatcher(navGen, prevDocRef) {
|
|
2967
3007
|
stopIframeContentApplyWatcher();
|
|
2968
3008
|
iframeEarlyGranularPrimedForGen = null;
|
|
2969
3009
|
iframeEarlySyncPrimedForGen = null;
|
|
3010
|
+
iframeEarlyDomSignature = '';
|
|
3011
|
+
iframeEarlyDomSignatureNavGen = navGen;
|
|
2970
3012
|
var iframe = document.getElementById('iframeId');
|
|
2971
3013
|
iframeContentApplyTimer = setInterval(function() {
|
|
2972
3014
|
if (navGen !== iframeContentNavGen) {
|
|
@@ -2976,6 +3018,7 @@ function startIframeContentApplyWatcher(navGen) {
|
|
|
2976
3018
|
try {
|
|
2977
3019
|
var doc = iframe.contentDocument;
|
|
2978
3020
|
if (!doc || !doc.body) return;
|
|
3021
|
+
if (prevDocRef && doc === prevDocRef) return;
|
|
2979
3022
|
var docUrl = '';
|
|
2980
3023
|
try { docUrl = String(doc.URL || ''); } catch(_) {}
|
|
2981
3024
|
if (docUrl === 'about:blank') return;
|
|
@@ -3010,6 +3053,7 @@ function startIframeContentApplyWatcher(navGen) {
|
|
|
3010
3053
|
}
|
|
3011
3054
|
if (iframeEarlySyncPrimedForGen !== navGen) {
|
|
3012
3055
|
iframeEarlySyncPrimedForGen = navGen;
|
|
3056
|
+
iframeEarlyDomSignature = computeIframeDomSignature(doc);
|
|
3013
3057
|
syncIframeInteractions('iframe-early-paint');
|
|
3014
3058
|
}
|
|
3015
3059
|
}
|
|
@@ -3079,6 +3123,8 @@ function injectIframeSelectionStyles(doc) {
|
|
|
3079
3123
|
st.textContent =
|
|
3080
3124
|
'.vve-selected{outline:2px solid #6366f1!important;outline-offset:2px!important;' +
|
|
3081
3125
|
'box-shadow:0 0 0 2px rgba(99,102,241,.28),inset 0 0 0 1px rgba(99,102,241,.18)!important;}' +
|
|
3126
|
+
'.vve-tree-hover{outline:2px solid #6366f1!important;outline-offset:2px!important;' +
|
|
3127
|
+
'box-shadow:0 0 0 2px rgba(99,102,241,.22),inset 0 0 0 1px rgba(99,102,241,.12)!important;}' +
|
|
3082
3128
|
'html.vve-drag-armed .vve-selected{cursor:grab!important;}' +
|
|
3083
3129
|
'.vve-dragging{opacity:0.92!important;outline:2px dashed #f59e0b!important;' +
|
|
3084
3130
|
'outline-offset:2px!important;cursor:grabbing!important;box-shadow:none!important;}';
|
|
@@ -3090,6 +3136,47 @@ function injectIframeSelectionStyles(doc) {
|
|
|
3090
3136
|
}
|
|
3091
3137
|
}
|
|
3092
3138
|
|
|
3139
|
+
function clearTreeHoverHighlight() {
|
|
3140
|
+
if (!hoveredTreeEl) return;
|
|
3141
|
+
beginSuppressIframeMutationDirty();
|
|
3142
|
+
try {
|
|
3143
|
+
if (hoveredTreeEl.classList) hoveredTreeEl.classList.remove('vve-tree-hover');
|
|
3144
|
+
} catch(_) {
|
|
3145
|
+
} finally {
|
|
3146
|
+
endSuppressIframeMutationDirty();
|
|
3147
|
+
hoveredTreeEl = null;
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
function setTreeHoverHighlight(el) {
|
|
3152
|
+
if (!el || el.nodeType !== 1) {
|
|
3153
|
+
clearTreeHoverHighlight();
|
|
3154
|
+
return;
|
|
3155
|
+
}
|
|
3156
|
+
if (hoveredTreeEl === el) return;
|
|
3157
|
+
clearTreeHoverHighlight();
|
|
3158
|
+
beginSuppressIframeMutationDirty();
|
|
3159
|
+
try {
|
|
3160
|
+
if (el.classList) el.classList.add('vve-tree-hover');
|
|
3161
|
+
hoveredTreeEl = el;
|
|
3162
|
+
} catch(_) {
|
|
3163
|
+
hoveredTreeEl = null;
|
|
3164
|
+
} finally {
|
|
3165
|
+
endSuppressIframeMutationDirty();
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
function isTreeHoverOnlyClassMutation(mutation) {
|
|
3170
|
+
if (!mutation || mutation.type !== 'attributes' || mutation.attributeName !== 'class') return false;
|
|
3171
|
+
var oldClass = String(mutation.oldValue || '');
|
|
3172
|
+
var target = mutation.target;
|
|
3173
|
+
var nextClass = '';
|
|
3174
|
+
try {
|
|
3175
|
+
nextClass = target && typeof target.className === 'string' ? target.className : '';
|
|
3176
|
+
} catch(_) {}
|
|
3177
|
+
return oldClass.indexOf('vve-tree-hover') >= 0 || String(nextClass).indexOf('vve-tree-hover') >= 0;
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3093
3180
|
function setDragHandleActive(on) {
|
|
3094
3181
|
dragHandleActive = !!on;
|
|
3095
3182
|
var b = document.getElementById('sf-drag');
|
|
@@ -3410,6 +3497,9 @@ function renderDomTree(filterRaw) {
|
|
|
3410
3497
|
if (e.target.closest && e.target.closest('.dt-chev')) return;
|
|
3411
3498
|
selectElementFromTree(el);
|
|
3412
3499
|
};
|
|
3500
|
+
row.onmouseenter = function() {
|
|
3501
|
+
setTreeHoverHighlight(el);
|
|
3502
|
+
};
|
|
3413
3503
|
root.appendChild(row);
|
|
3414
3504
|
|
|
3415
3505
|
if (!hasKids || collapsed) return;
|
|
@@ -3428,6 +3518,9 @@ function renderDomTree(filterRaw) {
|
|
|
3428
3518
|
? '<div class="dt-muted">No elements match your search.</div>'
|
|
3429
3519
|
: '<div class="dt-muted">No visible elements yet.</div>';
|
|
3430
3520
|
}
|
|
3521
|
+
root.onmouseleave = function() {
|
|
3522
|
+
clearTreeHoverHighlight();
|
|
3523
|
+
};
|
|
3431
3524
|
}
|
|
3432
3525
|
|
|
3433
3526
|
// \u2500\u2500 Utility helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -3847,12 +3940,25 @@ function renderRightPanel(el) {
|
|
|
3847
3940
|
var sel = buildSelector(el);
|
|
3848
3941
|
bindings.forEach(function(b){
|
|
3849
3942
|
var inp = document.getElementById(b[0]);
|
|
3850
|
-
if (inp)
|
|
3943
|
+
if (inp) {
|
|
3944
|
+
var onValueChange = function() {
|
|
3851
3945
|
// Read the original value BEFORE applying the change so we can revert later
|
|
3852
3946
|
var orig = getOriginalValue(b[0], el);
|
|
3853
3947
|
b[1](inp.value);
|
|
3854
|
-
|
|
3855
|
-
|
|
3948
|
+
var valueToLog = inp.value;
|
|
3949
|
+
try {
|
|
3950
|
+
console.log('[V2] input changed', {
|
|
3951
|
+
inputId: b[0],
|
|
3952
|
+
rawValue: inp.value,
|
|
3953
|
+
appliedValue: valueToLog,
|
|
3954
|
+
selector: sel,
|
|
3955
|
+
});
|
|
3956
|
+
} catch(_) {}
|
|
3957
|
+
logChange(sel, b[0], valueToLog, el, orig);
|
|
3958
|
+
};
|
|
3959
|
+
inp.addEventListener('input', onValueChange);
|
|
3960
|
+
inp.addEventListener('change', onValueChange);
|
|
3961
|
+
}
|
|
3856
3962
|
});
|
|
3857
3963
|
}
|
|
3858
3964
|
|
|
@@ -4115,6 +4221,32 @@ function attachClickHandler() {
|
|
|
4115
4221
|
} catch(_) {}
|
|
4116
4222
|
}
|
|
4117
4223
|
|
|
4224
|
+
function attachIframeHoverHandler() {
|
|
4225
|
+
try {
|
|
4226
|
+
var iframe = document.getElementById('iframeId');
|
|
4227
|
+
var doc = iframe && iframe.contentDocument;
|
|
4228
|
+
if (!doc || !doc.body) return;
|
|
4229
|
+
if (hoverAttachDoc === doc) return;
|
|
4230
|
+
hoverAttachDoc = doc;
|
|
4231
|
+
doc.addEventListener('mousemove', function(e) {
|
|
4232
|
+
if (currentMode !== 'editor') {
|
|
4233
|
+
clearTreeHoverHighlight();
|
|
4234
|
+
return;
|
|
4235
|
+
}
|
|
4236
|
+
var target = e.target;
|
|
4237
|
+
if (!target || target === doc.body || target === doc.documentElement) {
|
|
4238
|
+
clearTreeHoverHighlight();
|
|
4239
|
+
return;
|
|
4240
|
+
}
|
|
4241
|
+
setTreeHoverHighlight(target);
|
|
4242
|
+
}, true);
|
|
4243
|
+
doc.addEventListener('mouseout', function(e) {
|
|
4244
|
+
if (e.relatedTarget) return;
|
|
4245
|
+
clearTreeHoverHighlight();
|
|
4246
|
+
}, true);
|
|
4247
|
+
} catch(_) {}
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4118
4250
|
function attachChangeObserver() {
|
|
4119
4251
|
try {
|
|
4120
4252
|
var iframe = document.getElementById('iframeId');
|
|
@@ -4127,6 +4259,14 @@ function attachChangeObserver() {
|
|
|
4127
4259
|
changeObserverDoc = null;
|
|
4128
4260
|
}
|
|
4129
4261
|
changeObserver = new MutationObserver(function(mutations) {
|
|
4262
|
+
var hasMeaningfulMutation = false;
|
|
4263
|
+
for (var mi = 0; mi < mutations.length; mi++) {
|
|
4264
|
+
if (!isTreeHoverOnlyClassMutation(mutations[mi])) {
|
|
4265
|
+
hasMeaningfulMutation = true;
|
|
4266
|
+
break;
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
if (!hasMeaningfulMutation) return;
|
|
4130
4270
|
// Dirty state is derived from changesets baseline + stateChanges (not raw DOM mutations).
|
|
4131
4271
|
// Host scripts can replace selected nodes every few frames (e.g. A/B tool observers).
|
|
4132
4272
|
// Keep selection sticky by re-resolving from fingerprint.
|
|
@@ -4137,7 +4277,7 @@ function attachChangeObserver() {
|
|
|
4137
4277
|
updateSelectionToolbar();
|
|
4138
4278
|
});
|
|
4139
4279
|
changeObserver.observe(doc.body, {
|
|
4140
|
-
childList: true, subtree: true, attributes: true, characterData: true
|
|
4280
|
+
childList: true, subtree: true, attributes: true, characterData: true, attributeOldValue: true
|
|
4141
4281
|
});
|
|
4142
4282
|
changeObserverDoc = doc;
|
|
4143
4283
|
} catch(_) {}
|
|
@@ -4163,6 +4303,7 @@ function syncIframeInteractions(reason) {
|
|
|
4163
4303
|
injectIframeSelectionStyles(doc);
|
|
4164
4304
|
refreshPersistentChangesetStyleTagForActiveVariation();
|
|
4165
4305
|
attachClickHandler();
|
|
4306
|
+
attachIframeHoverHandler();
|
|
4166
4307
|
attachDragReposition();
|
|
4167
4308
|
attachChangeObserver();
|
|
4168
4309
|
startConsistencyWatchdog(doc);
|
|
@@ -4521,10 +4662,9 @@ window.addEventListener('load', function() {
|
|
|
4521
4662
|
var iframe = document.getElementById('iframeId');
|
|
4522
4663
|
iframe.addEventListener('load', function() {
|
|
4523
4664
|
if (!iframe.src || iframe.src === 'about:blank' || iframe.src === window.location.href) return;
|
|
4524
|
-
// New iframe navigation: always drop bindings tied to the previous document.
|
|
4525
|
-
resetIframeBindings();
|
|
4526
4665
|
var doc = iframe.contentDocument;
|
|
4527
4666
|
if (!doc) {
|
|
4667
|
+
resetIframeBindings();
|
|
4528
4668
|
syncIframeInteractions('iframe-load-no-doc');
|
|
4529
4669
|
return;
|
|
4530
4670
|
}
|
|
@@ -4533,14 +4673,32 @@ window.addEventListener('load', function() {
|
|
|
4533
4673
|
// Stale events: src may already be the proxy URL while the document is still
|
|
4534
4674
|
// about:blank (e.g. src cleared then reset to force reload). Ask sync path to retry.
|
|
4535
4675
|
if (docUrl === 'about:blank') {
|
|
4676
|
+
resetIframeBindings();
|
|
4536
4677
|
syncIframeInteractions('iframe-load-about-blank');
|
|
4537
4678
|
return;
|
|
4538
4679
|
}
|
|
4680
|
+
// If early-paint and final load DOM signatures match, avoid a second full apply/reset
|
|
4681
|
+
// that steals focus from sidebar controls while the page is still stabilizing.
|
|
4682
|
+
var shouldRefreshOnFinalLoad = true;
|
|
4683
|
+
if (
|
|
4684
|
+
iframeEarlyDomSignatureNavGen === iframeContentNavGen &&
|
|
4685
|
+
iframeEarlySyncPrimedForGen === iframeContentNavGen &&
|
|
4686
|
+
iframeEarlyDomSignature
|
|
4687
|
+
) {
|
|
4688
|
+
var finalDomSignature = computeIframeDomSignature(doc);
|
|
4689
|
+
if (finalDomSignature && finalDomSignature === iframeEarlyDomSignature) {
|
|
4690
|
+
shouldRefreshOnFinalLoad = false;
|
|
4691
|
+
}
|
|
4692
|
+
}
|
|
4693
|
+
if (clickAttachDoc !== doc || dragAttachDoc !== doc || changeObserverDoc !== doc || hoverAttachDoc !== doc) {
|
|
4694
|
+
resetIframeBindings();
|
|
4695
|
+
}
|
|
4539
4696
|
attachIframeLoadingUntilComplete(iframe);
|
|
4540
4697
|
if (doc.body && iframeDocMatchesNavigatedSrc(iframe, doc)) {
|
|
4541
4698
|
stopIframeContentApplyWatcher();
|
|
4542
|
-
|
|
4543
|
-
|
|
4699
|
+
if (shouldRefreshOnFinalLoad) {
|
|
4700
|
+
applyActiveVariationHtml();
|
|
4701
|
+
}
|
|
4544
4702
|
}
|
|
4545
4703
|
// Always attempt sync; it has its own readiness checks + retry loop.
|
|
4546
4704
|
syncIframeInteractions('iframe-load');
|
|
@@ -4597,6 +4755,7 @@ var getDefaultAnthropicApiKey = () => {
|
|
|
4597
4755
|
function createVisualEditorMiddleware(options) {
|
|
4598
4756
|
const anthropicApiKey = options?.anthropicApiKey || getDefaultAnthropicApiKey();
|
|
4599
4757
|
const enableGenerateTestApi = options?.enableGenerateTestApi ?? true;
|
|
4758
|
+
const strictObserverFreeze = options?.strictObserverFreeze === true;
|
|
4600
4759
|
const allowedFrameOrigins = options?.allowedFrameOrigins ?? ["*"];
|
|
4601
4760
|
function setFrameHeaders(req, res) {
|
|
4602
4761
|
res.removeHeader("X-Frame-Options");
|
|
@@ -4764,6 +4923,8 @@ function createVisualEditorMiddleware(options) {
|
|
|
4764
4923
|
const url = new URL(req.url || "", "http://localhost");
|
|
4765
4924
|
const targetUrl = url.searchParams.get("url");
|
|
4766
4925
|
const password = url.searchParams.get("password") || "";
|
|
4926
|
+
const strictFreezeParam = (url.searchParams.get("strictObserverFreeze") || "").toLowerCase();
|
|
4927
|
+
const strictObserverFreezeForRequest = strictFreezeParam === "1" || strictFreezeParam === "true" || strictFreezeParam === "yes" ? true : strictFreezeParam === "0" || strictFreezeParam === "false" || strictFreezeParam === "no" ? false : strictObserverFreeze;
|
|
4767
4928
|
if (!targetUrl) {
|
|
4768
4929
|
res.statusCode = 400;
|
|
4769
4930
|
res.end(JSON.stringify({ error: "Missing url parameter" }));
|
|
@@ -4882,9 +5043,12 @@ function createVisualEditorMiddleware(options) {
|
|
|
4882
5043
|
}
|
|
4883
5044
|
);
|
|
4884
5045
|
if (html.includes("</head>")) {
|
|
4885
|
-
html = html.replace(
|
|
4886
|
-
|
|
4887
|
-
|
|
5046
|
+
html = html.replace(
|
|
5047
|
+
"</head>",
|
|
5048
|
+
`${iframeAlwaysShowCss}
|
|
5049
|
+
${iframeAlwaysShowCssGuardScript}
|
|
5050
|
+
</head>`
|
|
5051
|
+
);
|
|
4888
5052
|
}
|
|
4889
5053
|
html = html.replace(
|
|
4890
5054
|
/<meta[^>]+http-equiv=["']?\s*(x-frame-options|content-security-policy)\s*["']?[^>]*>/gi,
|
|
@@ -4898,6 +5062,7 @@ ${consentAllowCss}
|
|
|
4898
5062
|
var TARGET_ORIGIN=${JSON.stringify(origin)};
|
|
4899
5063
|
var TARGET_PAGE_URL=${JSON.stringify(targetUrl)};
|
|
4900
5064
|
var PROXY_PASSWORD=${JSON.stringify(password)};
|
|
5065
|
+
var STRICT_OBSERVER_FREEZE=${JSON.stringify(strictObserverFreezeForRequest)};
|
|
4901
5066
|
window.__CONVERSION_EDITOR_ACTIVE__=true;
|
|
4902
5067
|
function isSkippable(raw){if(!raw||typeof raw!=="string")return true;return raw.startsWith("data:")||raw.startsWith("blob:")||raw.startsWith("javascript:")||raw.startsWith("#");}
|
|
4903
5068
|
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;}}
|
|
@@ -4917,6 +5082,9 @@ try{
|
|
|
4917
5082
|
var wrapped=function(list,obs){
|
|
4918
5083
|
try{
|
|
4919
5084
|
if(!window.__CONVERSION_EDITOR_ACTIVE__)return cb(list,obs);
|
|
5085
|
+
if(STRICT_OBSERVER_FREEZE){
|
|
5086
|
+
return;
|
|
5087
|
+
}
|
|
4920
5088
|
var now=Date.now();
|
|
4921
5089
|
if(now-last<120)return;
|
|
4922
5090
|
last=now;
|
|
@@ -4926,6 +5094,11 @@ try{
|
|
|
4926
5094
|
return new NativeMO(wrapped);
|
|
4927
5095
|
};
|
|
4928
5096
|
window.MutationObserver.prototype=NativeMO.prototype;
|
|
5097
|
+
try{
|
|
5098
|
+
if(STRICT_OBSERVER_FREEZE){
|
|
5099
|
+
console.info("[conversion-proxy] strict MutationObserver freeze active");
|
|
5100
|
+
}
|
|
5101
|
+
}catch(_){}
|
|
4929
5102
|
}
|
|
4930
5103
|
}catch(_){}
|
|
4931
5104
|
}catch(_){}})();</script>`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@accelerated-agency/visual-editor",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Conversion visual editor as a reusable React package",
|
|
6
6
|
"type": "module",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"dev": "tsup --watch",
|
|
26
26
|
"clean": "rm -rf dist",
|
|
27
27
|
"build": "tsup",
|
|
28
|
+
"local:build": "tsup && yarn --cwd ../codebase-server clean && yarn --cwd ../codebase-server dev",
|
|
28
29
|
"watch": "tsup --watch"
|
|
29
30
|
},
|
|
30
31
|
"peerDependencies": {
|