Merge pull request #41507 from nextcloud/41381-global-search-follow-up-2

Enhancements: Fix and updates to most recent global search UI
pull/41648/head
F. E Noel Nfebe 2023-11-21 15:26:53 +07:00 committed by GitHub
commit 163e8b616c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 203 additions and 125 deletions

@ -1,33 +1,33 @@
<template>
<NcModal v-if="isModalOpen"
id="global-search"
:name="t('core', 'Date range filter')"
:name="t('core', 'Custom date range')"
:show.sync="isModalOpen"
:size="'small'"
:clear-view-delay="0"
:title="t('Date range filter')"
:title="t('core', 'Custom date range')"
@close="closeModal">
<!-- Custom date range -->
<div class="global-search-custom-date-modal">
<h1>{{ t('core', 'Date range filter') }}</h1>
<h1>{{ t('core', 'Custom date range') }}</h1>
<div class="global-search-custom-date-modal__pickers">
<NcDateTimePicker :id="'globalsearch-custom-date-range-start'"
v-model="dateFilter.startFrom"
:max="new Date()"
:label="t('core', 'Pick a start date')"
:label="t('core', 'Pick start date')"
type="date" />
<NcDateTimePicker :id="'globalsearch-custom-date-range-end'"
v-model="dateFilter.endAt"
:max="new Date()"
:label="t('core', 'Pick an end date')"
:label="t('core', 'Pick end date')"
type="date" />
</div>
<NcButton @click="applyCustomRange">
{{ t('core', 'Apply range') }}
<template #icon>
<CalendarRangeIcon :size="20" />
</template>
</NcButton>
<div class="global-search-custom-date-modal__footer">
<NcButton @click="applyCustomRange">
{{ t('core', 'Search in date range') }}
<template #icon>
<CalendarRangeIcon :size="20" />
</template>
</NcButton>
</div>
</div>
</NcModal>
</template>
@ -94,5 +94,10 @@ export default {
flex-direction: column;
}
&__footer {
display: flex;
justify-content: end;
}
}
</style>

@ -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>

@ -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 v-if="result.icon"
class="result-items__item-icon"
:class="{
'result-items__item-icon--no-preview': !isValidUrl(result.thumbnailUrl),
'result-items__item-icon--with-thumbnail': isValidUrl(result.thumbnailUrl),
[result.icon]: !isValidUrl(result.icon),
}"
:style="{
backgroundImage: isValidUrl(result.icon) ? `url(${result.icon})` : '',
}">
<img v-if="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: {
@ -462,35 +439,36 @@ export default {
applyQuickDateRange(range) {
this.dateActionMenuIsOpen = false
const today = new Date()
let endDate = today
let startDate
let endDate
switch (range) {
case 'today':
// For 'Today', both start and end are set to today
startDate = today
startDate = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0, 0)
endDate = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59, 999)
this.dateFilter.text = t('core', 'Today')
break
case '7days':
// For 'Last 7 days', start date is 7 days ago, end is today
startDate = new Date(today)
startDate.setDate(today.getDate() - 7)
startDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 6, 0, 0, 0, 0)
this.dateFilter.text = t('core', 'Last 7 days')
break
case '30days':
// For 'Last 30 days', start date is 30 days ago, end is today
startDate = new Date(today)
startDate.setDate(today.getDate() - 30)
startDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 29, 0, 0, 0, 0)
this.dateFilter.text = t('core', 'Last 30 days')
break
case 'thisyear':
// For 'This year', start date is the first day of the year, end is today
startDate = new Date(today.getFullYear(), 0, 1)
// For 'This year', start date is the first day of the year, end is the last day of the year
startDate = new Date(today.getFullYear(), 0, 1, 0, 0, 0, 0)
endDate = new Date(today.getFullYear(), 11, 31, 23, 59, 59, 999)
this.dateFilter.text = t('core', 'This year')
break
case 'lastyear':
// For 'Last year', start date is the first day of the previous year, end is the last day of the previous year
startDate = new Date(today.getFullYear() - 1, 0, 1)
endDate = new Date(today.getFullYear() - 1, 11, 31)
startDate = new Date(today.getFullYear() - 1, 0, 1, 0, 0, 0, 0)
endDate = new Date(today.getFullYear() - 1, 11, 31, 23, 59, 59, 999)
this.dateFilter.text = t('core', 'Last year')
break
case 'custom':
@ -498,7 +476,6 @@ export default {
return
default:
return
}
this.dateFilter.startFrom = startDate
this.dateFilter.endAt = endDate
@ -512,9 +489,6 @@ export default {
this.dateFilter.text = t('core', `Between ${this.dateFilter.startFrom.toLocaleDateString()} and ${this.dateFilter.endAt.toLocaleDateString()}`)
this.updateDateFilter()
},
isValidUrl(icon) {
return /^https?:\/\//.test(icon) || icon.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;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long