voip click2calll
This commit is contained in:
1 parent
cb92abbb41
commit
38b1cf06df
18 files changed
+543
-26
No files matched your search
@@ -0,0 +1,119 @@
|
||||
(()=>{
|
||||
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);
|
||||
})();
|
||||
Reference in new issue
Block a user