Skip to content

Commit

Permalink
Allow unified search filtering
Browse files Browse the repository at this point in the history
Signed-off-by: John Molakvoæ (skjnldsv) <[email protected]>
  • Loading branch information
skjnldsv committed Aug 5, 2020
1 parent 55473dd commit 6f15bb7
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 7 deletions.
77 changes: 77 additions & 0 deletions core/src/components/UnifiedSearch/SearchFilter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<!--
- @copyright Copyright (c) 2020 John Molakvoæ <[email protected]>
-
- @author John Molakvoæ <[email protected]>
-
- @license GNU AGPL version 3 or any later version
-
- 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>
<li>
<a :title="t('core', 'Search for {name} only', { name })"
class="unified-search__filter"
href="#"
@click.prevent="onClick">
{{ filter }}
</a>
</li>
</template>

<script>
export default {
name: 'SearchFilter',
props: {
type: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
},
computed: {
filter() {
return `in:${this.type}`
},
},
methods: {
onClick() {
this.$emit('click', this.filter)
},
},
}
</script>

<style lang="scss" scoped>
.unified-search__filter {
height: 1em;
margin-right: 5px;
padding: 3px 8px;
border-radius: 1em;
background-color: var(--color-background-darker);
&:active,
&:focus,
&:hover {
background-color: var(--color-background-hover);
}
}
</style>
21 changes: 21 additions & 0 deletions core/src/components/UnifiedSearch/SearchResult.vue
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
<!--
- @copyright Copyright (c) 2020 John Molakvoæ <[email protected]>
-
- @author John Molakvoæ <[email protected]>
-
- @license GNU AGPL version 3 or any later version
-
- 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>
<a :href="resourceUrl || '#'"
class="unified-search__result"
Expand Down
3 changes: 3 additions & 0 deletions core/src/services/UnifiedSearchService.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import axios from '@nextcloud/axios'

export const defaultLimit = loadState('unified-search', 'limit-default')
export const minSearchLength = 2
export const regexFilterIn = /[^-]in:([a-z_-]+)/ig
export const regexFilterNot = /-in:([a-z_-]+)/ig

/**
* Get the list of available search providers
*
Expand Down
94 changes: 87 additions & 7 deletions core/src/views/UnifiedSearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@
@keypress.enter.prevent.stop="onInputEnter">
</div>

<!-- Search filters -->
<div v-if="availableFilters.length > 1" class="unified-search__filters">
<ul>
<SearchFilter v-for="type in availableFilters"
:key="type"
:type="type"
:name="typesMap[type]"
@click="onClickFilter" />
</ul>
</div>

<template v-if="!hasResults">
<!-- Loading placeholders -->
<ul v-if="isLoading">
Expand Down Expand Up @@ -97,12 +108,13 @@
</template>

<script>
import { minSearchLength, getTypes, search, defaultLimit } from '../services/UnifiedSearchService'
import { minSearchLength, getTypes, search, defaultLimit, regexFilterIn, regexFilterNot } from '../services/UnifiedSearchService'
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
import Magnify from 'vue-material-design-icons/Magnify'
import debounce from 'debounce'
import HeaderMenu from '../components/HeaderMenu'
import SearchFilter from '../components/UnifiedSearch/SearchFilter'
import SearchResult from '../components/UnifiedSearch/SearchResult'
import SearchResultPlaceholder from '../components/UnifiedSearch/SearchResultPlaceholder'
Expand All @@ -113,6 +125,7 @@ export default {
EmptyContent,
HeaderMenu,
Magnify,
SearchFilter,
SearchResult,
SearchResultPlaceholder,
},
Expand Down Expand Up @@ -161,17 +174,52 @@ export default {
/**
* Return ordered results
* @returns {Object}
* @returns {Array}
*/
orderedResults() {
return Object.values(this.typesIDs)
return this.typesIDs
.filter(type => type in this.results)
.map(type => ({
type,
list: this.results[type],
}))
},
/**
* Available filters
* We only show filters that are available on the results
* @returns {string[]}
*/
availableFilters() {
return Object.keys(this.results)
},
/**
* Applied filters
* @returns {string[]}
*/
usedFiltersIn() {
let match
const filters = []
while ((match = regexFilterIn.exec(this.query)) !== null) {
filters.push(match[1])
}
return filters
},
/**
* Applied anti filters
* @returns {string[]}
*/
usedFiltersNot() {
let match
const filters = []
while ((match = regexFilterNot.exec(this.query)) !== null) {
filters.push(match[1])
}
return filters
},
/**
* Is the current search too short
* @returns {boolean}
Expand Down Expand Up @@ -286,12 +334,30 @@ export default {
return
}
let types = this.typesIDs
let query = this.query
// Filter out types
if (this.usedFiltersNot.length > 0) {
types = this.typesIDs.filter(type => this.usedFiltersNot.indexOf(type) === -1)
}
// Only use those filters if any and check if they are valid
if (this.usedFiltersIn.length > 0) {
types = this.typesIDs.filter(type => this.usedFiltersIn.indexOf(type) > -1)
}
// remove any filters from the query
query = query.replace(regexFilterIn, '').replace(regexFilterNot, '')
console.debug('Searching', query, 'in', types)
// reset search if the query changed
this.resetState()
this.typesIDs.forEach(async type => {
types.forEach(async type => {
this.$set(this.loading, type, true)
const request = await search(type, this.query)
const request = await search(type, query)
// Process results
if (request.data.entries.length > 0) {
Expand Down Expand Up @@ -473,6 +539,13 @@ export default {
this.focused = index
}
},
onClickFilter(filter) {
this.query = `${this.query} ${filter}`
.replace(/ {2}/g, ' ')
.trim()
this.onInput()
},
},
}
</script>
Expand All @@ -495,6 +568,14 @@ $input-padding: 6px;
background-color: var(--color-main-background);
}
&__filters {
margin: $margin / 2 $margin;
ul {
display: inline-flex;
justify-content: space-between;
}
}
&__input {
// Minus margins
width: calc(100% - 2 * #{$margin});
Expand All @@ -505,10 +586,9 @@ $input-padding: 6px;
&[placeholder],
&::placeholder {
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
text-overflow: ellipsis;
}
}
&__results {
Expand Down

0 comments on commit 6f15bb7

Please sign in to comment.