From b7bc2d4d43a26e2ae7a06a244a279a87839f0534 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Mon, 27 Apr 2026 19:07:31 +0200 Subject: [PATCH] fix(search): immediately show filtered results - do not replace rendered list with 'Loading' while waiting for server response Signed-off-by: Maksim Sukharev --- src/components/LeftSidebar/LeftSidebar.vue | 14 ++++------- .../SearchConversationsResults.vue | 24 +++++++------------ 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/components/LeftSidebar/LeftSidebar.vue b/src/components/LeftSidebar/LeftSidebar.vue index 02f48cf37f8..aafd09b18d5 100644 --- a/src/components/LeftSidebar/LeftSidebar.vue +++ b/src/components/LeftSidebar/LeftSidebar.vue @@ -262,7 +262,7 @@ ref="searchResults" class="scroller" :searchText="searchText" - :contactsLoading="contactsLoading" + :searchResultsLoading="searchResultsLoading" :conversationsList="conversationsList" :searchResults="searchResults" :searchResultsListedConversations="searchResultsListedConversations" @@ -477,8 +477,7 @@ export default { searchText: '', searchResults: [], searchResultsListedConversations: [], - contactsLoading: false, - listedConversationsLoading: false, + searchResultsLoading: true, canStartConversations: getTalkConfig('local', 'conversations', 'can-create'), cancelSearchPossibleConversations: () => {}, cancelSearchListedConversations: () => {}, @@ -758,8 +757,6 @@ export default { }, async fetchPossibleConversations() { - this.contactsLoading = true - try { // FIXME: move to conversationsStore this.cancelSearchPossibleConversations('canceled') @@ -782,8 +779,6 @@ export default { this.searchResults = response?.data?.ocs?.data.filter((match) => { return !(match.source === ATTENDEE.ACTOR_TYPE.USERS && oneToOneMap.includes(match.id)) }) ?? [] - - this.contactsLoading = false } catch (exception) { if (CancelableRequest.isCancel(exception)) { return @@ -795,8 +790,6 @@ export default { async fetchListedConversations() { try { - this.listedConversationsLoading = true - // FIXME: move to conversationsStore this.cancelSearchListedConversations('canceled') const { request, cancel } = CancelableRequest(searchListedConversations) @@ -804,7 +797,6 @@ export default { const response = await request(this.searchText) this.searchResultsListedConversations = response.data.ocs.data - this.listedConversationsLoading = false } catch (exception) { if (CancelableRequest.isCancel(exception)) { return @@ -824,7 +816,9 @@ export default { this.showThreadsList = false this.resetNavigation() + this.searchResultsLoading = true await Promise.all([this.fetchPossibleConversations(), this.fetchListedConversations()]) + this.searchResultsLoading = false this.initializeNavigation() }, diff --git a/src/components/LeftSidebar/SearchConversationsResults/SearchConversationsResults.vue b/src/components/LeftSidebar/SearchConversationsResults/SearchConversationsResults.vue index b59a3172dcf..7bb4e4a3ce6 100644 --- a/src/components/LeftSidebar/SearchConversationsResults/SearchConversationsResults.vue +++ b/src/components/LeftSidebar/SearchConversationsResults/SearchConversationsResults.vue @@ -25,7 +25,7 @@ import { getPreloadedUserStatus } from '../../../utils/userStatus.ts' const props = defineProps<{ searchText: string conversationsList: TypeConversation[] - contactsLoading: boolean + searchResultsLoading: boolean searchResultsListedConversations: TypeConversation[] searchResults: ParticipantSearchResult[] }>() @@ -105,6 +105,13 @@ const searchResultsVirtual = computed(() => { virtualList.push({ type: 'action', id: 'new_conversation', name: props.searchText, subname: t('spreed', 'New private conversation') }) } + // Add 'Loading' message if there are no results received from the server yet + if (props.searchResultsLoading && !props.searchResultsListedConversations.length && !props.searchResults.length) { + virtualList.push({ type: 'caption', id: 'loading_results_caption', name: t('spreed', 'Other sources') }) + virtualList.push({ type: 'hint', id: 'loading_results_hint', hint: t('spreed', 'Loading …') }) + return virtualList + } + // Add open conversations section if any if (props.searchResultsListedConversations.length !== 0) { virtualList.push({ type: 'caption', id: 'open_conversation_caption', name: t('spreed', 'Open conversations') }) @@ -186,11 +193,6 @@ function iconData(item: ParticipantSearchResult) { } } -const hasSourcesWithoutResults = computed(() => { - return !searchResultsVirtual.value.some((item) => item.type === 'user' || item.type === 'group' - || (item.type === 'circle' && isCirclesEnabled)) -}) - /** * Generate the message for the "No results" section * @@ -221,10 +223,6 @@ function sourcesWithoutResults(list: SubListType): string { } } -const footerMargin = computed(() => { - return isCompact.value ? '0' : '18px' // 54px (item height) - 36px (current height) -}) - const iconSize = computed(() => isCompact.value ? AVATAR.SIZE.COMPACT : AVATAR.SIZE.DEFAULT) @@ -233,13 +231,7 @@ const iconSize = computed(() => isCompact.value ? AVATAR.SIZE.COMPACT : AVATAR.S :ref="containerProps.ref" :style="containerProps.style" @scroll="containerProps.onScroll"> -