diff --git a/server/public/home.html b/server/public/home.html index 316d039..eb35cc9 100644 --- a/server/public/home.html +++ b/server/public/home.html @@ -1134,7 +1134,7 @@
- // gone (inputs are 16px), that height is clean, so we lift the composer by exactly it. keyboardWillShow // fires at the START of the animation, so the CSS bottom-transition slides the composer up WITH the // keyboard (smooth). raw>innerHeight guards the rare device-pixel unit. - function setKb(px){ px=Math.max(0,Math.round(px)); r.style.setProperty('--kb',px+'px'); document.body.classList.toggle('kb-open',px>4); var b=document.getElementById('msgs'); if(b) b.scrollTop=b.scrollHeight; } + function setKb(px){ px=Math.max(0,Math.round(px)); r.style.setProperty('--kb',px+'px'); document.body.classList.toggle('kb-open',px>4); var b=document.getElementById('msgs'); if(b && (b.scrollHeight-b.scrollTop-b.clientHeight)<400) b.scrollTop=b.scrollHeight; } /* #1: only keep pinned to newest when ALREADY near the bottom — replying to an OLD message must not yank to the latest */ window.__bzResetKb=function(){ setKb(0); }; // called on composer blur as a safety net (#1: keyboard closed but lift stayed, leaving empty space after reply-cancel) var KB=C.Plugins&&C.Plugins.Keyboard; if(KB && KB.addListener){ @@ -1500,7 +1500,7 @@ function enablePullRefresh(el, onRefresh){ async function reloadThread(){ if(!selected) return; const kind=selected.kind, id=selected.id; const url=kind==='group'?('/api/messages/thread?group='+encodeURIComponent(id)):('/api/messages/thread?with='+encodeURIComponent(id)); try{ const msgs=await fetch(url).then(r=>r.json()); if(selected&&selected.kind===kind&&selected.id===id&&Array.isArray(msgs)){ THREAD=msgs; THREAD_CACHE.set(kind+':'+id, THREAD.slice()); renderThread(); } }catch(_){} } // ---- Older-history pagination: the thread loads the newest ~500; scrolling to the top fetches the // previous page (messages older than the oldest one loaded) and prepends them, preserving scroll. ---- -const PAGE=500; +const PAGE=40; // load the latest 40 on open (fast render, no 500-node jank); loadOlder() pages in older history on scroll-up. MUST match the server thread limit. let _hasMoreOlder=false, _loadingOlder=false, _olderCd=0; function threadUrl(kind,id,before){ return (kind==='group'?'/api/messages/thread?group='+encodeURIComponent(id):'/api/messages/thread?with='+encodeURIComponent(id))+(before?('&before='+before):''); } async function loadOlder(){ diff --git a/server/routes.js b/server/routes.js index 2eb4ce3..486fb3f 100644 --- a/server/routes.js +++ b/server/routes.js @@ -818,7 +818,7 @@ route('GET', '/api/messages/thread', async (req, res) => { const group = q.get('group'); if (group) { if (!R.conversations.isMember(group, u.id)) return json(res, 403, { error: 'not a member of this group' }); - const rows = R.messages.threadByConversation(group, 500, before); + const rows = R.messages.threadByConversation(group, 40, before); // page size (latest 40 / older via ?before) — matches client PAGE for smooth open + lazy load if (!peek && !before) { R.conversations.markRead(group, u.id); const evt = { type: 'group-read', group, by: u.id, byName: names[u.id] || u.email, at: now() }; @@ -837,7 +837,7 @@ route('GET', '/api/messages/thread', async (req, res) => { const other = R.users.resolve(q.get('with')); // follow a merge redirect so a stale peer id still loads the thread if (!other) return json(res, 400, { error: 'with or group required' }); if (!R.users.inTenant(other, u.team_id)) return json(res, 404, { error: 'no such contact' }); - const rows = R.messages.thread(u.team_id, u.id, other, 500, before); + const rows = R.messages.thread(u.team_id, u.id, other, 40, before); // page size (latest 40 / older via ?before) — matches client PAGE for smooth open + lazy load if (!peek && !before) { R.messages.markRead(u.team_id, u.id, other); try { CHAT.pushToUser(other, { type: 'chat-read', by: u.id }); } catch (_) {} try { CHAT.pushToUser(u.id, { type: 'notif-clear', kind: 'dm', id: other }); } catch (_) {} } // #13 const rxBy = groupReactions(R.reactions.forPair(u.team_id, u.id, other), u.id, names); return json(res, 200, rows.map((m) => { const d = buildMsgDTO(m, names, u.id); d.reactions = dtoReactions(rxBy, m.id); return d; }));