From e67c4c3170102ed29385b4f1fc9063dda7dd6453 Mon Sep 17 00:00:00 2001 From: jolavillette Date: Thu, 27 Aug 2026 13:35:05 +0200 Subject: [PATCH] webui v156: a chat room dragged the reader back to the last message The message pane scrolled to the bottom from its onupdate, unconditionally. That hook runs on every redraw of the chat hub -- an incoming message, a poll answer, a keystroke in the composer -- so scrolling up to read anything older was undone a few tens of milliseconds later, every time. It now follows the reader: the pane stays pinned while it is already at the bottom, stops as soon as it is scrolled away from it, and pins again when it comes back. Sending a message still jumps down whatever the state, and switching room opens on its last message, since the pane is reused rather than recreated and its oncreate does not run again. The scroll handler sets event.redraw = false: mithril redraws after every handler by default, and this one fires continuously while the pane is dragged. --- webui-src/app/chat/chat.js | 38 ++++++++++++++++++++++++++++++++++++-- webui-src/app/main.js | 4 ++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/webui-src/app/chat/chat.js b/webui-src/app/chat/chat.js index 8b2ade7..15319a5 100644 --- a/webui-src/app/chat/chat.js +++ b/webui-src/app/chat/chat.js @@ -110,6 +110,24 @@ function scrollChatToBottom() { }, 50); } +// Whether the conversation is still pinned to its last message. It starts +// pinned, follows the user's own scrolling, and decides whether a redraw is +// allowed to jump back down -- without it, reading anything older is +// impossible: the pane redraws on every event and every poll answer, and each +// one dragged the reader back to the bottom a few tens of milliseconds later. +let chatStickToBottom = true; + +// A little slack, because a reader who stops one line short of the end still +// means "keep following", and because scrollTop is fractional on zoomed or +// high-density displays. +const CHAT_STICK_SLACK_PX = 80; + +function updateChatStickToBottom(element) { + if (!element) return; + chatStickToBottom = + element.scrollHeight - element.scrollTop - element.clientHeight <= CHAT_STICK_SLACK_PX; +} + function renderUserTooltip(gxsId, name) { const details = ChatHubState.gxsDetails[gxsId]; if (!details) return null; @@ -359,8 +377,20 @@ const ChatConversationView = () => { m( '.chat-hub-messages' + (isRoom ? '.compact-container' : ''), { - oncreate: () => scrollChatToBottom(), - onupdate: () => scrollChatToBottom(), + oncreate: () => { + chatStickToBottom = true; + scrollChatToBottom(); + }, + onupdate: () => { + if (chatStickToBottom) scrollChatToBottom(); + }, + onscroll: (event) => { + // A scroll must not trigger a redraw: mithril redraws after + // every handler by default, and this one fires continuously + // while the reader drags the pane. + event.redraw = false; + updateChatStickToBottom(event.target); + }, }, ChatLobbyModel.messages ), @@ -1220,6 +1250,10 @@ const Layout = { if (lobbyId && ChatHubState.selectedRoomId !== lobbyId) { ChatHubState.mobilePane = 'detail'; ChatHubState.selectedRoomId = lobbyId; + // Another room, another conversation: it opens on its last message, + // whatever the reader had scrolled to in the previous one. The pane + // itself is reused rather than recreated, so its oncreate does not run. + chatStickToBottom = true; ChatLobbyModel.loadLobby(lobbyId); } else if (!lobbyId) { ChatHubState.mobilePane = 'list'; diff --git a/webui-src/app/main.js b/webui-src/app/main.js index 5240d06..3b88edc 100644 --- a/webui-src/app/main.js +++ b/webui-src/app/main.js @@ -135,7 +135,7 @@ const navbar = () => { ? 'Connected to RetroShare Core' : 'Connection Lost', }), - m('span.webui-version', { style: { fontSize: '0.7em' } }, 'v153'), + m('span.webui-version', { style: { fontSize: '0.7em' } }, 'v156'), m('i.fas.fa-sync-alt.refresh-icon', { style: { cursor: 'pointer', fontSize: '0.8em' }, onclick: () => window.location.reload(true), @@ -258,7 +258,7 @@ const MobileStatus = () => { m('small', statusbar.formatBytes(state.totalOut)), ]), ]), - m('.mobile-status-sheet__version', 'WebUI v153'), + m('.mobile-status-sheet__version', 'WebUI v156'), ])), ]; },