diff options
author | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2020-08-05 12:56:11 +0200 |
---|---|---|
committer | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2020-09-03 09:03:09 +0200 |
commit | f359529555fbf6fa344869d01f0b3c17ecce46c8 (patch) | |
tree | dbd2458d47cd6c585297930049fe7a8968390e12 /core/src | |
parent | 34aca4632574d153c716589a08497c0957b3679f (diff) | |
download | nextcloud-server-f359529555fbf6fa344869d01f0b3c17ecce46c8.tar.gz nextcloud-server-f359529555fbf6fa344869d01f0b3c17ecce46c8.zip |
Allow unified search filtering
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'core/src')
-rw-r--r-- | core/src/components/UnifiedSearch/SearchFilter.vue | 77 | ||||
-rw-r--r-- | core/src/components/UnifiedSearch/SearchResult.vue | 21 | ||||
-rw-r--r-- | core/src/services/UnifiedSearchService.js | 3 | ||||
-rw-r--r-- | core/src/views/UnifiedSearch.vue | 94 |
4 files changed, 188 insertions, 7 deletions
diff --git a/core/src/components/UnifiedSearch/SearchFilter.vue b/core/src/components/UnifiedSearch/SearchFilter.vue new file mode 100644 index 00000000000..f3c06a24528 --- /dev/null +++ b/core/src/components/UnifiedSearch/SearchFilter.vue @@ -0,0 +1,77 @@ + <!-- + - @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com> + - + - @author John Molakvoæ <skjnldsv@protonmail.com> + - + - @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> diff --git a/core/src/components/UnifiedSearch/SearchResult.vue b/core/src/components/UnifiedSearch/SearchResult.vue index d5374832da9..025ddef86c0 100644 --- a/core/src/components/UnifiedSearch/SearchResult.vue +++ b/core/src/components/UnifiedSearch/SearchResult.vue @@ -1,3 +1,24 @@ + <!-- + - @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com> + - + - @author John Molakvoæ <skjnldsv@protonmail.com> + - + - @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" diff --git a/core/src/services/UnifiedSearchService.js b/core/src/services/UnifiedSearchService.js index 1507ad5a2bd..152de3755f7 100644 --- a/core/src/services/UnifiedSearchService.js +++ b/core/src/services/UnifiedSearchService.js @@ -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 * diff --git a/core/src/views/UnifiedSearch.vue b/core/src/views/UnifiedSearch.vue index 4569d1c46b6..2d0b59db5dc 100644 --- a/core/src/views/UnifiedSearch.vue +++ b/core/src/views/UnifiedSearch.vue @@ -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"> @@ -97,13 +108,14 @@ </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 { emit } from '@nextcloud/event-bus' import HeaderMenu from '../components/HeaderMenu' +import SearchFilter from '../components/UnifiedSearch/SearchFilter' import SearchResult from '../components/UnifiedSearch/SearchResult' import SearchResultPlaceholder from '../components/UnifiedSearch/SearchResultPlaceholder' @@ -114,6 +126,7 @@ export default { EmptyContent, HeaderMenu, Magnify, + SearchFilter, SearchResult, SearchResultPlaceholder, }, @@ -162,10 +175,10 @@ 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, @@ -174,6 +187,41 @@ export default { }, /** + * 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} */ @@ -291,12 +339,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) { @@ -478,6 +544,13 @@ export default { this.focused = index } }, + + onClickFilter(filter) { + this.query = `${this.query} ${filter}` + .replace(/ {2}/g, ' ') + .trim() + this.onInput() + }, }, } </script> @@ -500,6 +573,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}); @@ -510,10 +591,9 @@ $input-padding: 6px; &[placeholder], &::placeholder { overflow: hidden; - text-overflow:ellipsis; white-space: nowrap; + text-overflow: ellipsis; } - } &__results { |