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 <noreply@anthropic.com>
This commit is contained in:
+12
-5
@@ -731,7 +731,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<script src="/icons.js?v=4"></script>
|
||||
<script>window.__BUILD='2026-07-01-batch22';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);</script>
|
||||
<script>window.__BUILD='2026-07-02-batch23';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);</script>
|
||||
<div class="loading" id="loading">Loading…</div>
|
||||
|
||||
<header>
|
||||
@@ -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='<div class="emoji-tabs">'+EMOJI_CATS.map((c,i)=>'<button type="button" data-i="'+i+'" class="'+(i===emojiCat?'active':'')+'">'+c.icon+'</button>').join('')+'</div><div class="emoji-grid" id="emojiGrid"></div>';
|
||||
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=>'<button type="button">'+e+'</button>').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 };
|
||||
|
||||
Reference in New Issue
Block a user