Skip to content

Commit b17459e

Browse files
authored
Merge pull request #56975 from nextcloud/backport/56620/stable31
[stable31] fix(unified-search): prevent provider disabling on content filter apply
2 parents 716c0c9 + e8631ef commit b17459e

File tree

3 files changed

+128
-20
lines changed

3 files changed

+128
-20
lines changed

core/src/components/UnifiedSearch/UnifiedSearchModal.vue

Lines changed: 125 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
:label="t('core', 'Search apps, files, tags, messages') + '...'"
2828
@update:value="debouncedFind" />
2929
<div class="unified-search-modal__filters" data-cy-unified-search-filters>
30-
<NcActions :menu-name="t('core', 'Places')" :open.sync="providerActionMenuIsOpen" data-cy-unified-search-filter="places">
30+
<NcActions :open.sync="providerActionMenuIsOpen" :menu-name="t('core', 'Places')" data-cy-unified-search-filter="places">
3131
<template #icon>
3232
<IconListBox :size="20" />
3333
</template>
@@ -43,7 +43,7 @@
4343
{{ provider.name }}
4444
</NcActionButton>
4545
</NcActions>
46-
<NcActions :menu-name="t('core', 'Date')" :open.sync="dateActionMenuIsOpen" data-cy-unified-search-filter="date">
46+
<NcActions :open.sync="dateActionMenuIsOpen" :menu-name="t('core', 'Date')" data-cy-unified-search-filter="date">
4747
<template #icon>
4848
<IconCalendarRange :size="20" />
4949
</template>
@@ -120,7 +120,8 @@
120120
<h3 class="hidden-visually">
121121
{{ t('core', 'Results') }}
122122
</h3>
123-
<div v-for="providerResult in results" :key="providerResult.id" class="result">
123+
<!-- Filtered results section -->
124+
<div v-for="providerResult in filteredResults" :key="providerResult.id" class="result">
124125
<h4 :id="`unified-search-result-${providerResult.id}`" class="result-title">
125126
{{ providerResult.name }}
126127
</h4>
@@ -144,6 +145,36 @@
144145
</NcButton>
145146
</div>
146147
</div>
148+
<!-- Unfiltered results section -->
149+
<template v-if="unfilteredResults.length > 0">
150+
<div class="unified-search-modal__unfiltered-header">
151+
<span class="unified-search-modal__unfiltered-label">{{ t('core', 'Partial matches') }}</span>
152+
</div>
153+
<div v-for="providerResult in unfilteredResults" :key="`unfiltered-${providerResult.id}`" class="result result--unfiltered">
154+
<h4 :id="`unified-search-result-unfiltered-${providerResult.id}`" class="result-title">
155+
{{ providerResult.name }}
156+
</h4>
157+
<ul class="result-items" :aria-labelledby="`unified-search-result-unfiltered-${providerResult.id}`">
158+
<SearchResult v-for="(result, index) in providerResult.results"
159+
:key="index"
160+
v-bind="result" />
161+
</ul>
162+
<div class="result-footer">
163+
<NcButton v-if="providerResult.results.length === providerResult.limit" variant="tertiary-no-background" @click="loadMoreResultsForProvider(providerResult)">
164+
{{ t('core', 'Load more results') }}
165+
<template #icon>
166+
<IconDotsHorizontal :size="20" />
167+
</template>
168+
</NcButton>
169+
<NcButton v-if="providerResult.inAppSearch" alignment="end-reverse" variant="tertiary-no-background">
170+
{{ t('core', 'Search in') }} {{ providerResult.name }}
171+
<template #icon>
172+
<IconArrowRight :size="20" />
173+
</template>
174+
</NcButton>
175+
</div>
176+
</div>
177+
</template>
147178
</div>
148179
</NcDialog>
149180
</template>
@@ -321,6 +352,50 @@ export default defineComponent({
321352
debouncedFilterContacts() {
322353
return debounce(this.filterContacts, 300)
323354
},
355+
356+
hasContentFilters() {
357+
return this.filters.some((filter) => filter.type === 'date' || filter.type === 'person')
358+
},
359+
360+
filteredResults() {
361+
const isInFolderAtRoot = (result) => {
362+
if (result.id !== 'in-folder') {
363+
return false
364+
}
365+
const path = result.extraParams?.path
366+
return !path || path === '/' || path === ''
367+
}
368+
369+
if (!this.hasContentFilters) {
370+
return this.results.filter((result) => !isInFolderAtRoot(result))
371+
}
372+
return this.results.filter((result) => result.supportsActiveFilters === true && !isInFolderAtRoot(result))
373+
},
374+
375+
filteredResultUrls() {
376+
const urls = new Set()
377+
this.filteredResults.forEach((provider) => {
378+
provider.results.forEach((entry) => {
379+
if (entry.resourceUrl) {
380+
urls.add(entry.resourceUrl)
381+
}
382+
})
383+
})
384+
return urls
385+
},
386+
387+
unfilteredResults() {
388+
if (!this.hasContentFilters) {
389+
return []
390+
}
391+
return this.results
392+
.filter((result) => result.supportsActiveFilters === false)
393+
.map((provider) => ({
394+
...provider,
395+
results: provider.results.filter((entry) => !this.filteredResultUrls.has(entry.resourceUrl)),
396+
}))
397+
.filter((provider) => provider.results.length > 0)
398+
},
324399
},
325400
326401
watch: {
@@ -413,20 +488,30 @@ export default defineComponent({
413488
414489
// This block of filter checks should be dynamic somehow and should be handled in
415490
// nextcloud/search lib
416-
const activeFilters = this.filters.filter(filter => {
491+
const contentFilterTypes = this.filters
492+
.filter((f) => f.type !== 'provider')
493+
.map((f) => f.type)
494+
const supportsActiveFilters = contentFilterTypes.length === 0
495+
|| contentFilterTypes.every((type) => this.providerIsCompatibleWithFilters(provider, [type]))
496+
497+
const baseProvider = provider.searchFrom
498+
? this.providers.find((p) => p.id === provider.searchFrom) ?? provider
499+
: provider
500+
501+
const activeFilters = this.filters.filter((filter) => {
417502
return filter.type !== 'provider' && this.providerIsCompatibleWithFilters(provider, [filter.type])
418503
})
419504
420-
activeFilters.forEach(filter => {
505+
activeFilters.forEach((filter) => {
421506
switch (filter.type) {
422507
case 'date':
423-
if (provider.filters?.since && provider.filters?.until) {
508+
if (baseProvider.filters?.since && baseProvider.filters?.until) {
424509
params.since = this.dateFilter.startFrom
425510
params.until = this.dateFilter.endAt
426511
}
427512
break
428513
case 'person':
429-
if (provider.filters?.person) {
514+
if (baseProvider.filters?.person) {
430515
params.person = this.personFilter.user
431516
}
432517
break
@@ -445,6 +530,7 @@ export default defineComponent({
445530
...provider,
446531
results: response.data.ocs.data.entries,
447532
limit: params.limit ?? 5,
533+
supportsActiveFilters,
448534
})
449535
450536
unifiedSearchLogger.debug('Unified search results:', { results: this.results, newResults })
@@ -525,10 +611,6 @@ export default defineComponent({
525611
this.filters[existingPersonFilter].name = person.displayName
526612
}
527613
528-
this.providers.forEach(async (provider, index) => {
529-
this.providers[index].disabled = !(await this.providerIsCompatibleWithFilters(provider, ['person']))
530-
})
531-
532614
this.debouncedFind(this.searchQuery)
533615
unifiedSearchLogger.debug('Person filter applied', { person })
534616
},
@@ -582,7 +664,6 @@ export default defineComponent({
582664
for (let i = 0; i < this.filters.length; i++) {
583665
if (this.filters[i].id === filter.id) {
584666
this.filters.splice(i, 1)
585-
this.enableAllProviders()
586667
break
587668
}
588669
}
@@ -621,9 +702,6 @@ export default defineComponent({
621702
this.filters.push(this.dateFilter)
622703
}
623704
624-
this.providers.forEach(async (provider, index) => {
625-
this.providers[index].disabled = !(await this.providerIsCompatibleWithFilters(provider, ['since', 'until']))
626-
})
627705
this.debouncedFind(this.searchQuery)
628706
},
629707
applyQuickDateRange(range) {
@@ -724,8 +802,20 @@ export default defineComponent({
724802
725803
return flattenedArray
726804
},
727-
async providerIsCompatibleWithFilters(provider, filterIds) {
728-
return filterIds.every(filterId => provider.filters?.[filterId] !== undefined)
805+
providerIsCompatibleWithFilters(provider, filterIds) {
806+
const baseProvider = provider.searchFrom
807+
? this.providers.find((p) => p.id === provider.searchFrom) ?? provider
808+
: provider
809+
return filterIds.every((filterId) => {
810+
switch (filterId) {
811+
case 'date':
812+
return baseProvider.filters?.since !== undefined && baseProvider.filters?.until !== undefined
813+
case 'person':
814+
return baseProvider.filters?.person !== undefined
815+
default:
816+
return baseProvider.filters?.[filterId] !== undefined
817+
}
818+
})
729819
},
730820
async enableAllProviders() {
731821
this.providers.forEach(async (_, index) => {
@@ -801,9 +891,27 @@ export default defineComponent({
801891
align-items: center;
802892
display: flex;
803893
}
894+
895+
&--unfiltered {
896+
opacity: 0.7;
897+
}
804898
}
805899
806900
}
901+
902+
&__unfiltered-header {
903+
display: flex;
904+
flex-direction: column;
905+
gap: 2px;
906+
margin-block: 16px 8px;
907+
padding-block: 12px 0;
908+
border-top: 1px solid var(--color-border);
909+
}
910+
911+
&__unfiltered-label {
912+
font-weight: bold;
913+
color: var(--color-text-maxcontrast);
914+
}
807915
}
808916
809917
.filter-button__icon {

dist/core-unified-search.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/core-unified-search.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)