--- /dev/null
+ <!--
+ - @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>
+ <!--
+ - @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"
@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">
</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'
EmptyContent,
HeaderMenu,
Magnify,
+ SearchFilter,
SearchResult,
SearchResultPlaceholder,
},
/**
* 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,
}))
},
+ /**
+ * 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}
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) {
this.focused = index
}
},
+
+ onClickFilter(filter) {
+ this.query = `${this.query} ${filter}`
+ .replace(/ {2}/g, ' ')
+ .trim()
+ this.onInput()
+ },
},
}
</script>
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});
&[placeholder],
&::placeholder {
overflow: hidden;
- text-overflow:ellipsis;
white-space: nowrap;
+ text-overflow: ellipsis;
}
-
}
&__results {