diff options
author | Dorra <dorra.jaoued7@gmail.com> | 2024-04-23 13:42:30 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-23 13:42:30 +0200 |
commit | 8f7b18126ee79d5ad9978a411e9f87e58c1e42b4 (patch) | |
tree | 622272f5616348ca5500c428e87120c7b4150057 | |
parent | 76acc8d9fb4835ef3140d51d5a56ae955ebaeb11 (diff) | |
parent | a7316950aad56e3ed797085df30f49577bee27b3 (diff) |
Merge pull request #11943 from nextcloud/fix/noid/refactor-chat-scrolling
Follow-up: chat scrolling refactoring
-rw-r--r-- | src/components/MessagesList/MessagesList.vue | 146 | ||||
-rw-r--r-- | src/store/messagesStore.js | 4 | ||||
-rw-r--r-- | src/store/participantsStore.js | 29 |
3 files changed, 66 insertions, 113 deletions
diff --git a/src/components/MessagesList/MessagesList.vue b/src/components/MessagesList/MessagesList.vue index 7307c9536..b443364e1 100644 --- a/src/components/MessagesList/MessagesList.vue +++ b/src/components/MessagesList/MessagesList.vue @@ -78,7 +78,6 @@ import uniqueId from 'lodash/uniqueId.js' import Message from 'vue-material-design-icons/Message.vue' import Axios from '@nextcloud/axios' -import { getCapabilities } from '@nextcloud/capabilities' import { subscribe, unsubscribe } from '@nextcloud/event-bus' import moment from '@nextcloud/moment' @@ -241,7 +240,7 @@ export default { return false } - return !!this.$store.getters.findParticipant(this.token, this.$store.getters.getParticipantIdentifier()) + return !!this.$store.getters.findParticipant(this.token, this.conversation) }, isInLobby() { @@ -613,36 +612,37 @@ export default { let isFocused = null if (focusMessageId) { // scroll to message in URL anchor - isFocused = this.focusMessage(focusMessageId, false) + this.focusMessage(focusMessageId) + return } - if (!isFocused && this.visualLastReadMessageId) { + if (this.visualLastReadMessageId) { // scroll to last read message if visible in the current pages isFocused = this.focusMessage(this.visualLastReadMessageId, false, false) } - // TODO: in case the element is not in a page but does exist in the DB, - // we need to scroll up / down to the page where it would exist after - // loading said pages - if (!isFocused) { - // if no anchor was present or the message to focus on did not exist, - // scroll to bottom - this.scrollToBottom({ force: true }) + // Safeguard: scroll to before last read message + const fallbackLastReadMessageId = this.$store.getters.getFirstDisplayableMessageIdBeforeReadMarker(this.token, this.visualLastReadMessageId) + if (fallbackLastReadMessageId) { + isFocused = this.focusMessage(fallbackLastReadMessageId, false, false) + this.$store.dispatch('setVisualLastReadMessageId', { + token: this.token, + id: fallbackLastReadMessageId, + }) + } else { + // This is an ultimate safeguard in case the fallback message is not found too + // scroll to bottom + this.scrollToBottom({ force: true, smooth: true }) + } } - // if no scrollbars, clear read marker directly as scrolling is not possible for the user to clear it - // also clear in case lastReadMessage is zero which is due to an older bug - if (this.visualLastReadMessageId === 0 - || (this.$refs.scroller && this.$refs.scroller.scrollHeight <= this.$refs.scroller.offsetHeight)) { - // clear after a delay, unless scrolling can resume in-between - this.debounceUpdateReadMarkerPosition() - } + // Update read marker in all cases except when the message is from URL anchor + this.debounceUpdateReadMarkerPosition() }, async handleStartGettingMessagesPreconditions() { if (this.token && this.isParticipant && !this.isInLobby) { - // prevent sticky mode before we have loaded anything this.isInitialisingMessages = true const focusMessageId = this.getMessageIdFromHash() @@ -653,70 +653,27 @@ export default { }) if (this.$store.getters.getFirstKnownMessageId(this.token) === null) { - let startingMessageId = 0 - // first time load, initialize important properties - if (focusMessageId === null) { - // Start from unread marker - this.$store.dispatch('setFirstKnownMessageId', { - token: this.token, - id: this.conversation.lastReadMessage, - }) - startingMessageId = this.conversation.lastReadMessage - this.$store.dispatch('setLastKnownMessageId', { - token: this.token, - id: this.conversation.lastReadMessage, - }) - } else { - // Start from message hash - this.$store.dispatch('setFirstKnownMessageId', { - token: this.token, - id: focusMessageId, - }) - startingMessageId = focusMessageId - this.$store.dispatch('setLastKnownMessageId', { - token: this.token, - id: focusMessageId, - }) - } + // Start from message hash or unread marker + const startingMessageId = focusMessageId !== null ? focusMessageId : this.conversation.lastReadMessage + // First time load, initialize important properties + this.$store.dispatch('setFirstKnownMessageId', { token: this.token, id: startingMessageId }) + this.$store.dispatch('setLastKnownMessageId', { token: this.token, id: startingMessageId }) // Get chat messages before last read message and after it await this.getMessageContext(startingMessageId) - const startingMessageFound = this.focusMessage(startingMessageId, false, focusMessageId !== null) - - if (!startingMessageFound) { - const fallbackStartingMessageId = this.$store.getters.getFirstDisplayableMessageIdBeforeReadMarker(this.token, startingMessageId) - this.$store.dispatch('setVisualLastReadMessageId', { - token: this.token, - id: fallbackStartingMessageId, - }) - this.focusMessage(fallbackStartingMessageId, false, false) - } } - let hasScrolled = false - if (focusMessageId === null) { - // if lookForNewMessages will long poll instead of returning existing messages, - // scroll right away to avoid delays - if (!this.hasMoreMessagesToLoad) { - hasScrolled = true - this.$nextTick(() => { - this.scrollToFocusedMessage(focusMessageId) - }) - } - } + this.$nextTick(() => { + // basically scrolling to either the last read message or the message in the URL anchor + // and there is a fallback to scroll to the bottom if the message is not found + this.scrollToFocusedMessage(focusMessageId) + }) this.isInitialisingMessages = false // get new messages await this.lookForNewMessages() - if (focusMessageId === null) { - // don't scroll if lookForNewMessages was polling as we don't want - // to scroll back to the read marker after receiving new messages later - if (!hasScrolled) { - this.scrollToFocusedMessage(focusMessageId) - } - } } else { this.$store.dispatch('cancelLookForNewMessages', { requestId: this.chatIdentifier }) } @@ -749,6 +706,12 @@ export default { if (Axios.isCancel(exception)) { console.debug('The request has been canceled', exception) } + + if (exception?.response?.status === 304 && exception?.response?.data === '') { + // 304 - Not modified + // Empty chat, no messages to load + this.$store.dispatch('loadedMessagesOfConversation', { token: this.token }) + } } this.loadingOldMessages = false }, @@ -1082,7 +1045,7 @@ export default { */ scrollToBottom(options = {}) { this.$nextTick(() => { - if (!this.$refs.scroller) { + if (!this.$refs.scroller || this.isFocusingMessage) { return } @@ -1105,7 +1068,6 @@ export default { newTop = this.$refs.scroller.scrollHeight this.setChatScrolledToBottom(true) } - this.$refs.scroller.scrollTo({ top: newTop, behavior: options?.smooth ? 'smooth' : 'auto', @@ -1124,33 +1086,33 @@ export default { focusMessage(messageId, smooth = true, highlightAnimation = true) { const element = document.getElementById(`message_${messageId}`) if (!element) { + // Message id doesn't exist // TODO: in some cases might need to trigger a scroll up if this is an older message + // https://github.com/nextcloud/spreed/pull/10084 console.warn('Message to focus not found in DOM', messageId) - return false + return false // element not found } console.debug('Scrolling to a focused message programmatically') this.isFocusingMessage = true - this.$nextTick(async () => { - // FIXME: this doesn't wait for the smooth scroll to end - element.scrollIntoView({ - behavior: smooth ? 'smooth' : 'auto', - block: 'center', - inline: 'nearest', - }) - if (this.$refs.scroller && !smooth) { - // scroll the viewport slightly further to make sure the element is about 1/3 from the top - this.$refs.scroller.scrollTop += this.$refs.scroller.offsetHeight / 4 - } - if (highlightAnimation) { - EventBus.emit('highlight-message', messageId) - } - this.isFocusingMessage = false - await this.handleScroll() + element.scrollIntoView({ + behavior: smooth ? 'smooth' : 'auto', + block: 'center', + inline: 'nearest', }) - return true + if (this.$refs.scroller && !smooth) { + // scroll the viewport slightly further to make sure the element is about 1/3 from the top + this.$refs.scroller.scrollTop += this.$refs.scroller.offsetHeight / 4 + } + + if (highlightAnimation) { + EventBus.emit('highlight-message', messageId) + } + this.isFocusingMessage = false + + return true // element found }, /** diff --git a/src/store/messagesStore.js b/src/store/messagesStore.js index de1ab0b83..995b52f8d 100644 --- a/src/store/messagesStore.js +++ b/src/store/messagesStore.js @@ -1410,6 +1410,10 @@ const actions = { async easeMessageList(context, { token }) { context.commit('easeMessageList', { token }) }, + + loadedMessagesOfConversation(context, { token }) { + context.commit('loadedMessagesOfConversation', { token }) + } } export default { state, mutations, getters, actions } diff --git a/src/store/participantsStore.js b/src/store/participantsStore.js index e18b95f7e..2bb34858d 100644 --- a/src/store/participantsStore.js +++ b/src/store/participantsStore.js @@ -230,29 +230,16 @@ const getters = { } if (participantIdentifier.attendeeId) { - if (state.attendees[token][participantIdentifier.attendeeId]) { - return state.attendees[token][participantIdentifier.attendeeId] - } - return null - } - - let foundAttendee = null - Object.keys(state.attendees[token]).forEach((attendeeId) => { - if (participantIdentifier.actorType && participantIdentifier.actorId - && state.attendees[token][attendeeId].actorType === participantIdentifier.actorType - && state.attendees[token][attendeeId].actorId === participantIdentifier.actorId) { - foundAttendee = attendeeId - } - if (participantIdentifier.sessionId && state.attendees[token][attendeeId].sessionIds.includes(participantIdentifier.sessionId)) { - foundAttendee = attendeeId - } - }) - - if (!foundAttendee) { - return null + return state.attendees[token][participantIdentifier.attendeeId] ?? null } - return state.attendees[token][foundAttendee] + // Fallback, sometimes actorId and actorType are set before the attendeeId + return Object.entries(state.attendees[token]).find(([attendeeId, attendee]) => { + return (participantIdentifier.actorType && participantIdentifier.actorId + && attendee.actorType === participantIdentifier.actorType + && attendee.actorId === participantIdentifier.actorId) + || (participantIdentifier.sessionId && attendee.sessionIds.includes(participantIdentifier.sessionId)) + })?.[1] ?? null }, getPeer: (state) => (token, sessionId, userId) => { if (state.peers[token]) { |