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 >
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 >
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 >
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 {
0 commit comments