Moderation UX: Block on DM contact info + visible report-resolved notice

Tester feedback:
1. "Block contact not on the contact info." The earlier Block button was only on
   the group-sender mini-profile, which a 1:1 chat never opens. A DM's info panel
   is openSharedItems('dm',…) (tap the conversation header). Added a full-width
   "Block contact / Unblock contact" button there.
2. "Reporter gets no notification on resolution." Delivery was actually working
   (verified: the reporter receives the 'report-resolved' event over the chat WS),
   but the client only added a silent bell entry — easy to miss — and a self-
   resolve (same admin reported + resolved) is intentionally skipped. Made it a
   visible toast + ping for an online reporter (report-new likewise toasts admins).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 22:39:13 +05:30
parent 928119725e
commit 18edc5dbfb
+5 -2
View File
@@ -1932,10 +1932,13 @@ async function openSharedItems(kind,id,name){
const r0=rowFor(kind,id); const fav=!!(r0&&r0.favorite); const online=!!(r0&&r0.online);
const ov=document.createElement('div'); ov.className='modal-ov'; ov.id='sharedModal';
ov.innerHTML='<div class="modal gi"><div class="gi-head" style="margin-bottom:.6rem"><button class="gi-photo" id="shPhoto" title="View photo"><span class="avatar" style="width:46px;height:46px;flex:0 0 46px;background:'+avColor(name)+'">'+pEsc(initials(name||'?'))+((r0&&r0.avatar)?'<img class="av-img" src="'+pEsc(r0.avatar)+'" alt="" onerror="this.remove()">':'')+'</span></button><div class="gi-name"><div class="gi-title-row"><span class="gi-title">'+pEsc(name||'')+'</span><button class="fav-star'+(fav?' on':'')+'" id="shFav" title="'+(fav?'Remove from favourites':'Add to favourites')+'">'+ic('star',16)+'</button></div><div class="gi-sub"><span class="st-dot '+statusCls(r0)+'"></span>'+statusLabel(r0)+'</div></div><button class="iconbtn" id="shClose">'+ic('x',18)+'</button></div>'
+mediaRowHTML()+'</div>';
+mediaRowHTML()
+((kind==='dm' && id!==(ME&&ME.id))?'<button id="shBlock" style="width:100%;margin-top:.7rem;display:inline-flex;align-items:center;justify-content:center;gap:.4rem;border:1px solid '+(BLOCKED.has(id)?'#c7d6f0':'#f1c4c4')+';border-radius:10px;background:#fff;color:'+(BLOCKED.has(id)?'#1F3B73':'#b91c1c')+';font-size:.9rem;font-weight:600;padding:.6rem;cursor:pointer">'+ic('ban',16)+' '+(BLOCKED.has(id)?'Unblock contact':'Block contact')+'</button>':'')
+'</div>';
document.body.appendChild(ov);
ov.onclick=e=>{ if(e.target===ov) ov.remove(); };
ov.querySelector('#shClose').onclick=()=>ov.remove();
const bb=ov.querySelector('#shBlock'); if(bb) bb.onclick=()=>{ ov.remove(); toggleBlock(id, name); }; // block/unblock this contact from their info panel
const sp=ov.querySelector('#shPhoto'); if(sp) sp.onclick=()=>{ const r=rowFor(kind,id)||r0; if(r&&r.avatar) openLightbox(r.avatar); else toast('No profile photo'); }; // #5: click the DP in the info view to preview it
const fb=ov.querySelector('#shFav'); if(fb) fb.onclick=async()=>{ const r=rowFor(kind,id); const on=!(r&&r.favorite); if(r) r.favorite=on; fb.classList.toggle('on',on); fb.title=on?'Remove from favourites':'Add to favourites'; try{ await postJSON('/api/favorites',{kind,id,on}); }catch(_){} renderChats(searchVal()); };
wireMediaEntry(ov, kind, id, name);
@@ -4754,7 +4757,7 @@ function connectChatWs(){
if(_chatReconnectT){ clearTimeout(_chatReconnectT); _chatReconnectT=null; }
chatWs=new WebSocket((location.protocol==='https:'?'wss://':'ws://')+location.host+'/ws');
chatWs.onopen=()=>{ try{ chatWs.send(JSON.stringify({type:'chat-hello'})); }catch(_){} if(_chatConnectedOnce) resyncChat(); _chatConnectedOnce=true; };
chatWs.onmessage=(e)=>{ let d; try{ d=JSON.parse(e.data); }catch(_){ return; } if(d.type==='chat-message' && d.message) onChatMessage(d.message); else if(d.type==='chat-deleted') onChatDeleted(d); else if(d.type==='chat-hidden') onChatHidden(d); else if(d.type==='chat-pinned') onChatPinned(d); else if(d.type==='chat-edited') onChatEdited(d); else if(d.type==='chat-reaction') onChatReaction(d); else if(d.type==='poll-update' && d.poll) onPollUpdate(d); else if(d.type==='chat-read') onChatRead(d); else if(d.type==='chat-delivered') onChatDelivered(d); else if(d.type==='group-read') onGroupRead(d); else if(d.type==='group-call') onGroupCall(d); else if(d.type==='dm-call') onDmCall(d); else if(d.type==='call-taken') onCallTaken(d); else if(d.type==='call-answered') onCallAnswered(d); else if(d.type==='presence') onPresence(d); else if(d.type==='chat-typing') onTyping(d); else if(d.type==='notif-clear') onNotifClear(d); else if(d.type==='group-update') onGroupUpdate(d); else if(d.type==='call-invite') showCallInvite(d.room, d.byName); else if(d.type==='meeting-invite') showMeetingInvite(d.meeting); else if(d.type==='meeting-reminder') showMeetingReminder(d.meeting); else if(d.type==='meeting-cancelled') showMeetingCancelled(d.meeting); else if(d.type==='group-role') onGroupRole(d); else if(d.type==='report-resolved') addNotif({icon:'flag', text:'An admin reviewed a message you reported.'}); else if(d.type==='report-new') addNotif({icon:'flag', text:'A message was reported — open “Reported messages” to review.'}); };
chatWs.onmessage=(e)=>{ let d; try{ d=JSON.parse(e.data); }catch(_){ return; } if(d.type==='chat-message' && d.message) onChatMessage(d.message); else if(d.type==='chat-deleted') onChatDeleted(d); else if(d.type==='chat-hidden') onChatHidden(d); else if(d.type==='chat-pinned') onChatPinned(d); else if(d.type==='chat-edited') onChatEdited(d); else if(d.type==='chat-reaction') onChatReaction(d); else if(d.type==='poll-update' && d.poll) onPollUpdate(d); else if(d.type==='chat-read') onChatRead(d); else if(d.type==='chat-delivered') onChatDelivered(d); else if(d.type==='group-read') onGroupRead(d); else if(d.type==='group-call') onGroupCall(d); else if(d.type==='dm-call') onDmCall(d); else if(d.type==='call-taken') onCallTaken(d); else if(d.type==='call-answered') onCallAnswered(d); else if(d.type==='presence') onPresence(d); else if(d.type==='chat-typing') onTyping(d); else if(d.type==='notif-clear') onNotifClear(d); else if(d.type==='group-update') onGroupUpdate(d); else if(d.type==='call-invite') showCallInvite(d.room, d.byName); else if(d.type==='meeting-invite') showMeetingInvite(d.meeting); else if(d.type==='meeting-reminder') showMeetingReminder(d.meeting); else if(d.type==='meeting-cancelled') showMeetingCancelled(d.meeting); else if(d.type==='group-role') onGroupRole(d); else if(d.type==='report-resolved'){ addNotif({icon:'flag', text:'An admin reviewed a message you reported.'}); try{ playPing(); }catch(_){} try{ if(window.BZToast) BZToast.info('An admin reviewed a message you reported.'); else toast('An admin reviewed a message you reported.'); }catch(_){} } else if(d.type==='report-new'){ addNotif({icon:'flag', text:'A message was reported — open “Reported messages” to review.'}); try{ if(window.BZToast) BZToast.info('A message was reported — review it in Reported messages.'); }catch(_){} } };
chatWs.onclose=()=>{ if(_chatReconnectT) clearTimeout(_chatReconnectT); _chatReconnectT=setTimeout(connectChatWs, 3000); }; // auto-reconnect (single pending timer)
}catch(_){}
}