diff options
Diffstat (limited to 'core/src/components')
7 files changed, 95 insertions, 78 deletions
diff --git a/core/src/components/AccountMenu/AccountMenuEntry.vue b/core/src/components/AccountMenu/AccountMenuEntry.vue index 47db84a7d33..d983226d273 100644 --- a/core/src/components/AccountMenu/AccountMenuEntry.vue +++ b/core/src/components/AccountMenu/AccountMenuEntry.vue @@ -11,28 +11,30 @@ compact :href="href" :name="name" - target="_self"> + target="_self" + @click="onClick"> <template #icon> - <img class="account-menu-entry__icon" + <NcLoadingIcon v-if="loading" :size="20" class="account-menu-entry__loading" /> + <slot v-else-if="$scopedSlots.icon" name="icon" /> + <img v-else + class="account-menu-entry__icon" :class="{ 'account-menu-entry__icon--active': active }" :src="iconSource" alt=""> </template> - <template v-if="loading" #indicator> - <NcLoadingIcon /> - </template> </NcListItem> </template> -<script> +<script lang="ts"> import { loadState } from '@nextcloud/initial-state' +import { defineComponent } from 'vue' import NcListItem from '@nextcloud/vue/components/NcListItem' import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon' const versionHash = loadState('core', 'versionHash', '') -export default { +export default defineComponent({ name: 'AccountMenuEntry', components: { @@ -55,11 +57,11 @@ export default { }, active: { type: Boolean, - required: true, + default: false, }, icon: { type: String, - required: true, + default: '', }, }, @@ -76,11 +78,17 @@ export default { }, methods: { - handleClick() { - this.loading = true + onClick(e: MouseEvent) { + this.$emit('click', e) + + // Allow to not show the loading indicator + // in case the click event was already handled + if (!e.defaultPrevented) { + this.loading = true + } }, }, -} +}) </script> <style lang="scss" scoped> @@ -96,6 +104,12 @@ export default { } } + &__loading { + height: 20px; + width: 20px; + margin: calc((var(--default-clickable-area) - 20px) / 2); // 20px icon size + } + :deep(.list-item-content__main) { width: fit-content; } diff --git a/core/src/components/AppMenuIcon.vue b/core/src/components/AppMenuIcon.vue index f2cee75e644..1b0d48daf8c 100644 --- a/core/src/components/AppMenuIcon.vue +++ b/core/src/components/AppMenuIcon.vue @@ -14,24 +14,25 @@ </template> <script setup lang="ts"> -import type { INavigationEntry } from '../types/navigation' +import type { INavigationEntry } from '../types/navigation.ts' + import { n } from '@nextcloud/l10n' import { computed } from 'vue' - -import IconDot from 'vue-material-design-icons/Circle.vue' +import IconDot from 'vue-material-design-icons/CircleOutline.vue' const props = defineProps<{ app: INavigationEntry }>() -const ariaHidden = computed(() => String(props.app.unread > 0)) +// only hide if there are no unread notifications +const ariaHidden = computed(() => !props.app.unread ? 'true' : undefined) const ariaLabel = computed(() => { - if (ariaHidden.value === 'true') { - return '' + if (!props.app.unread) { + return undefined } - return props.app.name - + (props.app.unread > 0 ? ` (${n('core', '{count} notification', '{count} notifications', props.app.unread, { count: props.app.unread })})` : '') + + return `${props.app.name} (${n('core', '{count} notification', '{count} notifications', props.app.unread, { count: props.app.unread })})` }) </script> @@ -51,6 +52,7 @@ $unread-indicator-size: 10px; height: $icon-size; width: $icon-size; filter: var(--background-image-invert-if-bright); + mask: var(--header-menu-icon-mask); } &__unread { diff --git a/core/src/components/PublicPageMenu/PublicPageMenuEntry.vue b/core/src/components/PublicPageMenu/PublicPageMenuEntry.vue index 4a8640f38a8..413806c7089 100644 --- a/core/src/components/PublicPageMenu/PublicPageMenuEntry.vue +++ b/core/src/components/PublicPageMenu/PublicPageMenuEntry.vue @@ -11,22 +11,24 @@ role="presentation" @click="$emit('click')"> <template #icon> - <div role="presentation" :class="['icon', icon, 'public-page-menu-entry__icon']" /> + <slot v-if="$scopedSlots.icon" name="icon" /> + <div v-else role="presentation" :class="['icon', icon, 'public-page-menu-entry__icon']" /> </template> </NcListItem> </template> <script setup lang="ts"> -import NcListItem from '@nextcloud/vue/components/NcListItem' import { onMounted } from 'vue' +import NcListItem from '@nextcloud/vue/components/NcListItem' + const props = defineProps<{ /** Only emit click event but do not open href */ clickOnly?: boolean // menu entry props id: string label: string - icon: string + icon?: string href: string details?: string }>() diff --git a/core/src/components/UnifiedSearch/UnifiedSearchLocalSearchBar.vue b/core/src/components/UnifiedSearch/UnifiedSearchLocalSearchBar.vue index 1860c54e1ff..171eada8a06 100644 --- a/core/src/components/UnifiedSearch/UnifiedSearchLocalSearchBar.vue +++ b/core/src/components/UnifiedSearch/UnifiedSearchLocalSearchBar.vue @@ -32,7 +32,7 @@ {{ t('core', 'Search everywhere') }} </template> <template #icon> - <NcIconSvgWrapper :path="mdiCloudSearch" /> + <NcIconSvgWrapper :path="mdiCloudSearchOutline" /> </template> </NcButton> </div> @@ -41,7 +41,7 @@ <script lang="ts" setup> import type { ComponentPublicInstance } from 'vue' -import { mdiCloudSearch, mdiClose } from '@mdi/js' +import { mdiCloudSearchOutline, mdiClose } from '@mdi/js' import { translate as t } from '@nextcloud/l10n' import { useIsMobile } from '@nextcloud/vue/composables/useIsMobile' import { useElementSize } from '@vueuse/core' diff --git a/core/src/components/UnifiedSearch/UnifiedSearchModal.vue b/core/src/components/UnifiedSearch/UnifiedSearchModal.vue index 6327d0e4d3d..b21c65301c4 100644 --- a/core/src/components/UnifiedSearch/UnifiedSearchModal.vue +++ b/core/src/components/UnifiedSearch/UnifiedSearchModal.vue @@ -129,7 +129,7 @@ v-bind="result" /> </ul> <div class="result-footer"> - <NcButton type="tertiary-no-background" @click="loadMoreResultsForProvider(providerResult)"> + <NcButton v-if="providerResult.results.length === providerResult.limit" type="tertiary-no-background" @click="loadMoreResultsForProvider(providerResult)"> {{ t('core', 'Load more results') }} <template #icon> <IconDotsHorizontal :size="20" /> @@ -159,8 +159,8 @@ import debounce from 'debounce' import { unifiedSearchLogger } from '../../logger' import IconArrowRight from 'vue-material-design-icons/ArrowRight.vue' -import IconAccountGroup from 'vue-material-design-icons/AccountGroup.vue' -import IconCalendarRange from 'vue-material-design-icons/CalendarRange.vue' +import IconAccountGroup from 'vue-material-design-icons/AccountGroupOutline.vue' +import IconCalendarRange from 'vue-material-design-icons/CalendarRangeOutline.vue' import IconDotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue' import IconFilter from 'vue-material-design-icons/Filter.vue' import IconListBox from 'vue-material-design-icons/ListBox.vue' @@ -252,11 +252,10 @@ export default defineComponent({ providerResultLimit: 5, dateFilter: { id: 'date', type: 'date', text: '', startFrom: null, endAt: null }, personFilter: { id: 'person', type: 'person', name: '' }, - dateFilterIsApplied: false, - personFilterIsApplied: false, filteredProviders: [], searching: false, searchQuery: '', + lastSearchQuery: '', placessearchTerm: '', dateTimeFilter: null, filters: [], @@ -330,7 +329,13 @@ export default defineComponent({ query: { immediate: true, handler() { - this.searchQuery = this.query.trim() + this.searchQuery = this.query + }, + }, + + searchQuery: { + handler() { + this.$emit('update:query', this.searchQuery) }, }, }, @@ -362,17 +367,23 @@ export default defineComponent({ this.$refs.searchInput?.focus() }) }, - find(query: string) { + find(query: string, providersToSearchOverride = null) { if (query.length === 0) { this.results = [] this.searching = false return } + // Reset the provider result limit when performing a new search + if (query !== this.lastSearchQuery) { + this.providerResultLimit = 5 + } + this.lastSearchQuery = query + this.searching = true const newResults = [] - const providersToSearch = this.filteredProviders.length > 0 ? this.filteredProviders : this.providers - const searchProvider = (provider, filters) => { + const providersToSearch = providersToSearchOverride || (this.filteredProviders.length > 0 ? this.filteredProviders : this.providers) + const searchProvider = (provider) => { const params = { type: provider.searchFrom ?? provider.id, query, @@ -382,18 +393,25 @@ export default defineComponent({ // This block of filter checks should be dynamic somehow and should be handled in // nextcloud/search lib - if (filters.dateFilterIsApplied) { - if (provider.filters?.since && provider.filters?.until) { - params.since = this.dateFilter.startFrom - params.until = this.dateFilter.endAt - } - } + const activeFilters = this.filters.filter(filter => { + return filter.type !== 'provider' && this.providerIsCompatibleWithFilters(provider, [filter.type]) + }) - if (filters.personFilterIsApplied) { - if (provider.filters?.person) { - params.person = this.personFilter.user + activeFilters.forEach(filter => { + switch (filter.type) { + case 'date': + if (provider.filters?.since && provider.filters?.until) { + params.since = this.dateFilter.startFrom + params.until = this.dateFilter.endAt + } + break + case 'person': + if (provider.filters?.person) { + params.person = this.personFilter.user + } + break } - } + }) if (this.providerResultLimit > 5) { params.limit = this.providerResultLimit @@ -404,13 +422,9 @@ export default defineComponent({ request().then((response) => { newResults.push({ - id: provider.id, - appId: provider.appId, - searchFrom: provider.searchFrom, - icon: provider.icon, - name: provider.name, - inAppSearch: provider.inAppSearch, + ...provider, results: response.data.ocs.data.entries, + limit: params.limit ?? 5, }) unifiedSearchLogger.debug('Unified search results:', { results: this.results, newResults }) @@ -419,12 +433,8 @@ export default defineComponent({ this.searching = false }) } - providersToSearch.forEach(provider => { - const dateFilterIsApplied = this.dateFilterIsApplied - const personFilterIsApplied = this.personFilterIsApplied - searchProvider(provider, { dateFilterIsApplied, personFilterIsApplied }) - }) + providersToSearch.forEach(searchProvider) }, updateResults(newResults) { let updatedResults = [...this.results] @@ -482,7 +492,7 @@ export default defineComponent({ }) }, applyPersonFilter(person) { - this.personFilterIsApplied = true + const existingPersonFilter = this.filters.findIndex(filter => filter.id === person.id) if (existingPersonFilter === -1) { this.personFilter.id = person.id @@ -504,15 +514,7 @@ export default defineComponent({ }, async loadMoreResultsForProvider(provider) { this.providerResultLimit += 5 - // If load more result for filter, remove other filters - this.filters = this.filters.filter(filter => filter.id === provider.id) - this.filteredProviders = this.filteredProviders.filter(filteredProvider => filteredProvider.id === provider.id) - // Plugin filters may have extra parameters, so we need to keep them - // See method handlePluginFilter for more details - if (this.filteredProviders.length > 0 && this.filteredProviders[0].isPluginFilter) { - provider = this.filteredProviders[0] - } - this.addProviderFilter(provider, true) + this.find(this.searchQuery, [provider]) }, addProviderFilter(providerFilter, loadMoreResultsForProvider = false) { unifiedSearchLogger.debug('Applying provider filter', { providerFilter, loadMoreResultsForProvider }) @@ -556,14 +558,10 @@ export default defineComponent({ unifiedSearchLogger.debug('Search filters (recently removed)', { filters: this.filters }) } else { + // Remove non provider filters such as date and person filters for (let i = 0; i < this.filters.length; i++) { - // Remove date and person filter - if (this.filters[i].id === 'date' || this.filters[i].id === filter.id) { - this.dateFilterIsApplied = false + if (this.filters[i].id === filter.id) { this.filters.splice(i, 1) - if (filter.type === 'person') { - this.personFilterIsApplied = false - } this.enableAllProviders() break } @@ -602,7 +600,7 @@ export default defineComponent({ } else { this.filters.push(this.dateFilter) } - this.dateFilterIsApplied = true + this.providers.forEach(async (provider, index) => { this.providers[index].disabled = !(await this.providerIsCompatibleWithFilters(provider, ['since', 'until'])) }) diff --git a/core/src/components/login/PasswordLessLoginForm.vue b/core/src/components/login/PasswordLessLoginForm.vue index bbca2ebf31d..bc4d25bf70f 100644 --- a/core/src/components/login/PasswordLessLoginForm.vue +++ b/core/src/components/login/PasswordLessLoginForm.vue @@ -57,7 +57,7 @@ import { import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent' import NcTextField from '@nextcloud/vue/components/NcTextField' -import InformationIcon from 'vue-material-design-icons/Information.vue' +import InformationIcon from 'vue-material-design-icons/InformationOutline.vue' import LoginButton from './LoginButton.vue' import LockOpenIcon from 'vue-material-design-icons/LockOpen.vue' import logger from '../../logger' diff --git a/core/src/components/setup/RecommendedApps.vue b/core/src/components/setup/RecommendedApps.vue index aaad6d93476..f2120c28402 100644 --- a/core/src/components/setup/RecommendedApps.vue +++ b/core/src/components/setup/RecommendedApps.vue @@ -4,7 +4,7 @@ --> <template> - <div class="guest-box"> + <div class="guest-box" data-cy-setup-recommended-apps> <h2>{{ t('core', 'Recommended apps') }}</h2> <p v-if="loadingApps" class="loading text-center"> {{ t('core', 'Loading apps …') }} @@ -38,15 +38,16 @@ <div class="dialog-row"> <NcButton v-if="showInstallButton && !installingApps" - type="tertiary" - role="link" - :href="defaultPageUrl"> + data-cy-setup-recommended-apps-skip + :href="defaultPageUrl" + variant="tertiary"> {{ t('core', 'Skip') }} </NcButton> <NcButton v-if="showInstallButton" - type="primary" + data-cy-setup-recommended-apps-install :disabled="installingApps || !isAnyAppSelected" + variant="primary" @click.stop.prevent="installApps"> {{ installingApps ? t('core', 'Installing apps …') : t('core', 'Install recommended apps') }} </NcButton> |