summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorfenn-cs <fenn25.fn@gmail.com>2023-11-18 13:40:04 +0100
committernextcloud-command <nextcloud-command@users.noreply.github.com>2023-11-21 13:29:03 +0000
commit701e626d1855beecb16433dec5a2ec9076951aba (patch)
tree0e5549c29778ec667222e97fcafeb43e8f6c549b /core
parent8dadb09e54b67ee8726d39ba7ee201a30fb963d5 (diff)
downloadnextcloud-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.vue2
-rw-r--r--core/src/components/GlobalSearch/SearchResult.vue168
-rw-r--r--core/src/views/GlobalSearchModal.vue101
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;