- #6: the server echoes your own sent message back over the socket; onChatMessage reset last_status='sent', overwriting a delivered/read that arrived first → the chat-list tick flipped back to single. Track last_msg_id and never downgrade the same message's tick. - #3: pin the 'You' note-to-self chat in its own slot — tinted row + 'Note to self' tag + a divider separating it from the conversation list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+14
-5
@@ -110,6 +110,10 @@
|
|||||||
.chatcol{width:312px;flex:0 0 312px;background:var(--card);border-right:1px solid var(--line);display:flex;flex-direction:column;min-height:0;}
|
.chatcol{width:312px;flex:0 0 312px;background:var(--card);border-right:1px solid var(--line);display:flex;flex-direction:column;min-height:0;}
|
||||||
.chatcol.hidden{display:none;}
|
.chatcol.hidden{display:none;}
|
||||||
.side-sec{font-size:.68rem;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);font-weight:700;padding:.6rem .9rem .25rem;}
|
.side-sec{font-size:.68rem;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);font-weight:700;padding:.6rem .9rem .25rem;}
|
||||||
|
/* Pinned "You" (note-to-self) set apart from the conversation list (#3): tinted + a divider. */
|
||||||
|
.side-div{height:1px;background:var(--line);margin:.4rem .7rem .3rem;}
|
||||||
|
.chat-row.self{background:var(--blue-soft);}
|
||||||
|
.chat-row.self .chat-name::after{content:"Note to self";font-size:.58rem;font-weight:700;color:var(--blue);background:#fff;border:1px solid var(--line);border-radius:99px;padding:.03rem .35rem;margin-left:.4rem;vertical-align:middle;text-transform:none;}
|
||||||
.contact-row,.dir-row{display:flex;align-items:center;gap:.6rem;padding:.5rem .9rem;cursor:pointer;}
|
.contact-row,.dir-row{display:flex;align-items:center;gap:.6rem;padding:.5rem .9rem;cursor:pointer;}
|
||||||
.contact-row:hover,.dir-row:hover{background:var(--blue-soft);}
|
.contact-row:hover,.dir-row:hover{background:var(--blue-soft);}
|
||||||
.contact-row .cr-name,.dir-row .cr-name{font-size:.9rem;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
.contact-row .cr-name,.dir-row .cr-name{font-size:.9rem;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||||
@@ -753,7 +757,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<script src="/icons.js?v=4"></script>
|
<script src="/icons.js?v=4"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@twemoji/api@15.1.0/dist/twemoji.min.js" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@twemoji/api@15.1.0/dist/twemoji.min.js" crossorigin="anonymous"></script>
|
||||||
<script>window.__BUILD='2026-07-02-batch32';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
<script>window.__BUILD='2026-07-02-batch33';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||||
// Render modern (Twemoji) emojis in place of the OS's flat ones. No-op if the CDN didn't load
|
// Render modern (Twemoji) emojis in place of the OS's flat ones. No-op if the CDN didn't load
|
||||||
// (emojis stay as plain Unicode). (#5)
|
// (emojis stay as plain Unicode). (#5)
|
||||||
function twemojify(el){ try{ if(el && window.twemoji) window.twemoji.parse(el, { folder:'svg', ext:'.svg' }); }catch(_){} }</script>
|
function twemojify(el){ try{ if(el && window.twemoji) window.twemoji.parse(el, { folder:'svg', ext:'.svg' }); }catch(_){} }</script>
|
||||||
@@ -1089,7 +1093,7 @@ function avatarHTML(it, big){
|
|||||||
}
|
}
|
||||||
function rowHTML(it){
|
function rowHTML(it){
|
||||||
const active=selected&&selected.kind===it.kind&&selected.id===it.id;
|
const active=selected&&selected.kind===it.kind&&selected.id===it.id;
|
||||||
const cls=['chat-row']; if(active)cls.push('active'); if(it.unread>0)cls.push('unread');
|
const cls=['chat-row']; if(active)cls.push('active'); if(it.unread>0)cls.push('unread'); if(it.self)cls.push('self');
|
||||||
const isG=it.kind==='group';
|
const isG=it.kind==='group';
|
||||||
const draft=getDraft(it.kind,it.id);
|
const draft=getDraft(it.kind,it.id);
|
||||||
let inner;
|
let inner;
|
||||||
@@ -1111,7 +1115,8 @@ function renderChats(filter){
|
|||||||
if(!q){ // "You" pinned at top, then favourites, then the rest
|
if(!q){ // "You" pinned at top, then favourites, then the rest
|
||||||
const self=rows.find(r=>r.self), others=rows.filter(r=>!r.self);
|
const self=rows.find(r=>r.self), others=rows.filter(r=>!r.self);
|
||||||
const favs=others.filter(r=>r.favorite), rest=others.filter(r=>!r.favorite);
|
const favs=others.filter(r=>r.favorite), rest=others.filter(r=>!r.favorite);
|
||||||
let h = self ? rowHTML(self) : '';
|
// "You" (note to self) sits in its own pinned slot, set apart from the conversation list.
|
||||||
|
let h = self ? (rowHTML(self)+(others.length?'<div class="side-div"></div>':'')) : '';
|
||||||
if(favs.length){ h+='<div class="side-sec">'+ic('star',12)+' Favourites</div>'+favs.map(rowHTML).join(''); if(rest.length) h+='<div class="side-sec">All chats</div>'; }
|
if(favs.length){ h+='<div class="side-sec">'+ic('star',12)+' Favourites</div>'+favs.map(rowHTML).join(''); if(rest.length) h+='<div class="side-sec">All chats</div>'; }
|
||||||
h+=rest.map(rowHTML).join('');
|
h+=rest.map(rowHTML).join('');
|
||||||
html = (self||others.length) ? h : '<div class="no-results">No conversations yet.</div>';
|
html = (self||others.length) ? h : '<div class="no-results">No conversations yet.</div>';
|
||||||
@@ -1866,7 +1871,7 @@ async function sendMessage(){
|
|||||||
clearReply(); pendingAttach=null; hideAttach();
|
clearReply(); pendingAttach=null; hideAttach();
|
||||||
{ const ck=selected.kind+':'+selected.id; if(THREAD_CACHE.has(ck)){ const a=THREAD_CACHE.get(ck); if(!a.some(x=>x.id===m.id)) a.push(m); } }
|
{ const ck=selected.kind+':'+selected.id; if(THREAD_CACHE.has(ck)){ const a=THREAD_CACHE.get(ck); if(!a.some(x=>x.id===m.id)) a.push(m); } }
|
||||||
const it=rowFor(selected.kind,selected.id);
|
const it=rowFor(selected.kind,selected.id);
|
||||||
if(it){ it.last_body=m.body||(m.attachment?'📎 '+(m.attachment.name||'Attachment'):''); it.last_at=m.created_at; it.last_from_me=true; it.last_status='sent'; it.unread=0; }
|
if(it){ it.last_body=m.body||(m.attachment?'📎 '+(m.attachment.name||'Attachment'):''); it.last_at=m.created_at; it.last_from_me=true; it.last_status='sent'; it.last_msg_id=m.id; it.unread=0; }
|
||||||
renderChats(searchVal());
|
renderChats(searchVal());
|
||||||
}catch(e){ inp.value=text; toast(e.message||'Could not send'); }
|
}catch(e){ inp.value=text; toast(e.message||'Could not send'); }
|
||||||
}
|
}
|
||||||
@@ -2081,7 +2086,11 @@ function onChatMessage(m){
|
|||||||
else if(kind==='dm' && wasNew) addNotif({icon:'chat', text:'New chat from '+pEsc(m.fromName||'someone'), link:{kind:'dm', id:rid}});
|
else if(kind==='dm' && wasNew) addNotif({icon:'chat', text:'New chat from '+pEsc(m.fromName||'someone'), link:{kind:'dm', id:rid}});
|
||||||
}
|
}
|
||||||
if(it){
|
if(it){
|
||||||
it.last_body=isSys?m.body:(m.body||(m.attachment?'📎 '+(m.attachment.name||'Attachment'):'')); it.last_at=m.created_at; it.last_from_me=(m.from===ME.id); it.last_status=(m.from===ME.id)?'sent':null;
|
it.last_body=isSys?m.body:(m.body||(m.attachment?'📎 '+(m.attachment.name||'Attachment'):'')); it.last_at=m.created_at; it.last_from_me=(m.from===ME.id);
|
||||||
|
// #6: the server echoes my own sent message back — don't let that echo DOWNGRADE a delivered/read
|
||||||
|
// tick that already arrived for this same message (it would flip the list back to a single tick).
|
||||||
|
if(m.from===ME.id){ if(!(it.last_msg_id===m.id && (it.last_status==='delivered'||it.last_status==='read'))) it.last_status='sent'; it.last_msg_id=m.id; }
|
||||||
|
else it.last_status=null;
|
||||||
if(m.from!==ME.id && !isOpen && !isSys) it.unread=(it.unread||0)+1;
|
if(m.from!==ME.id && !isOpen && !isSys) it.unread=(it.unread||0)+1;
|
||||||
}
|
}
|
||||||
if(isOpen){
|
if(isOpen){
|
||||||
|
|||||||
Reference in New Issue
Block a user