musealpha / src-tauri /resources /scripts /hover_overlay.js
asdf98's picture
fix: hover ADD nonce captureId and cleanup old badge DOM to prevent duplicate old overlay handlers
7967f6e verified
/* Refstudio hover overlay: + ADD button on web images.
* v12 — stable one-click capture with captureId nonce.
*/
(function () {
if (window.__muse_hover_v12) return;
window.__muse_hover_v12 = true;
// Remove old badge DOM left by previous injected versions. Anonymous old listeners
// may still exist, but they cannot duplicate backend actions because v12 adds captureId
// and Rust dedupes captureIds.
try { var old = document.getElementById('__muse_add_badge'); if (old) old.remove(); } catch (_) {}
var MIN = 90;
var activeImg = null;
var badge = null;
var showTimer = 0;
var hideTimer = 0;
var capturing = false;
function injectCSS() {
if (document.getElementById('__muse_badge_css_v12')) return;
var s = document.createElement('style'); s.id = '__muse_badge_css_v12';
s.textContent = '#__muse_add_badge{all:initial;position:fixed;z-index:2147483647;display:flex;align-items:center;gap:5px;padding:8px 16px;background:rgba(10,132,255,0.95);color:#fff;border-radius:999px;box-shadow:0 4px 20px rgba(0,0,0,0.4),0 0 0 1px rgba(255,255,255,0.1);font:700 12px/1 -apple-system,BlinkMacSystemFont,"Inter",sans-serif;cursor:pointer;opacity:0;transform:translateY(4px) scale(0.92);transition:opacity .2s,transform .2s,background .1s;pointer-events:auto;letter-spacing:0.01em;user-select:none}#__muse_add_badge.v{opacity:1;transform:none}#__muse_add_badge:hover{background:rgba(10,132,255,1);box-shadow:0 6px 28px rgba(10,132,255,0.4),0 0 0 1px rgba(255,255,255,0.2);transform:scale(1.06)}#__muse_add_badge:active{transform:scale(0.92);background:rgba(10,100,220,1)}#__muse_add_badge svg{width:14px;height:14px;stroke-width:2.5;pointer-events:none}#__mt7{all:initial;position:fixed;left:50%;bottom:24px;transform:translateX(-50%) translateY(8px);z-index:2147483647;padding:9px 16px;border-radius:999px;background:rgba(20,20,20,.97);border:1px solid rgba(255,255,255,.1);box-shadow:0 10px 36px rgba(0,0,0,.5);font:600 12px/1 -apple-system,sans-serif;color:#fff;opacity:0;transition:opacity .16s,transform .16s;pointer-events:none}#__mt7.s{opacity:1;transform:translateX(-50%) translateY(0)}';
(document.head || document.documentElement).appendChild(s);
}
function enc(v) { return encodeURIComponent(v == null ? '' : String(v)); }
function absUrl(s) { try { return new URL(s || '', location.href).href; } catch (_) { return s || ''; } }
function captureId() { return Date.now().toString(36) + '-' + Math.random().toString(36).slice(2); }
function bestImageUrl(img) {
var srcset = img.getAttribute('srcset') || '';
if (srcset) { var best='',bs=0; srcset.split(',').forEach(function(p){var b=p.trim().split(/\s+/),u=b[0],sc=1;if(b[1]){if(b[1].endsWith('w'))sc=parseInt(b[1],10)||1;else if(b[1].endsWith('x'))sc=(parseFloat(b[1])||1)*1000;}if(sc>bs){bs=sc;best=u;}}); if(best) return absUrl(best); }
var u = img.getAttribute('data-full')||img.getAttribute('data-original')||img.getAttribute('data-src')||img.getAttribute('data-lazy-src')||img.currentSrc||img.src;
u = absUrl(u);
try { if(u.includes('pinimg.com')&&u.match(/\/\d+x\//))u=u.replace(/\/\d+x\//,'/originals/'); if(u.includes('artstation.com')&&u.includes('/medium/'))u=u.replace('/medium/','/large/'); } catch(_){}
return u;
}
function toast(m) { var t=document.getElementById('__mt7'); if(!t){t=document.createElement('div');t.id='__mt7';document.documentElement.appendChild(t);} t.textContent=m;t.classList.add('s'); clearTimeout(t._t);t._t=setTimeout(function(){t.classList.remove('s');},2000); }
function fireAction(url, title, w, h) {
if (capturing) return;
capturing = true;
var id = captureId();
var u = 'muse-action://board?captureId='+enc(id)+'&url='+enc(url)+'&source='+enc(location.href)+'&title='+enc(title)+'&w='+enc(w)+'&h='+enc(h);
window.location.href = u;
setTimeout(function(){ capturing = false; }, 1000);
}
function clearHide(){clearTimeout(hideTimer);}
function scheduleHide(){clearHide();hideTimer=setTimeout(hide,600);}
function hide(){if(!badge)return;badge.classList.remove('v');activeImg=null;}
function pos(el,img){var r=img.getBoundingClientRect();var x=r.right-80,y=r.top+10;if(x<r.left+4)x=r.left+4;if(x>innerWidth-90)x=innerWidth-90;if(y<4)y=4;if(y>innerHeight-40)y=innerHeight-40;el.style.left=x+'px';el.style.top=y+'px';}
function show(img){
if(img===activeImg&&badge&&badge.classList.contains('v'))return;
injectCSS(); activeImg=img;
if(!badge){
badge=document.createElement('div');badge.id='__muse_add_badge';
badge.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>ADD';
badge.addEventListener('click',function(e){e.preventDefault();e.stopPropagation();e.stopImmediatePropagation();if(!activeImg)return;var url=bestImageUrl(activeImg);if(!url||url.startsWith('data:')||url.startsWith('blob:')){toast('Cannot capture');return;}fireAction(url,activeImg.alt||activeImg.title||document.title||'Reference',activeImg.naturalWidth||Math.round(activeImg.getBoundingClientRect().width),activeImg.naturalHeight||Math.round(activeImg.getBoundingClientRect().height));toast('✓ Added to Board');hide();},true);
['mousedown','mouseup','pointerdown','pointerup','touchstart','touchend'].forEach(function(ev){badge.addEventListener(ev,function(e){e.preventDefault();e.stopPropagation();e.stopImmediatePropagation();},true);});
badge.addEventListener('mouseenter',clearHide,true);
badge.addEventListener('mouseleave',scheduleHide,true);
document.documentElement.appendChild(badge);
}
pos(badge,activeImg);void badge.offsetHeight;badge.classList.add('v');
}
function isValid(el){if(!el||el.tagName!=='IMG')return false;if(el===badge)return false;var r=el.getBoundingClientRect();return r.width>=MIN&&r.height>=MIN;}
document.addEventListener('mouseover',function(e){var t=e.target;if(badge&&(t===badge||badge.contains(t))){clearHide();return;}if(!isValid(t))return;if(t===activeImg){clearHide();return;}clearHide();clearTimeout(showTimer);showTimer=setTimeout(function(){show(t);},150);},true);
document.addEventListener('mouseout',function(e){var t=e.target;if(!t||t.tagName!=='IMG'||t!==activeImg)return;var rel=e.relatedTarget;if(badge&&rel&&(badge===rel||badge.contains(rel))){clearHide();return;}clearTimeout(showTimer);scheduleHide();},true);
window.addEventListener('scroll',function(){if(!badge||!activeImg)return;var r=activeImg.getBoundingClientRect();if(r.bottom<-20||r.top>innerHeight+20)hide();else pos(badge,activeImg);},{passive:true,capture:true});
})();