From 46818f57b6db89f6ceaf27793c9e4595a5973220 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Tue, 9 Sep 2025 18:10:23 +0200 Subject: [PATCH 1/2] fix(chat): do not highlight messages, if they're not focused Signed-off-by: Maksim Sukharev --- src/components/MessagesList/MessagesList.vue | 17 +++++++++-------- src/components/Quote.vue | 2 +- .../SearchMessages/SearchMessageItem.vue | 2 +- src/composables/useGetMessages.ts | 9 +++++---- src/services/EventBus.ts | 2 +- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/components/MessagesList/MessagesList.vue b/src/components/MessagesList/MessagesList.vue index 712e7725f7..0e11c7acda 100644 --- a/src/components/MessagesList/MessagesList.vue +++ b/src/components/MessagesList/MessagesList.vue @@ -565,20 +565,20 @@ export default { let isFocused = null if (focusMessageId) { // scroll to message in URL anchor - this.focusMessage(focusMessageId, false) + this.focusMessage({ messageId: focusMessageId, smooth: false, highlight: true }) return } if (this.visualLastReadMessageId) { // scroll to last read message if visible in the current pages - isFocused = this.focusMessage(this.visualLastReadMessageId, false, false) + isFocused = this.focusMessage({ messageId: this.visualLastReadMessageId, smooth: false, highlight: false }) } if (!isFocused) { // Safeguard 1: scroll to first visible message before the read marker const fallbackLastReadMessageId = this.$store.getters.getFirstDisplayableMessageIdBeforeReadMarker(this.token, this.visualLastReadMessageId) if (fallbackLastReadMessageId) { - isFocused = this.focusMessage(fallbackLastReadMessageId, false, false) + isFocused = this.focusMessage({ messageId: fallbackLastReadMessageId, smooth: false, highlight: false }) } if (!isFocused) { @@ -909,12 +909,13 @@ export default { /** * Temporarily highlight the given message id with a fade out effect. * - * @param {number} messageId message id - * @param {boolean} smooth true to smooth scroll, false to jump directly - * @param {boolean} highlightAnimation true to highlight and set focus to the message + * @param payload function payload + * @param payload.messageId message id + * @param payload.smooth true to smooth scroll, false to jump directly + * @param payload.highlight true to highlight and set focus to the message * @return {boolean} true if element was found, false otherwise */ - focusMessage(messageId, smooth = true, highlightAnimation = true) { + focusMessage({ messageId, smooth = true, highlight = true }) { const element = document.getElementById(`message_${messageId}`) if (!element) { // Message id doesn't exist @@ -963,7 +964,7 @@ export default { this.checkChatNotScrollable() - if (highlightAnimation && scrollElement === element) { + if (highlight && scrollElement === element) { // element is visible, highlight it element.classList.add('message--highlighted') } diff --git a/src/components/Quote.vue b/src/components/Quote.vue index 4e54ce34fb..386cee4369 100644 --- a/src/components/Quote.vue +++ b/src/components/Quote.vue @@ -137,7 +137,7 @@ function handleQuoteClick() { if (route.hash === hash.value) { // Already on this message route, just trigger highlight - EventBus.emit('focus-message', message.id) + EventBus.emit('focus-message', { messageId: message.id }) } } diff --git a/src/components/RightSidebar/SearchMessages/SearchMessageItem.vue b/src/components/RightSidebar/SearchMessages/SearchMessageItem.vue index 06eb53949f..4fe46e1147 100644 --- a/src/components/RightSidebar/SearchMessages/SearchMessageItem.vue +++ b/src/components/RightSidebar/SearchMessages/SearchMessageItem.vue @@ -76,7 +76,7 @@ const active = computed(() => { function handleResultClick() { if (route.hash === '#message_' + props.messageId) { // Already on this message route, just trigger highlight - EventBus.emit('focus-message', props.messageId) + EventBus.emit('focus-message', { messageId: props.messageId }) } } diff --git a/src/composables/useGetMessages.ts b/src/composables/useGetMessages.ts index 18c263d395..95e56ab51d 100644 --- a/src/composables/useGetMessages.ts +++ b/src/composables/useGetMessages.ts @@ -244,7 +244,7 @@ export function useGetMessagesProvider() { : conversationLastMessageId.value } - await checkContextAndFocusMessage(to.params.token, contextMessageId.value, contextThreadId.value) + await checkContextAndFocusMessage(to.params.token, contextMessageId.value, contextThreadId.value, focusMessageId !== null) } /** @@ -253,8 +253,9 @@ export function useGetMessagesProvider() { * @param token * @param messageId * @param threadId + * @param highlight */ - async function checkContextAndFocusMessage(token: string, messageId: number, threadId: number) { + async function checkContextAndFocusMessage(token: string, messageId: number, threadId: number, highlight: boolean = false) { if (!chatStore.hasMessage(token, { messageId, threadId })) { // message not found in the list, need to fetch it first await getMessageContext(token, messageId, threadId) @@ -268,7 +269,7 @@ export function useGetMessagesProvider() { // need some delay (next tick is too short) to be able to run // after the browser's native "scroll to anchor" from the hash window.setTimeout(() => { - EventBus.emit('focus-message', messageId) + EventBus.emit('focus-message', { messageId, highlight }) }, 2) } @@ -316,7 +317,7 @@ export function useGetMessagesProvider() { return } } else { - await checkContextAndFocusMessage(token, contextMessageId.value, contextThreadId.value) + await checkContextAndFocusMessage(token, contextMessageId.value, contextThreadId.value, focusMessageId !== null) } isInitialisingMessages.value = false diff --git a/src/services/EventBus.ts b/src/services/EventBus.ts index ac6887d415..4e0c42ef12 100644 --- a/src/services/EventBus.ts +++ b/src/services/EventBus.ts @@ -28,7 +28,7 @@ export type Events = { 'editing-message': void 'editing-message-processing': { messageId: number, value: boolean } 'focus-chat-input': void - 'focus-message': number // TODO: listener method can receive ...[messageId, smooth, highlightAnimation] + 'focus-message': { messageId: number, smooth?: boolean, highlight?: boolean } 'forbidden-route': { error: string } 'joined-conversation': { token: string } 'poll-drafts-open': { token: string, selector?: string } From f98ce145bbce64e166ff500c98133d80242f7b84 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Tue, 9 Sep 2025 18:32:30 +0200 Subject: [PATCH 2/2] fix(MessagesList): ensure parent exists - fix "Cannot read properties of null (reading 'parentElement')" Signed-off-by: Maksim Sukharev --- src/components/MessagesList/MessagesList.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/MessagesList/MessagesList.vue b/src/components/MessagesList/MessagesList.vue index 0e11c7acda..4251c1c2eb 100644 --- a/src/components/MessagesList/MessagesList.vue +++ b/src/components/MessagesList/MessagesList.vue @@ -928,7 +928,8 @@ export default { let scrollElement = element if (this.isChatVisible && scrollElement.offsetParent === null) { console.debug('Message to focus is hidden, scrolling to its nearest visible parent', messageId) - scrollElement = scrollElement.closest('ul[style="display: none;"]').parentElement + const closestParent = scrollElement.closest('ul[style="display: none;"]') ?? scrollElement.closest('ul') + scrollElement = closestParent.parentElement } console.debug('Scrolling to a focused message programmatically')