aboutsummaryrefslogtreecommitdiffstats
path: root/core/src
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2023-12-13 00:52:18 +0100
committerFerdinand Thiessen <opensource@fthiessen.de>2024-02-01 17:21:23 +0100
commitffeecd86cf6640a2a2384f2dd8cbf39e717e9520 (patch)
tree15f96751267fb4764130e9d7d16ccbc9777e2c13 /core/src
parente49c331b7156e8a39052566f1da0f65b76f3c974 (diff)
downloadnextcloud-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.vue268
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>