Skip to content

Commit 71da116

Browse files
committed
Feat: Create filter-plugin architecture for unified search
This commit introduces the mechanism for apps out of the call, to add search filters to the unified search "Places" filter selector. Signed-off-by: fenn-cs <[email protected]>
1 parent 7f4aaab commit 71da116

File tree

5 files changed

+123
-9
lines changed

5 files changed

+123
-9
lines changed

core/src/services/UnifiedSearchService.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,10 @@ export async function getProviders() {
6565
* @param {string} options.until the search
6666
* @param {string} options.limit the search
6767
* @param {string} options.person the search
68+
* @param options.extraQueries
6869
* @return {object} {request: Promise, cancel: Promise}
6970
*/
70-
export function search({ type, query, cursor, since, until, limit, person }) {
71+
export function search({ type, query, cursor, since, until, limit, person, extraQueries = {} }) {
7172
/**
7273
* Generate an axios cancel token
7374
*/
@@ -84,6 +85,7 @@ export function search({ type, query, cursor, since, until, limit, person }) {
8485
person,
8586
// Sending which location we're currently at
8687
from: window.location.pathname.replace('/index.php', '') + window.location.search,
88+
...extraQueries,
8789
},
8890
})
8991

core/src/store/index.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Vue from 'vue';
2+
import Vuex from 'vuex';
3+
import search from './unified-search-external-filters';
4+
5+
Vue.use(Vuex);
6+
7+
export default new Vuex.Store({
8+
modules: {
9+
search,
10+
},
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* @copyright Copyright (c) 2021 Fon E. Noel NFEBE <[email protected]>
3+
*
4+
* @author Fon E. Noel NFEBE <[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+
const state = {
23+
externalFilters: [],
24+
}
25+
26+
const mutations = {
27+
registerExternalFilter(state, { id, label, callback, icon }) {
28+
state.externalFilters.push({ id, name: label, callback, icon, isPluginFilter: true })
29+
},
30+
}
31+
32+
const actions = {
33+
registerExternalFilter({ commit }, { id, label, callback, icon }) {
34+
commit('registerExternalFilter', { id, label, callback, icon })
35+
},
36+
}
37+
38+
export default {
39+
state,
40+
mutations,
41+
actions,
42+
}

core/src/unified-search.js

+16
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { translate as t, translatePlural as n } from '@nextcloud/l10n'
2626
import Vue from 'vue'
2727

2828
import UnifiedSearch from './views/UnifiedSearch.vue'
29+
import store from '../src/store/index.js'
2930

3031
// eslint-disable-next-line camelcase
3132
__webpack_nonce__ = btoa(getRequestToken())
@@ -47,9 +48,24 @@ Vue.mixin({
4748
},
4849
})
4950

51+
// Register the add/register filter action API globally
52+
window.OCA.Core = window.OCA.Core || {}
53+
window.OCA.Core.UnifiedSearch = {
54+
registerFilterAction: ({ id, name, label, callback, icon }) => {
55+
store.dispatch('registerExternalFilter', {
56+
id,
57+
name,
58+
label,
59+
icon,
60+
callback,
61+
})
62+
},
63+
}
64+
5065
export default new Vue({
5166
el: '#unified-search',
5267
// eslint-disable-next-line vue/match-component-file-name
5368
name: 'UnifiedSearchRoot',
69+
store,
5470
render: h => h(UnifiedSearch),
5571
})

core/src/views/UnifiedSearchModal.vue

+51-8
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
:label="t('core', 'Search apps, files, tags, messages') + '...'"
1919
@update:value="debouncedFind" />
2020
<div class="unified-search-modal__filters">
21-
<NcActions :menu-name="t('core', 'Apps and Settings')" :open.sync="providerActionMenuIsOpen">
21+
<NcActions :menu-name="t('core', 'Places')" :open.sync="providerActionMenuIsOpen">
2222
<template #icon>
2323
<ListBox :size="20" />
2424
</template>
25+
<!-- Provider id's may be duplicated since, plugin filters could depend on a provider that is already in the defaults.
26+
provider.id concatenated to provider.name is used to create the item id, if same then, there should be an issue. -->
2527
<NcActionButton v-for="provider in providers"
26-
:key="provider.id"
28+
:key="`${provider.id}-${provider.name.replace(/\s/g, '')}`"
2729
@click="addProviderFilter(provider)">
2830
<template #icon>
2931
<img :src="provider.icon" class="filter-button__icon" alt="">
@@ -150,8 +152,9 @@ import SearchableList from '../components/UnifiedSearch/SearchableList.vue'
150152
import SearchResult from '../components/UnifiedSearch/SearchResult.vue'
151153
152154
import debounce from 'debounce'
153-
import { emit } from '@nextcloud/event-bus'
155+
import { emit, subscribe } from '@nextcloud/event-bus'
154156
import { useBrowserLocation } from '@vueuse/core'
157+
import { mapState } from 'vuex'
155158
import { getProviders, search as unifiedSearch, getContacts } from '../services/UnifiedSearchService.js'
156159
157160
export default {
@@ -193,6 +196,7 @@ export default {
193196
},
194197
data() {
195198
return {
199+
searchScope: 'everywhere',
196200
providers: [],
197201
providerActionMenuIsOpen: false,
198202
dateActionMenuIsOpen: false,
@@ -217,6 +221,9 @@ export default {
217221
},
218222
219223
computed: {
224+
...mapState({
225+
externalFilters: state => state.search.externalFilters,
226+
}),
220227
userContacts() {
221228
return this.contacts
222229
},
@@ -250,8 +257,12 @@ export default {
250257
251258
},
252259
mounted() {
260+
subscribe('nextcloud:unified-search:conversation', this.handlePluginFilter)
253261
getProviders().then((providers) => {
254262
this.providers = providers
263+
this.externalFilters.forEach(filter => {
264+
this.providers.push(filter)
265+
})
255266
console.debug('Search providers', this.providers)
256267
})
257268
getContacts({ searchTerm: '' }).then((contacts) => {
@@ -267,7 +278,6 @@ export default {
267278
this.searching = false
268279
return
269280
}
270-
// Event should probably be refactored at some point to used nextcloud:unified-search.search
271281
emit('nextcloud:unified-search.search', { query })
272282
const newResults = []
273283
const providersToSearch = this.filteredProviders.length > 0 ? this.filteredProviders : this.providers
@@ -276,6 +286,7 @@ export default {
276286
type: provider.id,
277287
query,
278288
cursor: null,
289+
extraQueries: provider.extraParams,
279290
}
280291
281292
if (filters.dateFilterIsApplied) {
@@ -404,12 +415,27 @@ export default {
404415
},
405416
addProviderFilter(providerFilter, loadMoreResultsForProvider = false) {
406417
if (!providerFilter.id) return
418+
if (providerFilter.isPluginFilter) {
419+
providerFilter.callback()
420+
}
407421
this.providerResultLimit = loadMoreResultsForProvider ? this.providerResultLimit : 5
408422
this.providerActionMenuIsOpen = false
409-
const existingFilter = this.filteredProviders.find(existing => existing.id === providerFilter.id)
410-
if (!existingFilter) {
411-
this.filteredProviders.push({ id: providerFilter.id, name: providerFilter.name, icon: providerFilter.icon, type: 'provider', filters: providerFilter.filters })
423+
// With the possibility for other apps to add new filters
424+
// Resulting in a possible id/provider collision
425+
// If a user tries to apply a filter that seems to already exist, we remove the current one and add the new one.
426+
const existingFilterIndex = this.filteredProviders.findIndex(existing => existing.id === providerFilter.id)
427+
if (existingFilterIndex > -1) {
428+
this.filteredProviders.splice(existingFilterIndex, 1)
429+
this.filters = this.syncProviderFilters(this.filters, this.filteredProviders)
412430
}
431+
this.filteredProviders.push({
432+
id: providerFilter.id,
433+
name: providerFilter.name,
434+
icon: providerFilter.icon,
435+
type: providerFilter.type || 'provider',
436+
filters: providerFilter.filters,
437+
isPluginFilter: providerFilter.isPluginFilter || false,
438+
})
413439
this.filters = this.syncProviderFilters(this.filters, this.filteredProviders)
414440
console.debug('Search filters (newly added)', this.filters)
415441
this.debouncedFind(this.searchQuery)
@@ -527,6 +553,23 @@ export default {
527553
this.dateFilter.text = t('core', `Between ${this.dateFilter.startFrom.toLocaleDateString()} and ${this.dateFilter.endAt.toLocaleDateString()}`)
528554
this.updateDateFilter()
529555
},
556+
handlePluginFilter(addFilterEvent) {
557+
for (const provider of this.filteredProviders) {
558+
if (provider.id === addFilterEvent.id) {
559+
provider.name = addFilterEvent.filterUpdateText
560+
// Filters attached may only make sense with certain providers,
561+
// So, find the provider attached, add apply the exta parameters to those providers only
562+
const compatibleProviderIndex = this.providers.findIndex(provider => provider.id === addFilterEvent.provider)
563+
if (compatibleProviderIndex > -1) {
564+
provider.id = addFilterEvent.id
565+
provider.extraParams = addFilterEvent.filterParams
566+
}
567+
break
568+
}
569+
}
570+
console.debug('Search scope set to conversation', addFilterEvent)
571+
this.debouncedFind(this.searchQuery)
572+
},
530573
focusInput() {
531574
this.$refs.searchInput.$el.children[0].children[0].focus()
532575
},
@@ -549,7 +592,7 @@ export default {
549592
padding-block: 10px 0;
550593
551594
// inline padding on direct children to make sure the scrollbar is on the modal container
552-
> * {
595+
>* {
553596
padding-inline: 20px;
554597
}
555598

0 commit comments

Comments
 (0)