Skip to content

Commit 95e5642

Browse files
authored
Merge pull request #40823 from nextcloud/39162-global-search-2.0
New UI for global search
2 parents fa761b5 + 4e7a0e9 commit 95e5642

97 files changed

Lines changed: 1413 additions & 146 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

config/config.sample.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2395,4 +2395,11 @@
23952395
* Defaults to ``true``
23962396
*/
23972397
'reference_opengraph' => true,
2398+
2399+
/**
2400+
* Enable use of old unified search
2401+
*
2402+
* Defaults to ``false``
2403+
*/
2404+
'unified_search.enabled' => false,
23982405
];
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<template>
2+
<NcModal v-if="isModalOpen"
3+
id="global-search"
4+
:name="t('core', 'Date range filter')"
5+
:show.sync="isModalOpen"
6+
:size="'small'"
7+
:clear-view-delay="0"
8+
:title="t('Date range filter')"
9+
@close="closeModal">
10+
<!-- Custom date range -->
11+
<div class="global-search-custom-date-modal">
12+
<h1>{{ t('core', 'Date range filter') }}</h1>
13+
<div class="global-search-custom-date-modal__pickers">
14+
<NcDateTimePicker :id="'globalsearch-custom-date-range-start'"
15+
v-model="dateFilter.startFrom"
16+
:max="new Date()"
17+
:label="t('core', 'Pick start date')"
18+
type="date" />
19+
<NcDateTimePicker :id="'globalsearch-custom-date-range-end'"
20+
v-model="dateFilter.endAt"
21+
:max="new Date()"
22+
:label="t('core', 'Pick end date')"
23+
type="date" />
24+
</div>
25+
<NcButton @click="applyCustomRange">
26+
{{ t('core', 'Apply range') }}
27+
<template #icon>
28+
<CalendarRangeIcon :size="20" />
29+
</template>
30+
</NcButton>
31+
</div>
32+
</NcModal>
33+
</template>
34+
35+
<script>
36+
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
37+
import NcDateTimePicker from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
38+
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
39+
import CalendarRangeIcon from 'vue-material-design-icons/CalendarRange.vue'
40+
41+
export default {
42+
name: 'CustomDateRangeModal',
43+
components: {
44+
NcButton,
45+
NcModal,
46+
CalendarRangeIcon,
47+
NcDateTimePicker,
48+
},
49+
props: {
50+
isOpen: {
51+
type: Boolean,
52+
required: true,
53+
},
54+
},
55+
data() {
56+
return {
57+
dateFilter: { startFrom: null, endAt: null },
58+
}
59+
},
60+
computed: {
61+
isModalOpen: {
62+
get() {
63+
return this.isOpen
64+
},
65+
set(value) {
66+
this.$emit('update:is-open', value)
67+
},
68+
},
69+
},
70+
methods: {
71+
closeModal() {
72+
this.isModalOpen = false
73+
},
74+
applyCustomRange() {
75+
this.$emit('set:custom-date-range', this.dateFilter)
76+
this.closeModal()
77+
},
78+
},
79+
}
80+
</script>
81+
82+
<style lang="scss" scoped>
83+
.global-search-custom-date-modal {
84+
padding: 10px 20px 10px 20px;
85+
86+
h1 {
87+
font-size: 16px;
88+
font-weight: bolder;
89+
line-height: 2em;
90+
}
91+
92+
&__pickers {
93+
display: flex;
94+
flex-direction: column;
95+
}
96+
97+
}
98+
</style>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<template>
2+
<div class="chip">
3+
<span class="icon">
4+
<slot name="icon" />
5+
<span v-if="pretext.length"> {{ pretext }} : </span>
6+
</span>
7+
<span class="text">{{ text }}</span>
8+
<span class="close-icon" @click="deleteChip">
9+
<CloseIcon :size="16" />
10+
</span>
11+
</div>
12+
</template>
13+
14+
<script>
15+
import CloseIcon from 'vue-material-design-icons/CloseThick.vue'
16+
17+
export default {
18+
name: 'SearchFilterChip',
19+
components: {
20+
CloseIcon,
21+
},
22+
props: {
23+
text: String,
24+
pretext: String,
25+
},
26+
methods: {
27+
deleteChip() {
28+
this.$emit('delete', this.filter)
29+
},
30+
},
31+
}
32+
</script>
33+
34+
<style lang="scss" scoped>
35+
.chip {
36+
display: flex;
37+
align-items: center;
38+
padding: 2px 4px;
39+
border: 1px solid var(--color-primary-element-light);
40+
border-radius: 20px;
41+
background-color: var(--color-primary-element-light);
42+
margin: 2px;
43+
font-size: 10px;
44+
font-weight: bolder;
45+
46+
.icon {
47+
display: flex;
48+
align-items: center;
49+
padding-right: 5px;
50+
51+
img {
52+
width: 20px;
53+
padding: 2px;
54+
border-radius: 20px;
55+
}
56+
}
57+
58+
.text {
59+
margin: 0 2px;
60+
}
61+
62+
.close-icon {
63+
cursor: pointer;
64+
65+
:hover {
66+
border-radius: 4px;
67+
padding: 1px;
68+
}
69+
}
70+
}
71+
</style>
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
<!--
2+
- @copyright 2023 Marco Ambrosini <marcoambrosini@proton.me>
3+
-
4+
- @author Marco Ambrosini <marcoambrosini@proton.me>
5+
-
6+
- @license AGPL-3.0-or-later
7+
-
8+
- This program is free software: you can redistribute it and/or modify
9+
- it under the terms of the GNU Affero General Public License as
10+
- published by the Free Software Foundation, either version 3 of the
11+
- License, or (at your option) any later version.
12+
-
13+
- This program is distributed in the hope that it will be useful,
14+
- but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
- GNU Affero General Public License for more details.
17+
-
18+
- You should have received a copy of the GNU Affero General Public License
19+
- along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
-
21+
-->
22+
23+
<template>
24+
<NcPopover :shown="opened">
25+
<template #trigger>
26+
<slot name="trigger" />
27+
</template>
28+
<div class="searchable-list__wrapper">
29+
<NcTextField :value.sync="searchTerm"
30+
:label="labelText"
31+
trailing-button-icon="close"
32+
:show-trailing-button="searchTerm !== ''"
33+
@trailing-button-click="clearSearch">
34+
<Magnify :size="20" />
35+
</NcTextField>
36+
<ul v-if="filteredList.length > 0" class="searchable-list__list">
37+
<li v-for="element in filteredList"
38+
:key="element.id"
39+
:title="element.displayName"
40+
role="button">
41+
<NcButton alignment="start"
42+
type="tertiary"
43+
:wide="true"
44+
@click="itemSelected(element)">
45+
<template #icon>
46+
<NcAvatar :user="element.user" :show-user-status="false" :hide-favorite="false" />
47+
</template>
48+
{{ element.displayName }}
49+
</NcButton>
50+
</li>
51+
</ul>
52+
<div v-else class="searchable-list__empty-content">
53+
<NcEmptyContent :name="emptyContentText">
54+
<template #icon>
55+
<AlertCircleOutline />
56+
</template>
57+
</NcEmptyContent>
58+
</div>
59+
</div>
60+
</NcPopover>
61+
</template>
62+
63+
<script>
64+
import { NcPopover, NcTextField, NcAvatar, NcEmptyContent, NcButton } from '@nextcloud/vue'
65+
66+
import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
67+
import Magnify from 'vue-material-design-icons/Magnify.vue'
68+
69+
export default {
70+
name: 'SearchableList',
71+
72+
components: {
73+
NcPopover,
74+
NcTextField,
75+
Magnify,
76+
AlertCircleOutline,
77+
NcAvatar,
78+
NcEmptyContent,
79+
NcButton,
80+
},
81+
82+
props: {
83+
labelText: {
84+
type: String,
85+
default: 'this is a label',
86+
},
87+
88+
searchList: {
89+
type: Array,
90+
required: true,
91+
},
92+
93+
emptyContentText: {
94+
type: String,
95+
required: true,
96+
},
97+
},
98+
99+
data() {
100+
return {
101+
opened: false,
102+
error: false,
103+
searchTerm: '',
104+
}
105+
},
106+
107+
computed: {
108+
filteredList() {
109+
return this.searchList.filter((element) => {
110+
if (!this.searchTerm.toLowerCase().length) {
111+
return true
112+
}
113+
return ['displayName'].some(prop => element[prop].toLowerCase().includes(this.searchTerm.toLowerCase()))
114+
})
115+
},
116+
},
117+
118+
methods: {
119+
clearSearch() {
120+
this.searchTerm = ''
121+
},
122+
itemSelected(element) {
123+
this.$emit('item-selected', element)
124+
this.clearSearch()
125+
this.opened = false
126+
},
127+
},
128+
}
129+
</script>
130+
131+
<style lang="scss" scoped>
132+
.searchable-list {
133+
&__wrapper {
134+
padding: calc(var(--default-grid-baseline) * 3);
135+
display: flex;
136+
flex-direction: column;
137+
align-items: center;
138+
width: 250px;
139+
}
140+
141+
&__list {
142+
width: 100%;
143+
max-height: 284px;
144+
overflow-y: auto;
145+
margin-top: var(--default-grid-baseline);
146+
padding: var(--default-grid-baseline);
147+
148+
:deep(.button-vue) {
149+
border-radius: var(--border-radius-large) !important;
150+
}
151+
}
152+
153+
&__empty-content {
154+
margin-top: calc(var(--default-grid-baseline) * 3);
155+
}
156+
}
157+
</style>

core/src/global-search.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* @copyright Copyright (c) 2020 Fon E. Noel NFEBE <fenn25.fn@gmail.com>
3+
*
4+
* @author Fon E. Noel NFEBE <fenn25.fn@gmail.com>
5+
*
6+
* @license AGPL-3.0-or-later
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Affero General Public License as
10+
* published by the Free Software Foundation, either version 3 of the
11+
* License, or (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU Affero General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Affero General Public License
19+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
*
21+
*/
22+
23+
import { getLoggerBuilder } from '@nextcloud/logger'
24+
import { getRequestToken } from '@nextcloud/auth'
25+
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
26+
import Vue from 'vue'
27+
28+
import GlobalSearch from './views/GlobalSearch.vue'
29+
30+
// eslint-disable-next-line camelcase
31+
__webpack_nonce__ = btoa(getRequestToken())
32+
33+
const logger = getLoggerBuilder()
34+
.setApp('global-search')
35+
.detectUser()
36+
.build()
37+
38+
Vue.mixin({
39+
data() {
40+
return {
41+
logger,
42+
}
43+
},
44+
methods: {
45+
t,
46+
n,
47+
},
48+
})
49+
50+
export default new Vue({
51+
el: '#global-search',
52+
// eslint-disable-next-line vue/match-component-file-name
53+
name: 'GlobalSearchRoot',
54+
render: h => h(GlobalSearch),
55+
})

0 commit comments

Comments
 (0)