Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/core/src
diff options
context:
space:
mode:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2020-08-05 13:56:11 +0300
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2020-09-03 10:03:09 +0300
commitf359529555fbf6fa344869d01f0b3c17ecce46c8 (patch)
treedbd2458d47cd6c585297930049fe7a8968390e12 /core/src
parent34aca4632574d153c716589a08497c0957b3679f (diff)
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.vue77
-rw-r--r--core/src/components/UnifiedSearch/SearchResult.vue21
-rw-r--r--core/src/services/UnifiedSearchService.js3
-rw-r--r--core/src/views/UnifiedSearch.vue94
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 {