diff --git a/src/components/search/search.js b/src/components/search/search.js index 61a4dae2..db07f5e7 100644 --- a/src/components/search/search.js +++ b/src/components/search/search.js @@ -15,6 +15,8 @@ library.add( faSearch ) +const allSearchTypes = Object.freeze(['statuses', 'media', 'accounts', 'hashtags']) + const Search = { components: { FollowCard, @@ -28,14 +30,17 @@ const Search = { data () { return { loadedInitially: false, - loading: false, + loading: Object.fromEntries( + allSearchTypes.map((searchType) => [searchType, false]) + ), searchTerm: this.query || '', userIds: [], statuses: [], media: [], hashtags: [], + allTabs: allSearchTypes, currentResultTab: 'statuses', - preferredTab : 'statuses', + hasUserSelectedTab: false, statusesOffset: 0, lastStatusFetchCount: 0, @@ -72,6 +77,10 @@ const Search = { canSearchFollowing () { return this.isLoggedIn && this.$store.state.instance.searchOptionFollowingEnabled === true + }, + hasAtLeastOneResult () { + return allSearchTypes + .some((searchType) => this.getVisibleLength(searchType) > 0) } }, mounted () { @@ -97,12 +106,13 @@ const Search = { }, async search (query, searchType = null) { if (!query) { - this.loading = false + for (const searchType of allSearchTypes) { + this.loading[searchType] = false + } return } const isNewSearch = this.lastQuery !== query - this.loading = true this.$refs.searchInput.blur() if (isNewSearch) { this.userIds = [] @@ -116,92 +126,102 @@ const Search = { this.lastMediaFetchCount = 0 } - let searchTypes = ['statuses', 'media', 'accounts', 'hashtags'] - + let searchTypes = allSearchTypes if (searchType) { searchTypes = [searchType] - } else if (this.preferredTab !== 'statuses') { + } else if (this.currentResultTab !== 'statuses') { + // Sort search order; selected tab first searchTypes = [ - this.preferredTab, - ...searchTypes.filter((tab) => tab !== this.preferredTab) + this.currentResultTab, + ...allSearchTypes.filter((tab) => tab !== this.currentResultTab) ] } + for (const searchType of searchTypes) { + this.loading[searchType] = true + } + let oldStatusesLength = this.statuses.length let oldMediaLength = this.media.length let skipMediaSearch = !this.canSearchMediaPosts for (const searchType of searchTypes) { - if (searchType === 'media' && skipMediaSearch) { - continue - } - - let searchOffset - if (searchType === 'statuses') { - searchOffset = this.statusesOffset - } else if (searchType === 'media') { - searchOffset = this.mediaOffset - } - - const data = await this.$store.dispatch('search', { - q: query, - resolve: true, - offset: searchOffset, - 'type': searchType, - following: - 'followingOnly' in this.filter && this.filter.followingOnly - }) - - // Always append to old results. If new results are empty, this doesn't change anything - this.userIds = this.userIds.concat(map(data.accounts, 'id')) - this.statuses = uniqBy(this.statuses.concat(data.statuses), 'id') - if ('media' in data) { - this.media = uniqBy(this.media.concat(data.media), 'id') - } - this.hashtags = this.hashtags.concat(data.hashtags) - - if (isNewSearch) { - this.currentResultTab = this.getActiveTab() - if (searchType === 'statuses' && data.statuses.length === 0) { - // safe to assume that there are no media posts - skipMediaSearch = true + try { + if (searchType === 'media' && skipMediaSearch) { + continue } - } - if ( - !loadedInitially && - Object.values(data).some((result) => result.length > 0) - ) { - // Show results on the first meaningful response - this.loadedInitially = true - } + let searchOffset + if (searchType === 'statuses') { + searchOffset = this.statusesOffset + } else if (searchType === 'media') { + searchOffset = this.mediaOffset + } - if (searchType === 'statuses') { - // Offset from whatever we already have - this.statusesOffset = this.statuses.length - // Because the amount of new statuses can actually be zero, compare to old length instead - this.lastStatusFetchCount = this.statuses.length - oldStatusesLength - } else if (searchType === 'media') { - this.mediaOffset = this.media.length - this.lastMediaFetchCount = this.media.length - oldMediaLength + const data = await this.$store.dispatch('search', { + q: query, + resolve: true, + offset: searchOffset, + 'type': searchType, + following: + 'followingOnly' in this.filter && this.filter.followingOnly + }) + + // Always append to old results. If new results are empty, this doesn't change anything + this.userIds = this.userIds.concat(map(data.accounts, 'id')) + this.statuses = uniqBy(this.statuses.concat(data.statuses), 'id') + if ('media' in data) { + this.media = uniqBy(this.media.concat(data.media), 'id') + } + this.hashtags = this.hashtags.concat(data.hashtags) + + if (isNewSearch) { + if (!this.hasUserSelectedTab) { + this.currentResultTab = this.getFirstTabWithResults() + } + if (searchType === 'statuses' && data.statuses.length === 0) { + // Safe to assume that there are no media posts + skipMediaSearch = true + } + } + } catch (error) { + console.error(error) + } finally { + if (!this.loadedInitially && this.hasAtLeastOneResult) { + // Show results on the first meaningful response + this.loadedInitially = true + } + this.loading[searchType] = false + + if (searchType === 'statuses') { + // Offset from whatever we already have + this.statusesOffset = this.statuses.length + // Because the amount of new statuses can actually be zero, compare to old length instead + this.lastStatusFetchCount = this.statuses.length - oldStatusesLength + } else if (searchType === 'media') { + this.mediaOffset = this.media.length + this.lastMediaFetchCount = this.media.length - oldMediaLength + } } } this.lastQuery = query this.loadedInitially = true - this.loading = false + for (const searchType of allSearchTypes) { + this.loading[searchType] = false + } }, - resultCount (tabName) { - const length = this[tabName].length + resultCount (tab) { + const length = this.getVisibleLength(tab) if (length === 0 || !this.loadedInitially) { return '' } if ( - (tabName === 'visibleStatuses' && this.lastStatusFetchCount !== 0) || - (tabName === 'visibleMedia' && this.lastMediaFetchCount !== 0) + (tab === 'statuses' && this.lastStatusFetchCount !== 0) || + (tab === 'media' && this.lastMediaFetchCount !== 0) ) { return ` (${length}+)` } @@ -210,24 +230,11 @@ const Search = { }, onResultTabSwitch (key) { this.currentResultTab = key - this.preferredTab = key - this.loading = false + this.hasUserSelectedTab = true }, - getActiveTab () { - const available = { - statuses: this.visibleStatuses.length > 0, - media: this.visibleMedia.length > 0, - accounts: this.users.length > 0, - hashtags: this.hashtags.length > 0, - } - - if (available[this.preferredTab]) { - return this.preferredTab - } - - const tabOrder = ['statuses', 'media', 'accounts', 'hashtags'] - for (const tab of tabOrder) { - if (available[tab]) { + getFirstTabWithResults () { + for (const tab of allSearchTypes) { + if (this.getVisibleLength(tab) > 0) { return tab } } @@ -236,6 +243,17 @@ const Search = { }, lastHistoryRecord (hashtag) { return hashtag.history && hashtag.history[0] + }, + getVisibleLength (tab) { + if (tab === 'statuses') { + return this.visibleStatuses.length + } else if (tab === 'media') { + return this.visibleMedia.length + } else if (tab === 'accounts') { + return this.users.length + } else if (tab === 'hashtags') { + return this.hashtags.length + } } } } diff --git a/src/components/search/search.vue b/src/components/search/search.vue index 1a6e2d1b..389f8900 100644 --- a/src/components/search/search.vue +++ b/src/components/search/search.vue @@ -27,23 +27,7 @@ -
- -
-
+
+
+ +

@@ -116,7 +113,7 @@ :status-id="media.id" />

@@ -145,7 +145,7 @@

{{ $t('search.no_results') }}

@@ -159,7 +159,7 @@

{{ $t('search.no_results') }}

@@ -294,5 +294,4 @@ height: 3.5em; line-height: 3.5em; } -