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'), ])), ]; },