Skip to content

Commit

Permalink
Merge pull request #40823 from nextcloud/39162-global-search-2.0
Browse files Browse the repository at this point in the history
New UI for global search
  • Loading branch information
nfebe authored Nov 10, 2023
2 parents fa761b5 + 4e7a0e9 commit 95e5642
Show file tree
Hide file tree
Showing 97 changed files with 1,413 additions and 146 deletions.
7 changes: 7 additions & 0 deletions config/config.sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -2395,4 +2395,11 @@
* Defaults to ``true``
*/
'reference_opengraph' => true,

/**
* Enable use of old unified search
*
* Defaults to ``false``
*/
'unified_search.enabled' => false,
];
98 changes: 98 additions & 0 deletions core/src/components/GlobalSearch/CustomDateRangeModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<template>
<NcModal v-if="isModalOpen"
id="global-search"
:name="t('core', 'Date range filter')"
:show.sync="isModalOpen"
:size="'small'"
:clear-view-delay="0"
:title="t('Date range filter')"
@close="closeModal">
<!-- Custom date range -->
<div class="global-search-custom-date-modal">
<h1>{{ t('core', 'Date range filter') }}</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 start date')"
type="date" />
<NcDateTimePicker :id="'globalsearch-custom-date-range-end'"
v-model="dateFilter.endAt"
:max="new 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>
</NcModal>
</template>

<script>
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcDateTimePicker from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
import CalendarRangeIcon from 'vue-material-design-icons/CalendarRange.vue'
export default {
name: 'CustomDateRangeModal',
components: {
NcButton,
NcModal,
CalendarRangeIcon,
NcDateTimePicker,
},
props: {
isOpen: {
type: Boolean,
required: true,
},
},
data() {
return {
dateFilter: { startFrom: null, endAt: null },
}
},
computed: {
isModalOpen: {
get() {
return this.isOpen
},
set(value) {
this.$emit('update:is-open', value)
},
},
},
methods: {
closeModal() {
this.isModalOpen = false
},
applyCustomRange() {
this.$emit('set:custom-date-range', this.dateFilter)
this.closeModal()
},
},
}
</script>

<style lang="scss" scoped>
.global-search-custom-date-modal {
padding: 10px 20px 10px 20px;
h1 {
font-size: 16px;
font-weight: bolder;
line-height: 2em;
}
&__pickers {
display: flex;
flex-direction: column;
}
}
</style>
71 changes: 71 additions & 0 deletions core/src/components/GlobalSearch/SearchFilterChip.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<template>
<div class="chip">
<span class="icon">
<slot name="icon" />
<span v-if="pretext.length"> {{ pretext }} : </span>
</span>
<span class="text">{{ text }}</span>
<span class="close-icon" @click="deleteChip">
<CloseIcon :size="16" />
</span>
</div>
</template>

<script>
import CloseIcon from 'vue-material-design-icons/CloseThick.vue'
export default {
name: 'SearchFilterChip',
components: {
CloseIcon,
},
props: {
text: String,
pretext: String,
},
methods: {
deleteChip() {
this.$emit('delete', this.filter)
},
},
}
</script>

<style lang="scss" scoped>
.chip {
display: flex;
align-items: center;
padding: 2px 4px;
border: 1px solid var(--color-primary-element-light);
border-radius: 20px;
background-color: var(--color-primary-element-light);
margin: 2px;
font-size: 10px;
font-weight: bolder;
.icon {
display: flex;
align-items: center;
padding-right: 5px;
img {
width: 20px;
padding: 2px;
border-radius: 20px;
}
}
.text {
margin: 0 2px;
}
.close-icon {
cursor: pointer;
:hover {
border-radius: 4px;
padding: 1px;
}
}
}
</style>
157 changes: 157 additions & 0 deletions core/src/components/GlobalSearch/SearchableList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<!--
- @copyright 2023 Marco Ambrosini <marcoambrosini@proton.me>
-
- @author Marco Ambrosini <marcoambrosini@proton.me>
-
- @license AGPL-3.0-or-later
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->

<template>
<NcPopover :shown="opened">
<template #trigger>
<slot name="trigger" />
</template>
<div class="searchable-list__wrapper">
<NcTextField :value.sync="searchTerm"
:label="labelText"
trailing-button-icon="close"
:show-trailing-button="searchTerm !== ''"
@trailing-button-click="clearSearch">
<Magnify :size="20" />
</NcTextField>
<ul v-if="filteredList.length > 0" class="searchable-list__list">
<li v-for="element in filteredList"
:key="element.id"
:title="element.displayName"
role="button">
<NcButton alignment="start"
type="tertiary"
:wide="true"
@click="itemSelected(element)">
<template #icon>
<NcAvatar :user="element.user" :show-user-status="false" :hide-favorite="false" />
</template>
{{ element.displayName }}
</NcButton>
</li>
</ul>
<div v-else class="searchable-list__empty-content">
<NcEmptyContent :name="emptyContentText">
<template #icon>
<AlertCircleOutline />
</template>
</NcEmptyContent>
</div>
</div>
</NcPopover>
</template>

<script>
import { NcPopover, NcTextField, NcAvatar, NcEmptyContent, NcButton } from '@nextcloud/vue'

import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
import Magnify from 'vue-material-design-icons/Magnify.vue'

export default {
name: 'SearchableList',

components: {
NcPopover,
NcTextField,
Magnify,
AlertCircleOutline,
NcAvatar,
NcEmptyContent,
NcButton,
},

props: {
labelText: {
type: String,
default: 'this is a label',
},

searchList: {
type: Array,
required: true,
},

emptyContentText: {
type: String,
required: true,
},
},

data() {
return {
opened: false,
error: false,
searchTerm: '',
}
},

computed: {
filteredList() {
return this.searchList.filter((element) => {
if (!this.searchTerm.toLowerCase().length) {
return true
}
return ['displayName'].some(prop => element[prop].toLowerCase().includes(this.searchTerm.toLowerCase()))
})
},
},

methods: {
clearSearch() {
this.searchTerm = ''
},
itemSelected(element) {
this.$emit('item-selected', element)
this.clearSearch()
this.opened = false
},
},
}
</script>

<style lang="scss" scoped>
.searchable-list {
&__wrapper {
padding: calc(var(--default-grid-baseline) * 3);
display: flex;
flex-direction: column;
align-items: center;
width: 250px;
}

&__list {
width: 100%;
max-height: 284px;
overflow-y: auto;
margin-top: var(--default-grid-baseline);
padding: var(--default-grid-baseline);

:deep(.button-vue) {
border-radius: var(--border-radius-large) !important;
}
}

&__empty-content {
margin-top: calc(var(--default-grid-baseline) * 3);
}
}
</style>
55 changes: 55 additions & 0 deletions core/src/global-search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @copyright Copyright (c) 2020 Fon E. Noel NFEBE <fenn25.fn@gmail.com>
*
* @author Fon E. Noel NFEBE <fenn25.fn@gmail.com>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

import { getLoggerBuilder } from '@nextcloud/logger'
import { getRequestToken } from '@nextcloud/auth'
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import Vue from 'vue'

import GlobalSearch from './views/GlobalSearch.vue'

// eslint-disable-next-line camelcase
__webpack_nonce__ = btoa(getRequestToken())

const logger = getLoggerBuilder()
.setApp('global-search')
.detectUser()
.build()

Vue.mixin({
data() {
return {
logger,
}
},
methods: {
t,
n,
},
})

export default new Vue({
el: '#global-search',
// eslint-disable-next-line vue/match-component-file-name
name: 'GlobalSearchRoot',
render: h => h(GlobalSearch),
})
Loading

0 comments on commit 95e5642

Please sign in to comment.