Skip to content

Commit 4e687d9

Browse files
committed
Allow unified search filtering
Signed-off-by: John Molakvoæ (skjnldsv) <[email protected]>
1 parent 38621f2 commit 4e687d9

File tree

4 files changed

+188
-7
lines changed

4 files changed

+188
-7
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<!--
2+
- @copyright Copyright (c) 2020 John Molakvoæ <[email protected]>
3+
-
4+
- @author John Molakvoæ <[email protected]>
5+
-
6+
- @license GNU AGPL version 3 or any later version
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+
<template>
23+
<li>
24+
<a :title="t('core', 'Search for {name} only', { name })"
25+
class="unified-search__filter"
26+
href="#"
27+
@click.prevent="onClick">
28+
{{ filter }}
29+
</a>
30+
</li>
31+
</template>
32+
33+
<script>
34+
export default {
35+
name: 'SearchFilter',
36+
37+
props: {
38+
type: {
39+
type: String,
40+
required: true,
41+
},
42+
name: {
43+
type: String,
44+
required: true,
45+
},
46+
},
47+
computed: {
48+
filter() {
49+
return `in:${this.type}`
50+
},
51+
},
52+
53+
methods: {
54+
onClick() {
55+
this.$emit('click', this.filter)
56+
},
57+
},
58+
59+
}
60+
</script>
61+
62+
<style lang="scss" scoped>
63+
.unified-search__filter {
64+
height: 1em;
65+
margin-right: 5px;
66+
padding: 3px 8px;
67+
border-radius: 1em;
68+
background-color: var(--color-background-darker);
69+
70+
&:active,
71+
&:focus,
72+
&:hover {
73+
background-color: var(--color-background-hover);
74+
}
75+
}
76+
77+
</style>

core/src/components/UnifiedSearch/SearchResult.vue

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
<!--
2+
- @copyright Copyright (c) 2020 John Molakvoæ <[email protected]>
3+
-
4+
- @author John Molakvoæ <[email protected]>
5+
-
6+
- @license GNU AGPL version 3 or any later version
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+
-->
122
<template>
223
<a :href="resourceUrl || '#'"
324
class="unified-search__result"

core/src/services/UnifiedSearchService.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import axios from '@nextcloud/axios'
2525

2626
export const defaultLimit = loadState('unified-search', 'limit-default')
2727
export const minSearchLength = 2
28+
export const regexFilterIn = /[^-]in:([a-z_-]+)/ig
29+
export const regexFilterNot = /-in:([a-z_-]+)/ig
30+
2831
/**
2932
* Get the list of available search providers
3033
*

core/src/views/UnifiedSearch.vue

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@
4141
@keypress.enter.prevent.stop="onInputEnter">
4242
</div>
4343

44+
<!-- Search filters -->
45+
<div v-if="availableFilters.length > 1" class="unified-search__filters">
46+
<ul>
47+
<SearchFilter v-for="type in availableFilters"
48+
:key="type"
49+
:type="type"
50+
:name="typesMap[type]"
51+
@click="onClickFilter" />
52+
</ul>
53+
</div>
54+
4455
<template v-if="!hasResults">
4556
<!-- Loading placeholders -->
4657
<ul v-if="isLoading">
@@ -97,12 +108,13 @@
97108
</template>
98109

99110
<script>
100-
import { minSearchLength, getTypes, search, defaultLimit } from '../services/UnifiedSearchService'
111+
import { minSearchLength, getTypes, search, defaultLimit, regexFilterIn, regexFilterNot } from '../services/UnifiedSearchService'
101112
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
102113
import Magnify from 'vue-material-design-icons/Magnify'
103114
import debounce from 'debounce'
104115
105116
import HeaderMenu from '../components/HeaderMenu'
117+
import SearchFilter from '../components/UnifiedSearch/SearchFilter'
106118
import SearchResult from '../components/UnifiedSearch/SearchResult'
107119
import SearchResultPlaceholder from '../components/UnifiedSearch/SearchResultPlaceholder'
108120
@@ -113,6 +125,7 @@ export default {
113125
EmptyContent,
114126
HeaderMenu,
115127
Magnify,
128+
SearchFilter,
116129
SearchResult,
117130
SearchResultPlaceholder,
118131
},
@@ -161,17 +174,52 @@ export default {
161174
162175
/**
163176
* Return ordered results
164-
* @returns {Object}
177+
* @returns {Array}
165178
*/
166179
orderedResults() {
167-
return Object.values(this.typesIDs)
180+
return this.typesIDs
168181
.filter(type => type in this.results)
169182
.map(type => ({
170183
type,
171184
list: this.results[type],
172185
}))
173186
},
174187
188+
/**
189+
* Available filters
190+
* We only show filters that are available on the results
191+
* @returns {string[]}
192+
*/
193+
availableFilters() {
194+
return Object.keys(this.results)
195+
},
196+
197+
/**
198+
* Applied filters
199+
* @returns {string[]}
200+
*/
201+
usedFiltersIn() {
202+
let match
203+
const filters = []
204+
while ((match = regexFilterIn.exec(this.query)) !== null) {
205+
filters.push(match[1])
206+
}
207+
return filters
208+
},
209+
210+
/**
211+
* Applied anti filters
212+
* @returns {string[]}
213+
*/
214+
usedFiltersNot() {
215+
let match
216+
const filters = []
217+
while ((match = regexFilterNot.exec(this.query)) !== null) {
218+
filters.push(match[1])
219+
}
220+
return filters
221+
},
222+
175223
/**
176224
* Is the current search too short
177225
* @returns {boolean}
@@ -286,12 +334,30 @@ export default {
286334
return
287335
}
288336
337+
let types = this.typesIDs
338+
let query = this.query
339+
340+
// Filter out types
341+
if (this.usedFiltersNot.length > 0) {
342+
types = this.typesIDs.filter(type => this.usedFiltersNot.indexOf(type) === -1)
343+
}
344+
345+
// Only use those filters if any and check if they are valid
346+
if (this.usedFiltersIn.length > 0) {
347+
types = this.typesIDs.filter(type => this.usedFiltersIn.indexOf(type) > -1)
348+
}
349+
350+
// remove any filters from the query
351+
query = query.replace(regexFilterIn, '').replace(regexFilterNot, '')
352+
353+
console.debug('Searching', query, 'in', types)
354+
289355
// reset search if the query changed
290356
this.resetState()
291357
292-
this.typesIDs.forEach(async type => {
358+
types.forEach(async type => {
293359
this.$set(this.loading, type, true)
294-
const request = await search(type, this.query)
360+
const request = await search(type, query)
295361
296362
// Process results
297363
if (request.data.entries.length > 0) {
@@ -473,6 +539,13 @@ export default {
473539
this.focused = index
474540
}
475541
},
542+
543+
onClickFilter(filter) {
544+
this.query = `${this.query} ${filter}`
545+
.replace(/ {2}/g, ' ')
546+
.trim()
547+
this.onInput()
548+
},
476549
},
477550
}
478551
</script>
@@ -495,6 +568,14 @@ $input-padding: 6px;
495568
background-color: var(--color-main-background);
496569
}
497570
571+
&__filters {
572+
margin: $margin / 2 $margin;
573+
ul {
574+
display: inline-flex;
575+
justify-content: space-between;
576+
}
577+
}
578+
498579
&__input {
499580
// Minus margins
500581
width: calc(100% - 2 * #{$margin});
@@ -505,10 +586,9 @@ $input-padding: 6px;
505586
&[placeholder],
506587
&::placeholder {
507588
overflow: hidden;
508-
text-overflow:ellipsis;
509589
white-space: nowrap;
590+
text-overflow: ellipsis;
510591
}
511-
512592
}
513593
514594
&__results {

0 commit comments

Comments
 (0)