fix(gif): send a GIF as the active reply; show GIF (not URL) in reply bar/quote (batch89)

sendGif ignored replyTarget, so picking a GIF while replying sent a plain message instead of a
reply. It now carries replyTo and clears the reply, like sendMessage. Reply bar + quote show
'GIF' instead of the raw media URL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 15:54:31 +05:30
parent a172214c44
commit c8f383cbcb
+9 -5
View File
@@ -1078,7 +1078,7 @@
</head>
<body>
<script src="/icons.js?v=6"></script>
<script>window.__BUILD='2026-07-15-batch88';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
<script>window.__BUILD='2026-07-15-batch89';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
// Emoji are rendered with the OS's own (colour) emoji font — instant, zero network.
//
// We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from
@@ -1851,7 +1851,7 @@ function bubbleHTML(m){
// #1: the sender chip is clickable → mini profile card (with a Message button that opens their DM).
const sender=(convoIsGroup && !mine && m.fromName)?'<div class="sender profile-open" data-uid="'+pEsc(m.from)+'" title="View profile">'+senderAvatar(m.from, m.fromName)+'<span>'+pEsc(m.fromName)+'</span></div>':'';
let quote='';
if(m.reply){ const t=replyTint(m.reply.from||m.reply.fromName); quote='<div class="quote" data-jid="'+pEsc(m.reply.id||'')+'" data-jat="'+(m.reply.at||0)+'" title="Go to message" style="background:'+t[0]+';border-left-color:'+t[1]+'"><b style="color:'+t[1]+'">'+pEsc(m.reply.fromName||'')+'</b>: '+pEsc(m.reply.body)+'</div>'; }
if(m.reply){ const t=replyTint(m.reply.from||m.reply.fromName); const rb=isGifUrl(m.reply.body)?'🎞️ GIF':m.reply.body; quote='<div class="quote" data-jid="'+pEsc(m.reply.id||'')+'" data-jat="'+(m.reply.at||0)+'" title="Go to message" style="background:'+t[0]+';border-left-color:'+t[1]+'"><b style="color:'+t[1]+'">'+pEsc(m.reply.fromName||'')+'</b>: '+pEsc(rb)+'</div>'; }
const reacts=(m.reactions&&m.reactions.length)?'<div class="reacts">'+m.reactions.map(r=>'<button class="react-chip'+(r.mine?' mine':'')+'" data-id="'+pEsc(m.id)+'" data-emoji="'+pEsc(r.emoji)+'" title="'+pEsc((r.who||[]).join(', '))+'">'+pEsc(r.emoji)+' '+r.count+'</button>').join('')+'</div>':'';
const att = m.attachment ? (m.attachment.isImage
? '<img class="att-img" src="/files/'+pEsc(m.attachment.id)+'" data-img="/files/'+pEsc(m.attachment.id)+'" alt="'+pEsc(m.attachment.name)+'" title="Click to view">'
@@ -1963,7 +1963,8 @@ function setReply(m){
replyTarget=m;
const bar=document.getElementById('replyBar'); if(!bar) return;
const who=m.from===ME.id?'yourself':(m.fromName||currentName()||'them');
bar.innerHTML='<span>'+ic('reply',13)+' Replying to <b>'+pEsc(who)+'</b>: '+pEsc(m.body.length>80?m.body.slice(0,80)+'…':m.body)+'</span><span class="rx" id="replyCancel">'+ic('x',15)+'</span>';
const rprev=isGifUrl(m.body)?'🎞️ GIF':(m.body.length>80?m.body.slice(0,80)+'…':m.body);
bar.innerHTML='<span>'+ic('reply',13)+' Replying to <b>'+pEsc(who)+'</b>: '+pEsc(rprev)+'</span><span class="rx" id="replyCancel">'+ic('x',15)+'</span>';
bar.style.display='flex';
document.getElementById('replyCancel').onclick=clearReply;
const inp=document.getElementById('msgInput'); if(inp) inp.focus();
@@ -2137,12 +2138,15 @@ async function loadGifs(q){
grid.innerHTML=gifs.map(g=>'<button type="button" class="gif-cell" data-url="'+pEsc(g.url)+'" title="'+pEsc(g.title||'GIF')+'"><img src="'+pEsc(g.preview||g.url)+'" loading="lazy" alt=""></button>').join('');
grid.querySelectorAll('.gif-cell').forEach(b=>b.onclick=(ev)=>{ ev.stopPropagation(); sendGif(b.dataset.url); });
}
// GIFs send immediately (like a sticker), hotlinked to GIPHY's CDN per their terms.
// GIFs send immediately (like a sticker), hotlinked to GIPHY's CDN per their terms. If a reply is armed,
// the GIF is sent AS that reply (it was going out as a plain message before).
async function sendGif(url){
if(!url || !selected) return;
closeEmoji();
const kind=selected.kind, id=selected.id;
try{ const m=await postJSON('/api/messages', kind==='group'?{group:id, body:url}:{to:id, body:url}); if(m){ /* echo arrives via WS */ } }
const replyTo=replyTarget?replyTarget.id:null;
clearReply();
try{ await postJSON('/api/messages', kind==='group'?{group:id, body:url, replyTo}:{to:id, body:url, replyTo}); }
catch(e){ toast(e.message||'Could not send GIF'); }
}
function onEmojiPick(e){ if(emojiMode && emojiMode.react){ reactToMessage(emojiMode.react, e); closeEmoji(); } else { insertEmoji(e); } }