diff options
author | fenn-cs <fenn25.fn@gmail.com> | 2023-11-18 13:40:04 +0100 |
---|---|---|
committer | nextcloud-command <nextcloud-command@users.noreply.github.com> | 2023-11-21 13:29:03 +0000 |
commit | 701e626d1855beecb16433dec5a2ec9076951aba (patch) | |
tree | 0e5549c29778ec667222e97fcafeb43e8f6c549b /core | |
parent | 8dadb09e54b67ee8726d39ba7ee201a30fb963d5 (diff) | |
download | nextcloud-server-701e626d1855beecb16433dec5a2ec9076951aba.tar.gz nextcloud-server-701e626d1855beecb16433dec5a2ec9076951aba.zip |
Move search result section to component & hide thumbnail if error
The section that handles the search result list item is big and complex enough
to have it's own component. Inside this component, a new method is added to hide
the thumbnail preview if the image load errors out.
Signed-off-by: fenn-cs <fenn25.fn@gmail.com>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
Diffstat (limited to 'core')
-rw-r--r-- | core/src/components/GlobalSearch/CustomDateRangeModal.vue | 2 | ||||
-rw-r--r-- | core/src/components/GlobalSearch/SearchResult.vue | 168 | ||||
-rw-r--r-- | core/src/views/GlobalSearchModal.vue | 101 |
3 files changed, 172 insertions, 99 deletions
diff --git a/core/src/components/GlobalSearch/CustomDateRangeModal.vue b/core/src/components/GlobalSearch/CustomDateRangeModal.vue index 8effdcf0fe0..0ba6ddca015 100644 --- a/core/src/components/GlobalSearch/CustomDateRangeModal.vue +++ b/core/src/components/GlobalSearch/CustomDateRangeModal.vue @@ -5,7 +5,7 @@ :show.sync="isModalOpen" :size="'small'" :clear-view-delay="0" - :title="t('Custom date range')" + :title="t('core', 'Custom date range')" @close="closeModal"> <!-- Custom date range --> <div class="global-search-custom-date-modal"> diff --git a/core/src/components/GlobalSearch/SearchResult.vue b/core/src/components/GlobalSearch/SearchResult.vue new file mode 100644 index 00000000000..7647ca298ed --- /dev/null +++ b/core/src/components/GlobalSearch/SearchResult.vue @@ -0,0 +1,168 @@ +<template> + <NcListItem class="result-items__item" + :name="title" + :bold="false" + @click="openResult(result)"> + <template #icon> + <div aria-hidden="true" + class="result-items__item-icon" + :class="{ + 'result-items__item-icon--rounded': rounded, + 'result-items__item-icon--no-preview': !isValidIconOrPreviewUrl(thumbnailUrl), + 'result-items__item-icon--with-thumbnail': isValidIconOrPreviewUrl(thumbnailUrl), + [icon]: !isValidIconOrPreviewUrl(icon), + }" + :style="{ + backgroundImage: isValidIconOrPreviewUrl(icon) ? `url(${icon})` : '', + }"> + <img v-if="isValidIconOrPreviewUrl(thumbnailUrl) && !thumbnailHasError" + :src="thumbnailUrl" + @error="thumbnailErrorHandler"> + </div> + </template> + <template #subname> + {{ subline }} + </template> + </NcListItem> +</template> + +<script> +import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js' + +export default { + name: 'SearchResult', + components: { + NcListItem, + }, + props: { + thumbnailUrl: { + type: String, + default: null, + }, + title: { + type: String, + required: true, + }, + subline: { + type: String, + default: null, + }, + resourceUrl: { + type: String, + default: null, + }, + icon: { + type: String, + default: '', + }, + rounded: { + type: Boolean, + default: false, + }, + query: { + type: String, + default: '', + }, + + /** + * Only used for the first result as a visual feedback + * so we can keep the search input focused but pressing + * enter still opens the first result + */ + focused: { + type: Boolean, + default: false, + }, + }, + data() { + return { + thumbnailHasError: false, + } + }, + watch: { + thumbnailUrl() { + this.thumbnailHasError = false + }, + }, + methods: { + isValidIconOrPreviewUrl(url) { + return /^https?:\/\//.test(url) || url.startsWith('/') + }, + thumbnailErrorHandler() { + this.thumbnailHasError = true + }, + }, +} +</script> + +<style lang="scss" scoped> +@use "sass:math"; +$clickable-area: 44px; +$margin: 10px; + +.result-items { + &__item { + + ::v-deep a { + border-radius: 12px; + border: 2px solid transparent; + border-radius: var(--border-radius-large) !important; + + &--focused { + background-color: var(--color-background-hover); + } + + &:active, + &:hover, + &:focus { + background-color: var(--color-background-hover); + border: 2px solid var(--color-border-maxcontrast); + } + + * { + cursor: pointer; + } + + } + + &-icon { + overflow: hidden; + width: $clickable-area; + height: $clickable-area; + border-radius: var(--border-radius); + background-repeat: no-repeat; + background-position: center center; + background-size: 32px; + + &--rounded { + border-radius: math.div($clickable-area, 2); + } + + &--no-preview { + background-size: 32px; + } + + &--with-thumbnail { + background-size: cover; + } + + &--with-thumbnail:not(&--rounded) { + // compensate for border + max-width: $clickable-area - 2px; + max-height: $clickable-area - 2px; + border: 1px solid var(--color-border); + } + + img { + // Make sure to keep ratio + width: 100%; + height: 100%; + + object-fit: cover; + object-position: center; + } + } + + } +} +</style> diff --git a/core/src/views/GlobalSearchModal.vue b/core/src/views/GlobalSearchModal.vue index a9d422e0ff8..d7e911c35fa 100644 --- a/core/src/views/GlobalSearchModal.vue +++ b/core/src/views/GlobalSearchModal.vue @@ -97,30 +97,7 @@ <span>{{ providerResult.provider }}</span> </div> <ul class="result-items"> - <NcListItem v-for="(result, index) in providerResult.results" - :key="index" - class="result-items__item" - :name="result.title ?? ''" - :bold="false" - @click="openResult(result)"> - <template #icon> - <div class="result-items__item-icon" - :class="{ - 'result-items__item-icon--rounded': result.rounded, - 'result-items__item-icon--no-preview': !isValidIconOrPreviewUrl(result.thumbnailUrl), - 'result-items__item-icon--with-thumbnail': isValidIconOrPreviewUrl(result.thumbnailUrl), - [result.icon]: !isValidIconOrPreviewUrl(result.icon), - }" - :style="{ - backgroundImage: isValidIconOrPreviewUrl(result.icon) ? `url(${result.icon})` : '', - }"> - <img v-if="isValidIconOrPreviewUrl(result.thumbnailUrl)" :src="result.thumbnailUrl" class=""> - </div> - </template> - <template #subname> - {{ result.subline }} - </template> - </NcListItem> + <SearchResult v-for="(result, index) in providerResult.results" :key="index" v-bind="result" /> </ul> <div class="result-footer"> <NcButton type="tertiary-no-background" @click="loadMoreResultsForProvider(providerResult.id)"> @@ -158,9 +135,9 @@ import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js' import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js' import NcModal from '@nextcloud/vue/dist/Components/NcModal.js' -import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js' import MagnifyIcon from 'vue-material-design-icons/Magnify.vue' import SearchableList from '../components/GlobalSearch/SearchableList.vue' +import SearchResult from '../components/GlobalSearch/SearchResult.vue' import debounce from 'debounce' import { getProviders, search as globalSearch, getContacts } from '../services/GlobalSearchService.js' @@ -182,10 +159,10 @@ export default { NcButton, NcEmptyContent, NcModal, - NcListItem, NcInputField, MagnifyIcon, SearchableList, + SearchResult, }, props: { isVisible: { @@ -512,9 +489,6 @@ export default { this.dateFilter.text = t('core', `Between ${this.dateFilter.startFrom.toLocaleDateString()} and ${this.dateFilter.endAt.toLocaleDateString()}`) this.updateDateFilter() }, - isValidIconOrPreviewUrl(url) { - return /^https?:\/\//.test(url) || url.startsWith('/') - }, closeModal() { this.searchQuery = '' }, @@ -523,10 +497,6 @@ export default { </script> <style lang="scss" scoped> -@use "sass:math"; -$clickable-area: 44px; -$margin: 10px; - .global-search-modal { padding: 10px 20px 10px 20px; height: 60%; @@ -574,71 +544,6 @@ $margin: 10px; } } - .result-items { - ::v-deep &__item { - a { - border-radius: 12px; - border: 2px solid transparent; - border-radius: var(--border-radius-large) !important; - - &--focused { - background-color: var(--color-background-hover); - } - - &:active, - &:hover, - &:focus { - background-color: var(--color-background-hover); - border: 2px solid var(--color-border-maxcontrast); - } - - * { - cursor: pointer; - } - - } - - &-icon { - overflow: hidden; - width: $clickable-area; - height: $clickable-area; - border-radius: var(--border-radius); - background-repeat: no-repeat; - background-position: center center; - background-size: 32px; - - &--rounded { - border-radius: math.div($clickable-area, 2); - } - - &--no-preview { - background-size: 32px; - } - - &--with-thumbnail { - background-size: cover; - } - - &--with-thumbnail:not(&--rounded) { - // compensate for border - max-width: $clickable-area - 2px; - max-height: $clickable-area - 2px; - border: 1px solid var(--color-border); - } - - img { - // Make sure to keep ratio - width: 100%; - height: 100%; - - object-fit: cover; - object-position: center; - } - } - - } - } - .result-footer { justify-content: space-between; align-items: center; |