(()=>{ const roots=[...document.querySelectorAll('[data-voip-call]')]; if(!roots.length)return; const csrfToken=()=>{ const item=document.cookie.split(';').map(value=>value.trim()).find(value=>value.startsWith('csrftoken=')); return item?decodeURIComponent(item.slice('csrftoken='.length)):''; }; const menuFor=root=>root._voipMenu; const statusFor=root=>root._voipStatus; const positionMenu=root=>{ const menu=menuFor(root); const trigger=root.querySelector('[data-voip-call-trigger]'); if(!menu||menu.hidden)return; if(window.matchMedia('(max-width:560px)').matches){ menu.style.removeProperty('top'); menu.style.removeProperty('left'); return; } const rect=trigger.getBoundingClientRect(); const gap=10; const edge=12; const width=menu.offsetWidth; const height=menu.offsetHeight; const top=rect.bottom+gap+height<=window.innerHeight-edge?rect.bottom+gap:Math.max(edge,rect.top-height-gap); const left=Math.min(Math.max(edge,rect.right-width),window.innerWidth-width-edge); menu.style.top=`${Math.round(top)}px`; menu.style.left=`${Math.round(left)}px`; }; const setOpen=(root,open,{restoreFocus=false}={})=>{ const menu=menuFor(root); const trigger=root.querySelector('[data-voip-call-trigger]'); if(!menu||!trigger)return; menu.hidden=!open; menu.classList.toggle('is-open',open); trigger.classList.toggle('is-open',open); trigger.setAttribute('aria-expanded',String(open)); if(open){ positionMenu(root); requestAnimationFrame(()=>menu.querySelector('[data-voip-method]')?.focus()); }else{ menu.style.removeProperty('top'); menu.style.removeProperty('left'); if(restoreFocus)trigger.focus(); } }; const closeOthers=current=>roots.forEach(root=>{if(root!==current)setOpen(root,false);}); const notify=(root,message,state)=>{ const status=statusFor(root); clearTimeout(status._hideTimer); status.textContent=message; status.className=`voip-call-status is-visible is-${state}`; if(state!=='loading')status._hideTimer=setTimeout(()=>status.classList.remove('is-visible'),4200); }; const call=async(root,method)=>{ const trigger=root.querySelector('[data-voip-call-trigger]'); const buttons=[trigger,...menuFor(root).querySelectorAll('button')]; setOpen(root,false); buttons.forEach(button=>button.disabled=true); trigger.classList.add('is-loading'); notify(root,'در حال ارسال درخواست تماس…','loading'); try{ const response=await fetch(root.dataset.callUrl,{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-CSRFToken':csrfToken(),'Accept':'application/json'},body:JSON.stringify({method})}); const data=await response.json().catch(()=>({})); if(!response.ok||!data.success)throw new Error(); notify(root,data.message||'درخواست تماس با موفقیت ارسال شد.','success'); trigger.classList.add('is-success'); setTimeout(()=>trigger.classList.remove('is-success'),1800); }catch(error){ notify(root,'برقراری تماس ناموفق بود.','error'); }finally{ trigger.classList.remove('is-loading'); buttons.forEach(button=>button.disabled=false); } }; roots.forEach((root,index)=>{ const menu=root.querySelector('[data-voip-call-menu]'); const status=root.querySelector('[data-voip-call-status]'); const trigger=root.querySelector('[data-voip-call-trigger]'); menu.id=`voip-call-menu-${index+1}`; trigger.setAttribute('aria-controls',menu.id); root._voipMenu=menu; root._voipStatus=status; menu._voipRoot=root; document.body.append(menu,status); }); document.addEventListener('click',event=>{ const methodButton=event.target.closest('[data-voip-method]'); if(methodButton){ const root=methodButton.closest('[data-voip-call-menu]')._voipRoot; call(root,methodButton.dataset.voipMethod); return; } const trigger=event.target.closest('[data-voip-call-trigger]'); if(trigger){ const root=trigger.closest('[data-voip-call]'); const methods=menuFor(root).querySelectorAll('[data-voip-method]'); if(methods.length===1){call(root,methods[0].dataset.voipMethod);return;} const opening=menuFor(root).hidden; closeOthers(root); setOpen(root,opening); return; } if(!event.target.closest('[data-voip-call-menu]'))roots.forEach(root=>setOpen(root,false)); }); document.addEventListener('keydown',event=>{ if(event.key==='Escape')roots.forEach(root=>{if(!menuFor(root).hidden)setOpen(root,false,{restoreFocus:true});}); }); window.addEventListener('resize',()=>roots.forEach(positionMenu)); window.addEventListener('scroll',()=>roots.forEach(positionMenu),true); })();