feat: search in quick actions (#943)

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/943
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
This commit is contained in:
konrad 2021-11-13 20:26:03 +00:00
parent 0a2d5ef820
commit 0fe433891a
3 changed files with 81 additions and 45 deletions

View file

@ -62,7 +62,10 @@ import TeamModel from '@/models/team'
import {CURRENT_LIST, LOADING, LOADING_MODULE, QUICK_ACTIONS_ACTIVE} from '@/store/mutation-types' import {CURRENT_LIST, LOADING, LOADING_MODULE, QUICK_ACTIONS_ACTIVE} from '@/store/mutation-types'
import ListModel from '@/models/list' import ListModel from '@/models/list'
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue' import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
import {getHistory} from '../../modules/listHistory' import {getHistory} from '@/modules/listHistory'
import {parseTaskText, PrefixMode} from '@/modules/parseTaskText'
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
import {PREFIXES} from '@/modules/parseTaskText'
const TYPE_LIST = 'list' const TYPE_LIST = 'list'
const TYPE_TASK = 'task' const TYPE_TASK = 'task'
@ -107,11 +110,6 @@ export default {
results() { results() {
let lists = [] let lists = []
if (this.searchMode === SEARCH_MODE_ALL || this.searchMode === SEARCH_MODE_LISTS) { if (this.searchMode === SEARCH_MODE_ALL || this.searchMode === SEARCH_MODE_LISTS) {
let query = this.query
if (this.searchMode === SEARCH_MODE_LISTS) {
query = query.substr(1)
}
const ncache = {} const ncache = {}
const history = getHistory() const history = getHistory()
@ -122,25 +120,31 @@ export default {
}), }),
...Object.values(this.$store.state.lists)])] ...Object.values(this.$store.state.lists)])]
lists = (allLists.filter(l => { const {list} = this.parsedQuery
if (typeof l === 'undefined' || l === null) {
return false
}
if (l.isArchived) { if (list === null) {
return false lists = []
} } else {
lists = allLists.filter(l => {
if (typeof l === 'undefined' || l === null) {
return false
}
if (typeof ncache[l.namespaceId] === 'undefined') { if (l.isArchived) {
ncache[l.namespaceId] = this.$store.getters['namespaces/getNamespaceById'](l.namespaceId) return false
} }
if (ncache[l.namespaceId].isArchived) { if (typeof ncache[l.namespaceId] === 'undefined') {
return false ncache[l.namespaceId] = this.$store.getters['namespaces/getNamespaceById'](l.namespaceId)
} }
return l.title.toLowerCase().includes(query.toLowerCase()) if (ncache[l.namespaceId].isArchived) {
}) ?? []) return false
}
return l.title.toLowerCase().includes(list.toLowerCase())
}) ?? []
}
} }
const cmds = this.availableCmds const cmds = this.availableCmds
@ -207,7 +211,9 @@ export default {
} }
} }
return this.$t('quickActions.hint') const prefixes = PREFIXES[getQuickAddMagicMode()] ?? PREFIXES[PrefixMode.Default]
return this.$t('quickActions.hint', prefixes)
}, },
currentList() { currentList() {
return Object.keys(this.$store.state[CURRENT_LIST]).length === 0 ? null : this.$store.state[CURRENT_LIST] return Object.keys(this.$store.state[CURRENT_LIST]).length === 0 ? null : this.$store.state[CURRENT_LIST]
@ -236,18 +242,23 @@ export default {
return cmds return cmds
}, },
parsedQuery() {
return parseTaskText(this.query, getQuickAddMagicMode())
},
searchMode() { searchMode() {
if (this.query === '') { if (this.query === '') {
return SEARCH_MODE_ALL return SEARCH_MODE_ALL
} }
if (this.query.startsWith('#')) { const {text, list, labels, assignees} = this.parsedQuery
if (assignees.length === 0 && text !== '') {
return SEARCH_MODE_TASKS return SEARCH_MODE_TASKS
} }
if (this.query.startsWith('*')) { if (assignees.length === 0 && list !== null && text === '' && labels.length === 0) {
return SEARCH_MODE_LISTS return SEARCH_MODE_LISTS
} }
if (this.query.startsWith('@')) { if (assignees.length > 0 && list === null && text === '' && labels.length === 0) {
return SEARCH_MODE_TEAMS return SEARCH_MODE_TEAMS
} }
@ -268,12 +279,7 @@ export default {
return return
} }
let query = this.query if (this.selectedCmd !== null) {
if (this.searchMode === SEARCH_MODE_TASKS) {
query = query.substr(1)
}
if (query === '' || this.selectedCmd !== null) {
return return
} }
@ -282,8 +288,35 @@ export default {
this.taskSearchTimeout = null this.taskSearchTimeout = null
} }
const {text, list, labels} = this.parsedQuery
const params = {
s: text,
filter_by: [],
filter_value: [],
filter_comparator: [],
}
if (list !== null) {
const l = this.$store.getters['lists/findListByExactname'](list)
if (l !== null) {
params.filter_by.push('list_id')
params.filter_value.push(l.id)
params.filter_comparator.push('equals')
}
}
if (labels.length > 0) {
const labelIds = this.$store.getters['labels/getLabelsByExactTitles'](labels).map(l => l.id)
if (labelIds.length > 0) {
params.filter_by.push('labels')
params.filter_value.push(labelIds.join())
params.filter_comparator.push('in')
}
}
this.taskSearchTimeout = setTimeout(async () => { this.taskSearchTimeout = setTimeout(async () => {
const r = await this.taskService.getAll({}, {s: query}) const r = await this.taskService.getAll({}, params)
this.foundTasks = r.map(t => { this.foundTasks = r.map(t => {
t.type = TYPE_TASK t.type = TYPE_TASK
const list = this.$store.getters['lists/getListById'](t.listId) const list = this.$store.getters['lists/getListById'](t.listId)
@ -301,12 +334,7 @@ export default {
return return
} }
let query = this.query if (this.query === '' || this.selectedCmd !== null) {
if (this.searchMode === SEARCH_MODE_TEAMS) {
query = query.substr(1)
}
if (query === '' || this.selectedCmd !== null) {
return return
} }
@ -315,11 +343,14 @@ export default {
this.teamSearchTimeout = null this.teamSearchTimeout = null
} }
const {assignees} = this.parsedQuery
this.teamSearchTimeout = setTimeout(async () => { this.teamSearchTimeout = setTimeout(async () => {
const r = await this.teamService.getAll({}, {s: query}) const teamSearchPromises = assignees.map((t) => this.teamService.getAll({}, {s: t}))
this.foundTeams = r.map(t => { const teamsResult = await Promise.all(teamSearchPromises)
t.title = t.name this.foundTeams = teamsResult.flatMap(team => {
return t team.title = team.name
return team
}) })
}, 150) }, 150)
}, },
@ -348,7 +379,7 @@ export default {
this.doAction(this.results[0].type, this.results[0].items[0]) this.doAction(this.results[0].type, this.results[0].items[0])
return return
} }
if (this.selectedCmd === null) { if (this.selectedCmd === null) {
return return
} }

View file

@ -806,7 +806,7 @@
"quickActions": { "quickActions": {
"commands": "Commands", "commands": "Commands",
"placeholder": "Type a command or search…", "placeholder": "Type a command or search…",
"hint": "You can use # to only search for tasks, * to only search for lists and @ to only search for teams.", "hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
"tasks": "Tasks", "tasks": "Tasks",
"lists": "Lists", "lists": "Lists",
"teams": "Teams", "teams": "Teams",

View file

@ -1,6 +1,6 @@
import LabelService from '@/services/label' import LabelService from '@/services/label'
import {setLoading} from '@/store/helper' import {setLoading} from '@/store/helper'
import { success } from '@/message' import {success} from '@/message'
import {i18n} from '@/i18n' import {i18n} from '@/i18n'
import {getLabelsByIds, filterLabelsByQuery} from '@/helpers/labels' import {getLabelsByIds, filterLabelsByQuery} from '@/helpers/labels'
@ -45,6 +45,11 @@ export default {
filterLabelsByQuery(state) { filterLabelsByQuery(state) {
return (labelsToHide, query) => filterLabelsByQuery(state, labelsToHide, query) return (labelsToHide, query) => filterLabelsByQuery(state, labelsToHide, query)
}, },
getLabelsByExactTitles(state) {
return labelTitles => Object
.values(state.labels)
.filter(({title}) => labelTitles.some(l => l.toLowerCase() === title.toLowerCase()))
},
}, },
actions: { actions: {
async loadAllLabels(ctx, {forceLoad} = {}) { async loadAllLabels(ctx, {forceLoad} = {}) {