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