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> </head>
<body> <body>
<script src="/icons.js?v=6"></script> <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. // 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 // 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). // #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>':''; 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=''; 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 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 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">' ? '<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; replyTarget=m;
const bar=document.getElementById('replyBar'); if(!bar) return; const bar=document.getElementById('replyBar'); if(!bar) return;
const who=m.from===ME.id?'yourself':(m.fromName||currentName()||'them'); 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'; bar.style.display='flex';
document.getElementById('replyCancel').onclick=clearReply; document.getElementById('replyCancel').onclick=clearReply;
const inp=document.getElementById('msgInput'); if(inp) inp.focus(); 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.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); }); 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){ async function sendGif(url){
if(!url || !selected) return; if(!url || !selected) return;
closeEmoji(); closeEmoji();
const kind=selected.kind, id=selected.id; 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'); } catch(e){ toast(e.message||'Could not send GIF'); }
} }
function onEmojiPick(e){ if(emojiMode && emojiMode.react){ reactToMessage(emojiMode.react, e); closeEmoji(); } else { insertEmoji(e); } } function onEmojiPick(e){ if(emojiMode && emojiMode.react){ reactToMessage(emojiMode.react, e); closeEmoji(); } else { insertEmoji(e); } }