@ -24,13 +24,13 @@
< div class = "unified-search-modal__header" >
< NcInputField
ref = "searchInput"
v - model :value ="searchQuery"
v - model ="searchQuery"
data - cy - unified - search - input
type = "text"
: label = "t('core', 'Search apps, files, tags, messages') + '...'"
@ update : value = "debouncedFind" / >
< div class = "unified-search-modal__filters" data -cy -unified -search -filters >
< NcActions v -model :open ="providerActionMenuIsOpen" : menu -name = " t ( ' core ' , ' Places ' ) " data -cy -unified -search -filter = " places " >
< NcActions :open .sync ="providerActionMenuIsOpen" : menu -name = " t ( ' core ' , ' Places ' ) " data -cy -unified -search -filter = " places " >
< template # icon >
< IconListBox :size ="20" / >
< / template >
@ -47,7 +47,7 @@
{ { provider . name } }
< / NcActionButton >
< / NcActions >
< NcActions v -model :open ="dateActionMenuIsOpen" : menu -name = " t ( ' core ' , ' Date ' ) " data -cy -unified -search -filter = " date " >
< NcActions :open .sync ="dateActionMenuIsOpen" : menu -name = " t ( ' core ' , ' Date ' ) " data -cy -unified -search -filter = " date " >
< template # icon >
< IconCalendarRange :size ="20" / >
< / template >
@ -135,7 +135,8 @@
< h3 class = "hidden-visually" >
{ { t ( 'core' , 'Results' ) } }
< / h3 >
< div v-for ="providerResult in results" :key="providerResult.id" class="result" >
<!-- Filtered results section -- >
< div v-for ="providerResult in filteredResults" :key="providerResult.id" class="result" >
< h4 :id ="`unified-search-result-${providerResult.id}`" class = "result-title" >
{ { providerResult . name } }
< / h4 >
@ -160,6 +161,37 @@
< / NcButton >
< / div >
< / div >
<!-- Unfiltered results section -- >
< template v-if ="unfilteredResults.length > 0" >
< div class = "unified-search-modal__unfiltered-header" >
< span class = "unified-search-modal__unfiltered-label" > { { t ( 'core' , 'Partial matches' ) } } < / span >
< / div >
< div v-for ="providerResult in unfilteredResults" :key="`unfiltered-${providerResult.id}`" class="result result--unfiltered" >
< h4 :id ="`unified-search-result-unfiltered-${providerResult.id}`" class = "result-title" >
{ { providerResult . name } }
< / h4 >
< ul class = "result-items" :aria-labelledby ="`unified-search-result-unfiltered-${providerResult.id}`" >
< SearchResult
v - for = "(result, index) in providerResult.results"
: key = "index"
v - bind = "result" / >
< / ul >
< div class = "result-footer" >
< NcButton v-if ="providerResult.results.length === providerResult.limit" variant="tertiary-no-background" @click="loadMoreResultsForProvider(providerResult)" >
{ { t ( 'core' , 'Load more results' ) } }
< template # icon >
< IconDotsHorizontal :size ="20" / >
< / template >
< / NcButton >
< NcButton v-if ="providerResult.inAppSearch" alignment="end-reverse" variant="tertiary-no-background" >
{ { t ( 'core' , 'Search in' ) } } { { providerResult . name } }
< template # icon >
< IconArrowRight :size ="20" / >
< / template >
< / NcButton >
< / div >
< / div >
< / template >
< / div >
< / NcDialog >
< / template >
@ -342,6 +374,50 @@ export default defineComponent({
hasExternalResources ( ) {
return this . providers . some ( ( provider ) => provider . isExternalProvider )
} ,
hasContentFilters ( ) {
return this . filters . some ( ( filter ) => filter . type === 'date' || filter . type === 'person' )
} ,
filteredResults ( ) {
const isInFolderAtRoot = ( result ) => {
if ( result . id !== 'in-folder' ) {
return false
}
const path = result . extraParams ? . path
return ! path || path === '/' || path === ''
}
if ( ! this . hasContentFilters ) {
return this . results . filter ( ( result ) => ! isInFolderAtRoot ( result ) )
}
return this . results . filter ( ( result ) => result . supportsActiveFilters === true && ! isInFolderAtRoot ( result ) )
} ,
filteredResultUrls ( ) {
const urls = new Set ( )
this . filteredResults . forEach ( ( provider ) => {
provider . results . forEach ( ( entry ) => {
if ( entry . resourceUrl ) {
urls . add ( entry . resourceUrl )
}
} )
} )
return urls
} ,
unfilteredResults ( ) {
if ( ! this . hasContentFilters ) {
return [ ]
}
return this . results
. filter ( ( result ) => result . supportsActiveFilters === false )
. map ( ( provider ) => ( {
... provider ,
results : provider . results . filter ( ( entry ) => ! this . filteredResultUrls . has ( entry . resourceUrl ) ) ,
} ) )
. filter ( ( provider ) => provider . results . length > 0 )
} ,
} ,
watch : {
@ -444,6 +520,16 @@ export default defineComponent({
/ / T h i s b l o c k o f f i l t e r c h e c k s s h o u l d b e d y n a m i c s o m e h o w a n d s h o u l d b e h a n d l e d i n
/ / n e x t c l o u d / s e a r c h l i b
const contentFilterTypes = this . filters
. filter ( ( f ) => f . type !== 'provider' )
. map ( ( f ) => f . type )
const supportsActiveFilters = contentFilterTypes . length === 0
|| contentFilterTypes . every ( ( type ) => this . providerIsCompatibleWithFilters ( provider , [ type ] ) )
const baseProvider = provider . searchFrom
? this . providers . find ( ( p ) => p . id === provider . searchFrom ) ? ? provider
: provider
const activeFilters = this . filters . filter ( ( filter ) => {
return filter . type !== 'provider' && this . providerIsCompatibleWithFilters ( provider , [ filter . type ] )
} )
@ -451,13 +537,13 @@ export default defineComponent({
activeFilters . forEach ( ( filter ) => {
switch ( filter . type ) {
case 'date' :
if ( provider. filters ? . since && p rovider. filters ? . until ) {
if ( baseProvider. filters ? . since && baseP rovider. filters ? . until ) {
params . since = this . dateFilter . startFrom
params . until = this . dateFilter . endAt
}
break
case 'person' :
if ( p rovider. filters ? . person ) {
if ( baseP rovider. filters ? . person ) {
params . person = this . personFilter . user
}
break
@ -484,6 +570,7 @@ export default defineComponent({
... provider ,
results : response . data . ocs . data . entries ,
limit : params . limit ? ? 5 ,
supportsActiveFilters ,
} )
unifiedSearchLogger . debug ( 'Unified search results:' , { results : this . results , newResults } )
@ -766,8 +853,20 @@ export default defineComponent({
return flattenedArray
} ,
async providerIsCompatibleWithFilters ( provider , filterIds ) {
return filterIds . every ( ( filterId ) => provider . filters ? . [ filterId ] !== undefined )
providerIsCompatibleWithFilters ( provider , filterIds ) {
const baseProvider = provider . searchFrom
? this . providers . find ( ( p ) => p . id === provider . searchFrom ) ? ? provider
: provider
return filterIds . every ( ( filterId ) => {
switch ( filterId ) {
case 'date' :
return baseProvider . filters ? . since !== undefined && baseProvider . filters ? . until !== undefined
case 'person' :
return baseProvider . filters ? . person !== undefined
default :
return baseProvider . filters ? . [ filterId ] !== undefined
}
} )
} ,
async enableAllProviders ( ) {
@ -859,9 +958,27 @@ export default defineComponent({
align - items : center ;
display : flex ;
}
& -- unfiltered {
opacity : 0.7 ;
}
}
}
& _ _unfiltered - header {
display : flex ;
flex - direction : column ;
gap : 2 px ;
margin - block : 16 px 8 px ;
padding - block : 12 px 0 ;
border - top : 1 px solid var ( -- color - border ) ;
}
& _ _unfiltered - label {
font - weight : bold ;
color : var ( -- color - text - maxcontrast ) ;
}
}
. filter - button _ _icon {