enh(UnifiedSearch): Keep the searchbar on top of the modal

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/42184/head
Ferdinand Thiessen 6 months ago
parent f7bead00d1
commit ac2182cc77
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400

@ -11,79 +11,89 @@
@update:is-open="showDateRangeModal = $event" />
<!-- Unified search form -->
<div ref="unifiedSearch" class="unified-search-modal">
<h1>{{ t('core', 'Unified search') }}</h1>
<NcInputField ref="searchInput"
:value.sync="searchQuery"
type="text"
:label="t('core', 'Search apps, files, tags, messages') + '...'"
@update:value="debouncedFind" />
<div class="unified-search-modal__filters">
<NcActions :menu-name="t('core', 'Apps and Settings')" :open.sync="providerActionMenuIsOpen">
<template #icon>
<ListBox :size="20" />
</template>
<NcActionButton v-for="provider in providers" :key="provider.id" @click="addProviderFilter(provider)">
<div class="unified-search-modal__header">
<h1>{{ t('core', 'Unified search') }}</h1>
<NcInputField ref="searchInput"
:value.sync="searchQuery"
type="text"
:label="t('core', 'Search apps, files, tags, messages') + '...'"
@update:value="debouncedFind" />
<div class="unified-search-modal__filters">
<NcActions :menu-name="t('core', 'Apps and Settings')" :open.sync="providerActionMenuIsOpen">
<template #icon>
<img :src="provider.icon">
<ListBox :size="20" />
</template>
{{ t('core', provider.name) }}
</NcActionButton>
</NcActions>
<NcActions :menu-name="t('core', 'Date')" :open.sync="dateActionMenuIsOpen">
<template #icon>
<CalendarRangeIcon :size="20" />
</template>
<NcActionButton :close-after-click="true" @click="applyQuickDateRange('today')">
{{ t('core', 'Today') }}
</NcActionButton>
<NcActionButton :close-after-click="true" @click="applyQuickDateRange('7days')">
{{ t('core', 'Last 7 days') }}
</NcActionButton>
<NcActionButton :close-after-click="true" @click="applyQuickDateRange('30days')">
{{ t('core', 'Last 30 days') }}
</NcActionButton>
<NcActionButton :close-after-click="true" @click="applyQuickDateRange('thisyear')">
{{ t('core', 'This year') }}
</NcActionButton>
<NcActionButton :close-after-click="true" @click="applyQuickDateRange('lastyear')">
{{ t('core', 'Last year') }}
</NcActionButton>
<NcActionButton :close-after-click="true" @click="applyQuickDateRange('custom')">
{{ t('core', 'Custom date range') }}
</NcActionButton>
</NcActions>
<SearchableList :label-text="t('core', 'Search people')"
:search-list="userContacts"
:empty-content-text="t('core', 'Not found')"
@search-term-change="debouncedFilterContacts"
@item-selected="applyPersonFilter">
<template #trigger>
<NcButton>
<NcActionButton v-for="provider in providers"
:key="provider.id"
@click="addProviderFilter(provider)">
<template #icon>
<AccountGroup :size="20" />
<img :src="provider.icon" class="filter-button__icon" alt="">
</template>
{{ t('core', 'People') }}
</NcButton>
</template>
</SearchableList>
</div>
<div class="unified-search-modal__filters-applied">
<FilterChip v-for="filter in filters"
:key="filter.id"
:text="filter.name ?? filter.text"
:pretext="''"
@delete="removeFilter(filter)">
<template #icon>
<NcAvatar v-if="filter.type === 'person'"
:user="filter.user"
:size="24"
:disable-menu="true"
:show-user-status="false"
:hide-favorite="false" />
<CalendarRangeIcon v-else-if="filter.type === 'date'" />
<img v-else :src="filter.icon" alt="">
</template>
</FilterChip>
{{ provider.name }}
</NcActionButton>
</NcActions>
<NcActions :menu-name="t('core', 'Date')" :open.sync="dateActionMenuIsOpen">
<template #icon>
<CalendarRangeIcon :size="20" />
</template>
<NcActionButton :close-after-click="true" @click="applyQuickDateRange('today')">
{{ t('core', 'Today') }}
</NcActionButton>
<NcActionButton :close-after-click="true" @click="applyQuickDateRange('7days')">
{{ t('core', 'Last 7 days') }}
</NcActionButton>
<NcActionButton :close-after-click="true" @click="applyQuickDateRange('30days')">
{{ t('core', 'Last 30 days') }}
</NcActionButton>
<NcActionButton :close-after-click="true" @click="applyQuickDateRange('thisyear')">
{{ t('core', 'This year') }}
</NcActionButton>
<NcActionButton :close-after-click="true" @click="applyQuickDateRange('lastyear')">
{{ t('core', 'Last year') }}
</NcActionButton>
<NcActionButton :close-after-click="true" @click="applyQuickDateRange('custom')">
{{ t('core', 'Custom date range') }}
</NcActionButton>
</NcActions>
<SearchableList :label-text="t('core', 'Search people')"
:search-list="userContacts"
:empty-content-text="t('core', 'Not found')"
@search-term-change="debouncedFilterContacts"
@item-selected="applyPersonFilter">
<template #trigger>
<NcButton>
<template #icon>
<AccountGroup :size="20" />
</template>
{{ t('core', 'People') }}
</NcButton>
</template>
</SearchableList>
<NcButton v-if="supportFiltering" @click="closeModal">
{{ t('core', 'Filter in current view') }}
<template #icon>
<FilterIcon :size="20" />
</template>
</NcButton>
</div>
<div class="unified-search-modal__filters-applied">
<FilterChip v-for="filter in filters"
:key="filter.id"
:text="filter.name ?? filter.text"
:pretext="''"
@delete="removeFilter(filter)">
<template #icon>
<NcAvatar v-if="filter.type === 'person'"
:user="filter.user"
:size="24"
:disable-menu="true"
:show-user-status="false"
:hide-favorite="false" />
<CalendarRangeIcon v-else-if="filter.type === 'date'" />
<img v-else :src="filter.icon" alt="">
</template>
</FilterChip>
</div>
</div>
<div v-if="noContentInfo.show" class="unified-search-modal__no-content">
<NcEmptyContent :name="noContentInfo.text">
@ -92,8 +102,8 @@
</template>
</NcEmptyContent>
</div>
<div v-for="providerResult in results" :key="providerResult.id" class="unified-search-modal__results">
<div class="results">
<div v-else class="unified-search-modal__results">
<div v-for="providerResult in results" :key="providerResult.id" class="result">
<div class="result-title">
<span>{{ providerResult.provider }}</span>
</div>
@ -116,14 +126,6 @@
</div>
</div>
</div>
<div v-if="supportFiltering()" class="unified-search-modal__results">
<NcButton @click="closeModal">
{{ t('core', 'Filter in current view') }}
<template #icon>
<FilterIcon :size="20" />
</template>
</NcButton>
</div>
</div>
</NcModal>
</template>
@ -150,6 +152,7 @@ import SearchResult from '../components/UnifiedSearch/SearchResult.vue'
import debounce from 'debounce'
import { emit } from '@nextcloud/event-bus'
import { useBrowserLocation } from '@vueuse/core'
import { getProviders, search as unifiedSearch, getContacts } from '../services/UnifiedSearchService.js'
export default {
@ -180,6 +183,15 @@ export default {
required: true,
},
},
setup() {
/**
* Reactive version of window.location
*/
const currentLocation = useBrowserLocation()
return {
currentLocation,
}
},
data() {
return {
providers: [],
@ -206,22 +218,22 @@ export default {
},
computed: {
userContacts: {
get() {
return this.contacts
},
userContacts() {
return this.contacts
},
noContentInfo: {
get() {
const isEmptySearch = this.searchQuery.length === 0
const hasNoResults = this.searchQuery.length > 0 && this.results.length === 0
return {
show: isEmptySearch || hasNoResults,
text: this.searching && hasNoResults ? t('core', 'Searching …') : (isEmptySearch ? t('core', 'Start typing to search') : t('core', 'No matching results')),
icon: MagnifyIcon,
}
},
noContentInfo() {
const isEmptySearch = this.searchQuery.length === 0
const hasNoResults = this.searchQuery.length > 0 && this.results.length === 0
return {
show: isEmptySearch || hasNoResults,
text: this.searching && hasNoResults ? t('core', 'Searching …') : (isEmptySearch ? t('core', 'Start typing to search') : t('core', 'No matching results')),
icon: MagnifyIcon,
}
},
supportFiltering() {
/* Hard coded apps for the moment this would be improved in coming updates. */
const providerPaths = ['/settings/users', '/apps/files', '/apps/deck']
return providerPaths.some((path) => this.currentLocation.pathname?.includes?.(path))
},
},
watch: {
@ -523,21 +535,27 @@ export default {
this.internalIsVisible = false
this.searchQuery = ''
},
supportFiltering() {
/* Hard coded apps for the moment this would be improved in coming updates. */
const providerPaths = ['/settings/users', '/apps/files', '/apps/deck']
const currentPath = window.location.pathname.replace('/index.php', '')
const containsProvider = providerPaths.some(path => currentPath.includes(path))
return containsProvider
},
},
}
</script>
<style lang="scss" scoped>
.unified-search-modal {
padding: 10px 20px 10px 20px;
height: 60%;
box-sizing: border-box;
height: 100%;
display: flex;
flex-direction: column;
padding-block: 10px 0;
// inline padding on direct children to make sure the scrollbar is on the modal container
> * {
padding-inline: 20px;
}
&__header {
padding-block-end: 8px;
}
&__heading {
font-size: 16px;
@ -548,14 +566,10 @@ export default {
&__filters {
display: flex;
flex-wrap: wrap;
gap: 4px;
justify-content: start;
padding-top: 4px;
justify-content: left;
>* {
margin-right: 4px;
}
}
&__filters-applied {
@ -571,11 +585,11 @@ export default {
}
&__results {
padding: 10px;
overflow: hidden scroll;
padding-block: 0 10px;
.results {
.result-title {
.result {
&-title {
span {
color: var(--color-primary-element);
font-weight: bolder;
@ -583,7 +597,7 @@ export default {
}
}
.result-footer {
&-footer {
justify-content: space-between;
align-items: center;
display: flex;
@ -593,20 +607,18 @@ export default {
}
}
div.v-popper__wrapper {
ul {
li {
::v-deep button.action-button {
align-items: center !important;
img {
width: 20px;
margin: 0 4px;
filter: var(--background-invert-if-bright);
}
.filter-button__icon {
height: 20px;
width: 20px;
object-fit: contain;
filter: var(--background-invert-if-bright);
padding: 11px; // align with text to fit at least 44px
}
}
}
// Ensure modal is accessible on small devices
@media only screen and (max-height: 400px) {
.unified-search-modal__results {
overflow: unset;
}
}
</style>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
package-lock.json generated

@ -32,6 +32,7 @@
"@nextcloud/vue": "^8.3.0",
"@skjnldsv/sanitize-svg": "^1.0.2",
"@vueuse/components": "^10.7.0",
"@vueuse/core": "^10.7.0",
"@vueuse/integrations": "^10.6.0",
"autosize": "^6.0.1",
"backbone": "^1.4.1",

@ -59,6 +59,7 @@
"@nextcloud/vue": "^8.3.0",
"@skjnldsv/sanitize-svg": "^1.0.2",
"@vueuse/components": "^10.7.0",
"@vueuse/core": "^10.7.0",
"@vueuse/integrations": "^10.6.0",
"autosize": "^6.0.1",
"backbone": "^1.4.1",

Loading…
Cancel
Save