diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2023-12-13 00:52:18 +0100 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-02-01 17:21:23 +0100 |
commit | ffeecd86cf6640a2a2384f2dd8cbf39e717e9520 (patch) | |
tree | 15f96751267fb4764130e9d7d16ccbc9777e2c13 /core/src | |
parent | e49c331b7156e8a39052566f1da0f65b76f3c974 (diff) | |
download | nextcloud-server-ffeecd86cf6640a2a2384f2dd8cbf39e717e9520.tar.gz nextcloud-server-ffeecd86cf6640a2a2384f2dd8cbf39e717e9520.zip |
enh(UnifiedSearch): Keep the searchbar on top of the modal
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'core/src')
-rw-r--r-- | core/src/views/UnifiedSearchModal.vue | 268 |
1 files changed, 140 insertions, 128 deletions
diff --git a/core/src/views/UnifiedSearchModal.vue b/core/src/views/UnifiedSearchModal.vue index 59afa04d0ab..004005b57d9 100644 --- a/core/src/views/UnifiedSearchModal.vue +++ b/core/src/views/UnifiedSearchModal.vue @@ -10,79 +10,89 @@ @update:is-open="showDateRangeModal = $event" /> <!-- Unified search form --> <div ref="unifiedSearch" class="unified-search-modal"> - <h2>{{ t('core', 'Unified search') }}</h2> - <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"> + <h2>{{ t('core', 'Unified search') }}</h2> + <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"> @@ -91,8 +101,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> @@ -115,14 +125,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> @@ -149,6 +151,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 { @@ -179,6 +182,15 @@ export default { required: true, }, }, + setup() { + /** + * Reactive version of window.location + */ + const currentLocation = useBrowserLocation() + return { + currentLocation, + } + }, data() { return { providers: [], @@ -205,22 +217,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: { @@ -522,21 +534,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; @@ -547,14 +565,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 { @@ -570,11 +584,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; @@ -582,7 +596,7 @@ export default { } } - .result-footer { + &-footer { justify-content: space-between; align-items: center; display: flex; @@ -592,20 +606,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> |