Skip to content

Commit 186b12b

Browse files
authored
Merge pull request #56620 from nextcloud/fix/filter-interaction-issues
fix(unified-search): prevent provider disabling on content filter apply
2 parents a86a2a0 + cf56d63 commit 186b12b

File tree

3 files changed

+127
-18
lines changed

3 files changed

+127
-18
lines changed

core/src/components/UnifiedSearch/UnifiedSearchModal.vue

Lines changed: 124 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
:label="t('core', 'Search apps, files, tags, messages') + '...'"
3131
@update:value="debouncedFind" />
3232
<div class="unified-search-modal__filters" data-cy-unified-search-filters>
33-
<NcActions :menu-name="t('core', 'Places')" :open.sync="providerActionMenuIsOpen" data-cy-unified-search-filter="places">
33+
<NcActions :open.sync="providerActionMenuIsOpen" :menu-name="t('core', 'Places')" data-cy-unified-search-filter="places">
3434
<template #icon>
3535
<IconListBox :size="20" />
3636
</template>
@@ -47,7 +47,7 @@
4747
{{ provider.name }}
4848
</NcActionButton>
4949
</NcActions>
50-
<NcActions :menu-name="t('core', 'Date')" :open.sync="dateActionMenuIsOpen" data-cy-unified-search-filter="date">
50+
<NcActions :open.sync="dateActionMenuIsOpen" :menu-name="t('core', 'Date')" data-cy-unified-search-filter="date">
5151
<template #icon>
5252
<IconCalendarRange :size="20" />
5353
</template>
@@ -135,7 +135,8 @@
135135
<h3 class="hidden-visually">
136136
{{ t('core', 'Results') }}
137137
</h3>
138-
<div v-for="providerResult in results" :key="providerResult.id" class="result">
138+
<!-- Filtered results section -->
139+
<div v-for="providerResult in filteredResults" :key="providerResult.id" class="result">
139140
<h4 :id="`unified-search-result-${providerResult.id}`" class="result-title">
140141
{{ providerResult.name }}
141142
</h4>
@@ -160,6 +161,37 @@
160161
</NcButton>
161162
</div>
162163
</div>
164+
<!-- Unfiltered results section -->
165+
<template v-if="unfilteredResults.length > 0">
166+
<div class="unified-search-modal__unfiltered-header">
167+
<span class="unified-search-modal__unfiltered-label">{{ t('core', 'Partial matches') }}</span>
168+
</div>
169+
<div v-for="providerResult in unfilteredResults" :key="`unfiltered-${providerResult.id}`" class="result result--unfiltered">
170+
<h4 :id="`unified-search-result-unfiltered-${providerResult.id}`" class="result-title">
171+
{{ providerResult.name }}
172+
</h4>
173+
<ul class="result-items" :aria-labelledby="`unified-search-result-unfiltered-${providerResult.id}`">
174+
<SearchResult
175+
v-for="(result, index) in providerResult.results"
176+
:key="index"
177+
v-bind="result" />
178+
</ul>
179+
<div class="result-footer">
180+
<NcButton v-if="providerResult.results.length === providerResult.limit" variant="tertiary-no-background" @click="loadMoreResultsForProvider(providerResult)">
181+
{{ t('core', 'Load more results') }}
182+
<template #icon>
183+
<IconDotsHorizontal :size="20" />
184+
</template>
185+
</NcButton>
186+
<NcButton v-if="providerResult.inAppSearch" alignment="end-reverse" variant="tertiary-no-background">
187+
{{ t('core', 'Search in') }} {{ providerResult.name }}
188+
<template #icon>
189+
<IconArrowRight :size="20" />
190+
</template>
191+
</NcButton>
192+
</div>
193+
</div>
194+
</template>
163195
</div>
164196
</NcDialog>
165197
</template>
@@ -342,6 +374,50 @@ export default defineComponent({
342374
hasExternalResources() {
343375
return this.providers.some((provider) => provider.isExternalProvider)
344376
},
377+
378+
hasContentFilters() {
379+
return this.filters.some((filter) => filter.type === 'date' || filter.type === 'person')
380+
},
381+
382+
filteredResults() {
383+
const isInFolderAtRoot = (result) => {
384+
if (result.id !== 'in-folder') {
385+
return false
386+
}
387+
const path = result.extraParams?.path
388+
return !path || path === '/' || path === ''
389+
}
390+
391+
if (!this.hasContentFilters) {
392+
return this.results.filter((result) => !isInFolderAtRoot(result))
393+
}
394+
return this.results.filter((result) => result.supportsActiveFilters === true && !isInFolderAtRoot(result))
395+
},
396+
397+
filteredResultUrls() {
398+
const urls = new Set()
399+
this.filteredResults.forEach((provider) => {
400+
provider.results.forEach((entry) => {
401+
if (entry.resourceUrl) {
402+
urls.add(entry.resourceUrl)
403+
}
404+
})
405+
})
406+
return urls
407+
},
408+
409+
unfilteredResults() {
410+
if (!this.hasContentFilters) {
411+
return []
412+
}
413+
return this.results
414+
.filter((result) => result.supportsActiveFilters === false)
415+
.map((provider) => ({
416+
...provider,
417+
results: provider.results.filter((entry) => !this.filteredResultUrls.has(entry.resourceUrl)),
418+
}))
419+
.filter((provider) => provider.results.length > 0)
420+
},
345421
},
346422
347423
watch: {
@@ -444,20 +520,30 @@ export default defineComponent({
444520
445521
// This block of filter checks should be dynamic somehow and should be handled in
446522
// nextcloud/search lib
523+
const contentFilterTypes = this.filters
524+
.filter((f) => f.type !== 'provider')
525+
.map((f) => f.type)
526+
const supportsActiveFilters = contentFilterTypes.length === 0
527+
|| contentFilterTypes.every((type) => this.providerIsCompatibleWithFilters(provider, [type]))
528+
529+
const baseProvider = provider.searchFrom
530+
? this.providers.find((p) => p.id === provider.searchFrom) ?? provider
531+
: provider
532+
447533
const activeFilters = this.filters.filter((filter) => {
448534
return filter.type !== 'provider' && this.providerIsCompatibleWithFilters(provider, [filter.type])
449535
})
450536
451537
activeFilters.forEach((filter) => {
452538
switch (filter.type) {
453539
case 'date':
454-
if (provider.filters?.since && provider.filters?.until) {
540+
if (baseProvider.filters?.since && baseProvider.filters?.until) {
455541
params.since = this.dateFilter.startFrom
456542
params.until = this.dateFilter.endAt
457543
}
458544
break
459545
case 'person':
460-
if (provider.filters?.person) {
546+
if (baseProvider.filters?.person) {
461547
params.person = this.personFilter.user
462548
}
463549
break
@@ -484,6 +570,7 @@ export default defineComponent({
484570
...provider,
485571
results: response.data.ocs.data.entries,
486572
limit: params.limit ?? 5,
573+
supportsActiveFilters,
487574
})
488575
489576
unifiedSearchLogger.debug('Unified search results:', { results: this.results, newResults })
@@ -567,10 +654,6 @@ export default defineComponent({
567654
this.filters[existingPersonFilter].name = person.displayName
568655
}
569656
570-
this.providers.forEach(async (provider, index) => {
571-
this.providers[index].disabled = !(await this.providerIsCompatibleWithFilters(provider, ['person']))
572-
})
573-
574657
this.debouncedFind(this.searchQuery)
575658
unifiedSearchLogger.debug('Person filter applied', { person })
576659
},
@@ -628,7 +711,6 @@ export default defineComponent({
628711
for (let i = 0; i < this.filters.length; i++) {
629712
if (this.filters[i].id === filter.id) {
630713
this.filters.splice(i, 1)
631-
this.enableAllProviders()
632714
break
633715
}
634716
}
@@ -669,9 +751,6 @@ export default defineComponent({
669751
this.filters.push(this.dateFilter)
670752
}
671753
672-
this.providers.forEach(async (provider, index) => {
673-
this.providers[index].disabled = !(await this.providerIsCompatibleWithFilters(provider, ['since', 'until']))
674-
})
675754
this.debouncedFind(this.searchQuery)
676755
},
677756
@@ -774,8 +853,20 @@ export default defineComponent({
774853
return flattenedArray
775854
},
776855
777-
async providerIsCompatibleWithFilters(provider, filterIds) {
778-
return filterIds.every((filterId) => provider.filters?.[filterId] !== undefined)
856+
providerIsCompatibleWithFilters(provider, filterIds) {
857+
const baseProvider = provider.searchFrom
858+
? this.providers.find((p) => p.id === provider.searchFrom) ?? provider
859+
: provider
860+
return filterIds.every((filterId) => {
861+
switch (filterId) {
862+
case 'date':
863+
return baseProvider.filters?.since !== undefined && baseProvider.filters?.until !== undefined
864+
case 'person':
865+
return baseProvider.filters?.person !== undefined
866+
default:
867+
return baseProvider.filters?.[filterId] !== undefined
868+
}
869+
})
779870
},
780871
781872
async enableAllProviders() {
@@ -867,9 +958,27 @@ export default defineComponent({
867958
align-items: center;
868959
display: flex;
869960
}
961+
962+
&--unfiltered {
963+
opacity: 0.7;
964+
}
870965
}
871966
872967
}
968+
969+
&__unfiltered-header {
970+
display: flex;
971+
flex-direction: column;
972+
gap: 2px;
973+
margin-block: 16px 8px;
974+
padding-block: 12px 0;
975+
border-top: 1px solid var(--color-border);
976+
}
977+
978+
&__unfiltered-label {
979+
font-weight: bold;
980+
color: var(--color-text-maxcontrast);
981+
}
873982
}
874983
875984
.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)