From a33ed27ffed1eeaae40985992aba8b8f6c8c8396 Mon Sep 17 00:00:00 2001 From: sravan Date: Thu, 2 Jul 2026 11:55:14 +0530 Subject: [PATCH] feat(chat): per-chat drafts + fix emoji picker closing on tab switch - Unsent text is saved per conversation (survives switching chats and reloads) and restored when you reopen the chat; cleared on send. (#1) - Emoji picker: stopPropagation on tab/grid clicks so switching category no longer closes it (the re-render was detaching the clicked node -> outside-click handler fired). (#6) build batch23. Co-Authored-By: Claude Opus 4.8 --- server/public/home.html | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/server/public/home.html b/server/public/home.html index f6a1699..a280d08 100644 --- a/server/public/home.html +++ b/server/public/home.html @@ -731,7 +731,7 @@ - +
Loading…
@@ -1316,10 +1316,12 @@ function positionEmojiPop(pop, anchorEl){ function closeEmoji(){ const pop=document.getElementById('emojiPop'); if(pop) pop.style.display='none'; emojiOpen=false; } function renderEmojiPop(pop){ pop.innerHTML='
'+EMOJI_CATS.map((c,i)=>'').join('')+'
'; - pop.querySelectorAll('.emoji-tabs button').forEach(b=>b.onclick=()=>{ emojiCat=+b.dataset.i; renderEmojiPop(pop); }); + // stopPropagation: switching tabs re-renders the pop (detaching the clicked button), which would + // make the outside-click handler think the click was outside and close the picker. + pop.querySelectorAll('.emoji-tabs button').forEach(b=>b.onclick=(ev)=>{ ev.stopPropagation(); emojiCat=+b.dataset.i; renderEmojiPop(pop); }); const grid=pop.querySelector('#emojiGrid'); grid.innerHTML=EMOJI_CATS[emojiCat].list.trim().split(/\s+/).map(e=>'').join(''); - grid.querySelectorAll('button').forEach(b=>b.onclick=()=>onEmojiPick(b.textContent)); + grid.querySelectorAll('button').forEach(b=>b.onclick=(ev)=>{ ev.stopPropagation(); onEmojiPick(b.textContent); }); } function onEmojiPick(e){ if(emojiMode && emojiMode.react){ reactToMessage(emojiMode.react, e); closeEmoji(); } else { insertEmoji(e); } } function openEmojiForReact(messageId, anchorEl){ openEmoji({ react: messageId }, anchorEl); } @@ -1534,6 +1536,10 @@ function appendBubble(m){ box.scrollTop=box.scrollHeight; } let _openUnread=0; // set by selectChat (the unread count before it's reset) so we can open at the first unread +// Per-conversation drafts: unsent text is kept (across chat switches AND reloads) until sent. +function draftKey(kind,id){ return 'draft_'+((ME&&ME.id)||'')+'_'+kind+':'+id; } +function getDraft(kind,id){ try{ return localStorage.getItem(draftKey(kind,id))||''; }catch(_){ return ''; } } +function setDraft(kind,id,val){ try{ if(val&&val.trim()) localStorage.setItem(draftKey(kind,id), val); else localStorage.removeItem(draftKey(kind,id)); }catch(_){} } async function openConvo(kind,id){ const it=rowFor(kind,id)||{kind,id,name:'Conversation'}; const _unreadN=_openUnread; _openUnread=0; // consume the captured unread count (#3) @@ -1555,8 +1561,9 @@ async function openConvo(kind,id){ const pb=document.getElementById('pollBtn'); if(pb) pb.onclick=()=>openPollModal(id); const inpEl=document.getElementById('msgInput'); if(inpEl){ + const _d=getDraft(kind,id); if(_d){ inpEl.value=_d; autoGrow(inpEl); } // restore unsent draft inpEl.addEventListener('paste', onPaste); - inpEl.addEventListener('input', ()=>autoGrow(inpEl)); + inpEl.addEventListener('input', ()=>{ autoGrow(inpEl); setDraft(kind,id,inpEl.value); }); // keep the draft in sync inpEl.addEventListener('keydown', (e)=>{ if(e.key==='Enter' && !e.shiftKey){ if(mentionItems && mentionItems.length) return; e.preventDefault(); sendMessage(); } }); // Enter sends, Shift+Enter = newline } const ci=document.getElementById('convoInfo'); if(ci) ci.onclick=()=>openGroupInfo(id); @@ -1774,7 +1781,7 @@ async function selectChat(kind,id){ async function sendMessage(){ const inp=document.getElementById('msgInput'); if(!inp) return; const text=inp.value.trim(); if((!text&&!pendingAttach)||!selected) return; - inp.value=''; inp.style.height='auto'; + inp.value=''; inp.style.height='auto'; setDraft(selected.kind, selected.id, ''); // sent → clear the draft const replyTo=replyTarget?replyTarget.id:null; const attachmentId=pendingAttach?pendingAttach.id:null; const payload = selected.kind==='group' ? { group:selected.id, body:text, replyTo, attachmentId, mentions:collectMentions(text) } : { to:selected.id, body:text, replyTo, attachmentId };