From 597ef472d8c62b6ac52b4e2138be0f06899552a6 Mon Sep 17 00:00:00 2001 From: jolavillette Date: Thu, 27 Aug 2026 13:44:39 +0200 Subject: [PATCH] webui v157: read further back in a room by scrolling up A room opens on its last 20 messages and that was all there ever was: reaching the top of the pane loaded nothing, so anything older was only reachable through the separate history browser. Scrolling near the top now asks for a larger slice. p3HistoryMgr::getMessages takes a count and nothing else -- no cursor, no "before this message" -- and always answers with the newest ones, so the only way to reach older text is to ask for more and let addMessages() drop what is already held. It re-sends what we have, which is the price of that API; the slice is small, and the core keeps ten days at most anyway. An answer shorter than the count asked for means there is nothing older left, and the pane stops asking. The opening slice stays at 20: every room opening pays for it, and on a phone each request is a fresh connection on a core that answers one at a time. Older messages are inserted above what is on screen, which would push the conversation down by their height; that height goes back into scrollTop, so the reader does not move while a slice lands. --- webui-src/app/chat/chat.js | 24 ++++++++++++++ webui-src/app/chat/chat_state.js | 57 ++++++++++++++++++++++++++++++-- webui-src/app/main.js | 4 +-- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/webui-src/app/chat/chat.js b/webui-src/app/chat/chat.js index 15319a5..9faed6e 100644 --- a/webui-src/app/chat/chat.js +++ b/webui-src/app/chat/chat.js @@ -128,6 +128,29 @@ function updateChatStickToBottom(element) { element.scrollHeight - element.scrollTop - element.clientHeight <= CHAT_STICK_SLACK_PX; } +// Far enough from the top to have the next slice ready before the reader gets +// there, close enough not to fire on the first flick of a long conversation. +const CHAT_LOAD_OLDER_AT_PX = 120; + +function loadOlderWhenAtTop(element) { + if (!element || element.scrollTop > CHAT_LOAD_OLDER_AT_PX) return; + + // Older messages are inserted above the ones on screen, which pushes + // everything down by exactly the height they add. Put that height back into + // scrollTop and the reader does not move at all -- without it the pane jumps + // to a different part of the conversation each time a slice lands. + const previousHeight = element.scrollHeight; + const previousTop = element.scrollTop; + + ChatLobbyModel.loadOlderHistory(() => { + requestAnimationFrame(() => { + const pane = document.querySelector('.chat-hub-messages'); + if (!pane) return; + pane.scrollTop = previousTop + (pane.scrollHeight - previousHeight); + }); + }); +} + function renderUserTooltip(gxsId, name) { const details = ChatHubState.gxsDetails[gxsId]; if (!details) return null; @@ -390,6 +413,7 @@ const ChatConversationView = () => { // while the reader drags the pane. event.redraw = false; updateChatStickToBottom(event.target); + loadOlderWhenAtTop(event.target); }, }, ChatLobbyModel.messages diff --git a/webui-src/app/chat/chat_state.js b/webui-src/app/chat/chat_state.js index 9d6b319..dee841f 100644 --- a/webui-src/app/chat/chat_state.js +++ b/webui-src/app/chat/chat_state.js @@ -734,7 +734,12 @@ const ChatLobbyModel = { } }, - loadHistory(id, type) { + // How much of a conversation is on screen when it opens. Small on purpose: + // every room opening pays for it, and on a phone each request is a fresh + // connection on a core that answers one at a time. + HISTORY_PAGE: 20, + + historyChatPeerId(id, type) { const chatPeerId = { broadcast_status_peer_id: '00000000000000000000000000000000', type, @@ -746,20 +751,66 @@ const ChatLobbyModel = { if (type === 3) chatPeerId.lobby_id.xstr64 = id; else if (type === 2) chatPeerId.distant_chat_id = id; else if (type === 1) chatPeerId.peer_id = id; + return chatPeerId; + }, + + loadHistory(id, type) { + this.historyLoaded = this.HISTORY_PAGE; + this.historyExhausted = false; + this.historyLoading = false; rs.rsJsonApiRequest( '/rsHistory/getMessages', { - chatPeerId, - loadCount: 20, + chatPeerId: this.historyChatPeerId(id, type), + loadCount: this.HISTORY_PAGE, }, (data, success) => { if (success && data.msgs) { + if (data.msgs.length < this.HISTORY_PAGE) this.historyExhausted = true; this.addMessages(data.msgs); } } ); }, + + // Reading further back. p3HistoryMgr::getMessages takes a count and nothing + // else -- no cursor, no "before this message" -- and always answers with the + // newest ones, so the only way to see older text is to ask for a bigger slice + // and let addMessages() drop what is already here. It re-sends what we hold, + // which is the price of that API; a page is small and the core keeps ten days + // at most anyway (mMaxStorageDurationSeconds). + loadOlderHistory(done) { + const detail = this.currentLobby; + if (!detail || this.historyLoading || this.historyExhausted) return false; + + const id = this.lastLobbyId; + if (!id) return false; + + this.historyLoading = true; + const wanted = (this.historyLoaded || this.HISTORY_PAGE) + this.HISTORY_PAGE * 2; + + rs.rsJsonApiRequest( + '/rsHistory/getMessages', + { + chatPeerId: this.historyChatPeerId(id, detail.chatType), + loadCount: wanted, + }, + (data, success) => { + this.historyLoading = false; + if (!success || !data.msgs) { + if (done) done(); + return; + } + // Fewer than asked for means the core has nothing older left. + if (data.msgs.length < wanted) this.historyExhausted = true; + this.historyLoaded = wanted; + this.addMessages(data.msgs); + if (done) done(); + } + ); + return true; + }, loadAllHistoryForRoom(lobbyId, callback) { ChatHubState.isHistoryLoading = true; ChatHubState.fullHistoryMessages = []; diff --git a/webui-src/app/main.js b/webui-src/app/main.js index 3b88edc..99ab9a9 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' } }, 'v156'), + m('span.webui-version', { style: { fontSize: '0.7em' } }, 'v157'), 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 v156'), + m('.mobile-status-sheet__version', 'WebUI v157'), ])), ]; },