feat: quick-actions script setup (#2478)
Co-authored-by: Dominik Pschenitschni <mail@celement.de> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/2478 Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de> Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
This commit is contained in:
parent
9807858436
commit
386fd79b49
@ -13,7 +13,7 @@
|
|||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@keyup="search"
|
@keyup="search"
|
||||||
ref="searchInput"
|
ref="searchInput"
|
||||||
@keydown.down.prevent="() => select(0, 0)"
|
@keydown.down.prevent="select(0, 0)"
|
||||||
@keyup.prevent.delete="unselectCmd"
|
@keyup.prevent.delete="unselectCmd"
|
||||||
@keyup.prevent.enter="doCmd"
|
@keyup.prevent.enter="doCmd"
|
||||||
@keyup.prevent.esc="closeQuickActions"
|
@keyup.prevent.esc="closeQuickActions"
|
||||||
@ -35,13 +35,14 @@
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
v-for="(i, key) in r.items"
|
v-for="(i, key) in r.items"
|
||||||
:key="key"
|
:key="key"
|
||||||
:ref="`result-${k}_${key}`"
|
class="result-item-button"
|
||||||
@keydown.up.prevent="() => select(k, key - 1)"
|
:class="{'is-strikethrough': (i as DoAction<ITask>)?.done}"
|
||||||
@keydown.down.prevent="() => select(k, key + 1)"
|
:ref="(el: Element | ComponentPublicInstance | null) => setResultRefs(el, k, key)"
|
||||||
@click.prevent.stop="() => doAction(r.type, i)"
|
@keydown.up.prevent="select(k, key - 1)"
|
||||||
@keyup.prevent.enter="() => doAction(r.type, i)"
|
@keydown.down.prevent="select(k, key + 1)"
|
||||||
@keyup.prevent.esc="() => $refs.searchInput.focus()"
|
@click.prevent.stop="doAction(r.type, i)"
|
||||||
:class="{'is-strikethrough': i.done}"
|
@keyup.prevent.enter="doAction(r.type, i)"
|
||||||
|
@keyup.prevent.esc="searchInput?.focus()"
|
||||||
>
|
>
|
||||||
{{ i.title }}
|
{{ i.title }}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
@ -52,23 +53,20 @@
|
|||||||
</modal>
|
</modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import {defineComponent} from 'vue'
|
import {ref, computed, watchEffect, shallowReactive, type ComponentPublicInstance} from 'vue'
|
||||||
|
import {useI18n} from 'vue-i18n'
|
||||||
|
import {useRouter} from 'vue-router'
|
||||||
|
|
||||||
import TaskService from '@/services/task'
|
import TaskService from '@/services/task'
|
||||||
import TeamService from '@/services/team'
|
import TeamService from '@/services/team'
|
||||||
|
|
||||||
import NamespaceModel from '@/models/namespace'
|
import NamespaceModel from '@/models/namespace'
|
||||||
import TeamModel from '@/models/team'
|
import TeamModel from '@/models/team'
|
||||||
|
|
||||||
import ListModel from '@/models/list'
|
import ListModel from '@/models/list'
|
||||||
|
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
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 {parseTaskText, PrefixMode} from '@/modules/parseTaskText'
|
|
||||||
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
|
|
||||||
import {PREFIXES} from '@/modules/parseTaskText'
|
|
||||||
|
|
||||||
import {useBaseStore} from '@/stores/base'
|
import {useBaseStore} from '@/stores/base'
|
||||||
import {useListStore} from '@/stores/lists'
|
import {useListStore} from '@/stores/lists'
|
||||||
@ -76,429 +74,504 @@ import {useNamespaceStore} from '@/stores/namespaces'
|
|||||||
import {useLabelStore} from '@/stores/labels'
|
import {useLabelStore} from '@/stores/labels'
|
||||||
import {useTaskStore} from '@/stores/tasks'
|
import {useTaskStore} from '@/stores/tasks'
|
||||||
|
|
||||||
const TYPE_LIST = 'list'
|
import {getHistory} from '@/modules/listHistory'
|
||||||
const TYPE_TASK = 'task'
|
import {parseTaskText, PrefixMode, PREFIXES} from '@/modules/parseTaskText'
|
||||||
const TYPE_CMD = 'cmd'
|
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
|
||||||
const TYPE_TEAM = 'team'
|
import {success} from '@/message'
|
||||||
|
|
||||||
const CMD_NEW_TASK = 'newTask'
|
import type {ITeam} from '@/modelTypes/ITeam'
|
||||||
const CMD_NEW_LIST = 'newList'
|
import type {ITask} from '@/modelTypes/ITask'
|
||||||
const CMD_NEW_NAMESPACE = 'newNamespace'
|
import type {INamespace} from '@/modelTypes/INamespace'
|
||||||
const CMD_NEW_TEAM = 'newTeam'
|
import type {IList} from '@/modelTypes/IList'
|
||||||
|
|
||||||
const SEARCH_MODE_ALL = 'all'
|
const {t} = useI18n({useScope: 'global'})
|
||||||
const SEARCH_MODE_TASKS = 'tasks'
|
const router = useRouter()
|
||||||
const SEARCH_MODE_LISTS = 'lists'
|
|
||||||
const SEARCH_MODE_TEAMS = 'teams'
|
|
||||||
|
|
||||||
export default defineComponent({
|
const baseStore = useBaseStore()
|
||||||
name: 'quick-actions',
|
const listStore = useListStore()
|
||||||
components: {
|
const namespaceStore = useNamespaceStore()
|
||||||
BaseButton,
|
const labelStore = useLabelStore()
|
||||||
QuickAddMagic,
|
const taskStore = useTaskStore()
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
query: '',
|
|
||||||
selectedCmd: null,
|
|
||||||
|
|
||||||
foundTasks: [],
|
type DoAction<Type = {}> = { type: ACTION_TYPE } & Type
|
||||||
taskSearchTimeout: null,
|
|
||||||
taskService: new TaskService(),
|
|
||||||
|
|
||||||
foundTeams: [],
|
enum ACTION_TYPE {
|
||||||
teamSearchTimeout: null,
|
CMD = 'cmd',
|
||||||
teamService: new TeamService(),
|
TASK = 'task',
|
||||||
|
LIST = 'list',
|
||||||
|
TEAM = 'team',
|
||||||
|
}
|
||||||
|
|
||||||
|
enum COMMAND_TYPE {
|
||||||
|
NEW_TASK = 'newTask',
|
||||||
|
NEW_LIST = 'newList',
|
||||||
|
NEW_NAMESPACE = 'newNamespace',
|
||||||
|
NEW_TEAM = 'newTeam',
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SEARCH_MODE {
|
||||||
|
ALL = 'all',
|
||||||
|
TASKS = 'tasks',
|
||||||
|
LISTS = 'lists',
|
||||||
|
TEAMS = 'teams',
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = ref('')
|
||||||
|
const selectedCmd = ref<Command | null>(null)
|
||||||
|
|
||||||
|
const foundTasks = ref<DoAction<ITask>[]>([])
|
||||||
|
const taskService = shallowReactive(new TaskService())
|
||||||
|
|
||||||
|
const foundTeams = ref<ITeam[]>([])
|
||||||
|
const teamService = shallowReactive(new TeamService())
|
||||||
|
|
||||||
|
const active = computed(() => baseStore.quickActionsActive)
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (!active.value) {
|
||||||
|
reset()
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
computed: {
|
|
||||||
active() {
|
function closeQuickActions() {
|
||||||
const active = useBaseStore().quickActionsActive
|
baseStore.setQuickActionsActive(false)
|
||||||
if (!active) {
|
}
|
||||||
// FIXME: computeds shouldn't have side effects.
|
|
||||||
// create a watcher instead
|
const foundLists = computed(() => {
|
||||||
this.reset()
|
const { list } = parsedQuery.value
|
||||||
|
if (
|
||||||
|
searchMode.value === SEARCH_MODE.ALL ||
|
||||||
|
searchMode.value === SEARCH_MODE.LISTS ||
|
||||||
|
list === null
|
||||||
|
) {
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
return active
|
|
||||||
},
|
|
||||||
results() {
|
|
||||||
let lists = []
|
|
||||||
const listStore = useListStore()
|
|
||||||
|
|
||||||
if (this.searchMode === SEARCH_MODE_ALL || this.searchMode === SEARCH_MODE_LISTS) {
|
const ncache: { [id: ListModel['id']]: INamespace } = {}
|
||||||
const {list} = this.parsedQuery
|
|
||||||
|
|
||||||
if (list === null) {
|
|
||||||
lists = []
|
|
||||||
} else {
|
|
||||||
const ncache = {}
|
|
||||||
const history = getHistory()
|
const history = getHistory()
|
||||||
// Puts recently visited lists at the top
|
const allLists = [
|
||||||
const allLists = [...new Set([
|
...new Set([
|
||||||
...history.map(l => listStore.getListById(l.id)),
|
...history.map((l) => listStore.getListById(l.id)),
|
||||||
...listStore.searchList(list),
|
...listStore.searchList(list),
|
||||||
])]
|
]),
|
||||||
|
]
|
||||||
|
|
||||||
lists = allLists.filter(l => {
|
return allLists.filter((l) => {
|
||||||
if (typeof l === 'undefined' || l === null) {
|
if (typeof l === 'undefined' || l === null) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof ncache[l.namespaceId] === 'undefined') {
|
if (typeof ncache[l.namespaceId] === 'undefined') {
|
||||||
ncache[l.namespaceId] = useNamespaceStore().getNamespaceById(l.namespaceId)
|
ncache[l.namespaceId] = namespaceStore.getNamespaceById(l.namespaceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return !ncache[l.namespaceId].isArchived
|
return !ncache[l.namespaceId].isArchived
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const cmds = this.availableCmds
|
// FIXME: use fuzzysearch
|
||||||
.filter(a => a.title.toLowerCase().includes(this.query.toLowerCase()))
|
const foundCommands = computed(() => availableCmds.value.filter((a) =>
|
||||||
|
a.title.toLowerCase().includes(query.value.toLowerCase()),
|
||||||
|
))
|
||||||
|
|
||||||
|
interface Result {
|
||||||
|
type: ACTION_TYPE
|
||||||
|
title: string
|
||||||
|
items: DoAction<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = computed<Result[]>(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: TYPE_CMD,
|
type: ACTION_TYPE.CMD,
|
||||||
title: this.$t('quickActions.commands'),
|
title: t('quickActions.commands'),
|
||||||
items: cmds,
|
items: foundCommands.value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: TYPE_TASK,
|
type: ACTION_TYPE.TASK,
|
||||||
title: this.$t('quickActions.tasks'),
|
title: t('quickActions.tasks'),
|
||||||
items: this.foundTasks,
|
items: foundTasks.value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: TYPE_LIST,
|
type: ACTION_TYPE.LIST,
|
||||||
title: this.$t('quickActions.lists'),
|
title: t('quickActions.lists'),
|
||||||
items: lists,
|
items: foundLists.value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: TYPE_TEAM,
|
type: ACTION_TYPE.TEAM,
|
||||||
title: this.$t('quickActions.teams'),
|
title: t('quickActions.teams'),
|
||||||
items: this.foundTeams,
|
items: foundTeams.value,
|
||||||
},
|
},
|
||||||
].filter(i => i.items.length > 0)
|
].filter((i) => i.items.length > 0)
|
||||||
},
|
})
|
||||||
nothing() {
|
|
||||||
return this.search === '' || Object.keys(this.results).length === 0
|
|
||||||
},
|
|
||||||
loading() {
|
|
||||||
return this.taskService.loading ||
|
|
||||||
useNamespaceStore().isLoading || useListStore().isLoading ||
|
|
||||||
this.teamService.loading
|
|
||||||
},
|
|
||||||
placeholder() {
|
|
||||||
if (this.selectedCmd !== null) {
|
|
||||||
switch (this.selectedCmd.action) {
|
|
||||||
case CMD_NEW_TASK:
|
|
||||||
return this.$t('quickActions.newTask')
|
|
||||||
case CMD_NEW_LIST:
|
|
||||||
return this.$t('quickActions.newList')
|
|
||||||
case CMD_NEW_NAMESPACE:
|
|
||||||
return this.$t('quickActions.newNamespace')
|
|
||||||
case CMD_NEW_TEAM:
|
|
||||||
return this.$t('quickActions.newTeam')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.$t('quickActions.placeholder')
|
const loading = computed(() =>
|
||||||
|
taskService.loading ||
|
||||||
|
namespaceStore.isLoading ||
|
||||||
|
listStore.isLoading ||
|
||||||
|
teamService.loading,
|
||||||
|
)
|
||||||
|
|
||||||
|
interface Command {
|
||||||
|
type: COMMAND_TYPE
|
||||||
|
title: string
|
||||||
|
placeholder: string
|
||||||
|
action: () => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
const commands = computed<{ [key in COMMAND_TYPE]: Command }>(() => ({
|
||||||
|
newTask: {
|
||||||
|
type: COMMAND_TYPE.NEW_TASK,
|
||||||
|
title: t('quickActions.cmds.newTask'),
|
||||||
|
placeholder: t('quickActions.newTask'),
|
||||||
|
action: newTask,
|
||||||
},
|
},
|
||||||
hintText() {
|
newList: {
|
||||||
|
type: COMMAND_TYPE.NEW_LIST,
|
||||||
|
title: t('quickActions.cmds.newList'),
|
||||||
|
placeholder: t('quickActions.newList'),
|
||||||
|
action: newList,
|
||||||
|
},
|
||||||
|
newNamespace: {
|
||||||
|
type: COMMAND_TYPE.NEW_NAMESPACE,
|
||||||
|
title: t('quickActions.cmds.newNamespace'),
|
||||||
|
placeholder: t('quickActions.newNamespace'),
|
||||||
|
action: newNamespace,
|
||||||
|
},
|
||||||
|
newTeam: {
|
||||||
|
type: COMMAND_TYPE.NEW_TEAM,
|
||||||
|
title: t('quickActions.cmds.newTeam'),
|
||||||
|
placeholder: t('quickActions.newTeam'),
|
||||||
|
action: newTeam,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const placeholder = computed(() => selectedCmd.value?.placeholder || t('quickActions.placeholder'))
|
||||||
|
|
||||||
|
const currentList = computed(() => Object.keys(baseStore.currentList).length === 0
|
||||||
|
? null
|
||||||
|
: baseStore.currentList,
|
||||||
|
)
|
||||||
|
|
||||||
|
const hintText = computed(() => {
|
||||||
let namespace
|
let namespace
|
||||||
|
if (selectedCmd.value !== null && currentList.value !== null) {
|
||||||
if (this.selectedCmd !== null && this.currentList !== null) {
|
switch (selectedCmd.value.type) {
|
||||||
switch (this.selectedCmd.action) {
|
case COMMAND_TYPE.NEW_TASK:
|
||||||
case CMD_NEW_TASK:
|
return t('quickActions.createTask', {
|
||||||
return this.$t('quickActions.createTask', {title: this.currentList.title})
|
title: currentList.value.title,
|
||||||
case CMD_NEW_LIST:
|
})
|
||||||
namespace = useNamespaceStore().getNamespaceById(this.currentList.namespaceId)
|
case COMMAND_TYPE.NEW_LIST:
|
||||||
return this.$t('quickActions.createList', {title: namespace.title})
|
namespace = namespaceStore.getNamespaceById(
|
||||||
|
currentList.value.namespaceId,
|
||||||
|
)
|
||||||
|
return t('quickActions.createList', {
|
||||||
|
title: namespace?.title,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const prefixes =
|
||||||
|
PREFIXES[getQuickAddMagicMode()] ?? PREFIXES[PrefixMode.Default]
|
||||||
|
return t('quickActions.hint', prefixes)
|
||||||
|
})
|
||||||
|
|
||||||
const prefixes = PREFIXES[getQuickAddMagicMode()] ?? PREFIXES[PrefixMode.Default]
|
const availableCmds = computed(() => {
|
||||||
|
|
||||||
return this.$t('quickActions.hint', prefixes)
|
|
||||||
},
|
|
||||||
currentList() {
|
|
||||||
const currentList = useBaseStore().currentList
|
|
||||||
return Object.keys(currentList).length === 0 ? null : currentList
|
|
||||||
},
|
|
||||||
availableCmds() {
|
|
||||||
const cmds = []
|
const cmds = []
|
||||||
|
if (currentList.value !== null) {
|
||||||
if (this.currentList !== null) {
|
cmds.push(commands.value.newTask, commands.value.newList)
|
||||||
cmds.push({
|
|
||||||
title: this.$t('quickActions.cmds.newTask'),
|
|
||||||
action: CMD_NEW_TASK,
|
|
||||||
})
|
|
||||||
cmds.push({
|
|
||||||
title: this.$t('quickActions.cmds.newList'),
|
|
||||||
action: CMD_NEW_LIST,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
cmds.push({
|
cmds.push(commands.value.newNamespace, commands.value.newTeam)
|
||||||
title: this.$t('quickActions.cmds.newNamespace'),
|
|
||||||
action: CMD_NEW_NAMESPACE,
|
|
||||||
})
|
|
||||||
cmds.push({
|
|
||||||
title: this.$t('quickActions.cmds.newTeam'),
|
|
||||||
action: CMD_NEW_TEAM,
|
|
||||||
})
|
|
||||||
|
|
||||||
return cmds
|
return cmds
|
||||||
},
|
})
|
||||||
parsedQuery() {
|
|
||||||
return parseTaskText(this.query, getQuickAddMagicMode())
|
const parsedQuery = computed(() => parseTaskText(query.value, getQuickAddMagicMode()))
|
||||||
},
|
|
||||||
searchMode() {
|
const searchMode = computed(() => {
|
||||||
if (this.query === '') {
|
if (query.value === '') {
|
||||||
return SEARCH_MODE_ALL
|
return SEARCH_MODE.ALL
|
||||||
}
|
}
|
||||||
|
const { text, list, labels, assignees } = parsedQuery.value
|
||||||
const {text, list, labels, assignees} = this.parsedQuery
|
|
||||||
|
|
||||||
if (assignees.length === 0 && text !== '') {
|
if (assignees.length === 0 && text !== '') {
|
||||||
return SEARCH_MODE_TASKS
|
return SEARCH_MODE.TASKS
|
||||||
}
|
}
|
||||||
if (assignees.length === 0 && list !== null && text === '' && labels.length === 0) {
|
if (
|
||||||
return SEARCH_MODE_LISTS
|
assignees.length === 0 &&
|
||||||
|
list !== null &&
|
||||||
|
text === '' &&
|
||||||
|
labels.length === 0
|
||||||
|
) {
|
||||||
|
return SEARCH_MODE.LISTS
|
||||||
}
|
}
|
||||||
if (assignees.length > 0 && list === null && text === '' && labels.length === 0) {
|
if (
|
||||||
return SEARCH_MODE_TEAMS
|
assignees.length > 0 &&
|
||||||
|
list === null &&
|
||||||
|
text === '' &&
|
||||||
|
labels.length === 0
|
||||||
|
) {
|
||||||
|
return SEARCH_MODE.TEAMS
|
||||||
}
|
}
|
||||||
|
return SEARCH_MODE.ALL
|
||||||
|
})
|
||||||
|
|
||||||
return SEARCH_MODE_ALL
|
const isNewTaskCommand = computed(() => (
|
||||||
},
|
selectedCmd.value !== null &&
|
||||||
isNewTaskCommand() {
|
selectedCmd.value.type === COMMAND_TYPE.NEW_TASK
|
||||||
return this.selectedCmd !== null && this.selectedCmd.action === CMD_NEW_TASK
|
))
|
||||||
},
|
|
||||||
},
|
const taskSearchTimeout = ref<ReturnType<typeof setTimeout> | null>(null)
|
||||||
methods: {
|
|
||||||
search() {
|
type Filter = {by: string, value: string | number, comparator: string}
|
||||||
this.searchTasks()
|
|
||||||
this.searchTeams()
|
function filtersToParams(filters: Filter[]) {
|
||||||
},
|
const filter_by : Filter['by'][] = []
|
||||||
searchTasks() {
|
const filter_value : Filter['value'][] = []
|
||||||
if (this.searchMode !== SEARCH_MODE_ALL && this.searchMode !== SEARCH_MODE_TASKS) {
|
const filter_comparator : Filter['comparator'][] = []
|
||||||
this.foundTasks = []
|
|
||||||
|
filters.forEach(({by, value, comparator}) => {
|
||||||
|
filter_by.push(by)
|
||||||
|
filter_value.push(value)
|
||||||
|
filter_comparator.push(comparator)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
filter_by,
|
||||||
|
filter_value,
|
||||||
|
filter_comparator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchTasks() {
|
||||||
|
if (
|
||||||
|
searchMode.value !== SEARCH_MODE.ALL &&
|
||||||
|
searchMode.value !== SEARCH_MODE.TASKS
|
||||||
|
) {
|
||||||
|
foundTasks.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selectedCmd !== null) {
|
if (selectedCmd.value !== null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.taskSearchTimeout !== null) {
|
if (taskSearchTimeout.value !== null) {
|
||||||
clearTimeout(this.taskSearchTimeout)
|
clearTimeout(taskSearchTimeout.value)
|
||||||
this.taskSearchTimeout = null
|
taskSearchTimeout.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const {text, list, labels} = this.parsedQuery
|
const { text, list: listName, labels } = parsedQuery.value
|
||||||
|
|
||||||
const params = {
|
const filters: Filter[] = []
|
||||||
s: text,
|
|
||||||
filter_by: [],
|
// FIXME: improve types
|
||||||
filter_value: [],
|
function addFilter(
|
||||||
filter_comparator: [],
|
by: Filter['by'],
|
||||||
|
value: Filter['value'],
|
||||||
|
comparator: Filter['comparator'],
|
||||||
|
) {
|
||||||
|
filters.push({
|
||||||
|
by,
|
||||||
|
value,
|
||||||
|
comparator,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const listStore = useListStore()
|
if (listName !== null) {
|
||||||
|
const list = listStore.findListByExactname(listName)
|
||||||
if (list !== null) {
|
if (list !== null) {
|
||||||
const l = listStore.findListByExactname(list)
|
addFilter('listId', list.id, 'equals')
|
||||||
if (l !== null) {
|
|
||||||
params.filter_by.push('list_id')
|
|
||||||
params.filter_value.push(l.id)
|
|
||||||
params.filter_comparator.push('equals')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (labels.length > 0) {
|
if (labels.length > 0) {
|
||||||
const labelIds = useLabelStore().getLabelsByExactTitles(labels).map(l => l.id)
|
const labelIds = labelStore.getLabelsByExactTitles(labels).map((l) => l.id)
|
||||||
if (labelIds.length > 0) {
|
if (labelIds.length > 0) {
|
||||||
params.filter_by.push('labels')
|
addFilter('labels', labelIds.join(), 'in')
|
||||||
params.filter_value.push(labelIds.join())
|
|
||||||
params.filter_comparator.push('in')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.taskSearchTimeout = setTimeout(async () => {
|
const params = {
|
||||||
const r = await this.taskService.getAll({}, params)
|
s: text,
|
||||||
this.foundTasks = r.map(t => {
|
...filtersToParams(filters),
|
||||||
t.type = TYPE_TASK
|
}
|
||||||
|
|
||||||
|
taskSearchTimeout.value = setTimeout(async () => {
|
||||||
|
const r = await taskService.getAll({}, params) as DoAction<ITask>[]
|
||||||
|
foundTasks.value = r.map((t) => {
|
||||||
|
t.type = ACTION_TYPE.TASK
|
||||||
const list = listStore.getListById(t.listId)
|
const list = listStore.getListById(t.listId)
|
||||||
if (list !== null) {
|
if (list !== null) {
|
||||||
t.title = `${t.title} (${list.title})`
|
t.title = `${t.title} (${list.title})`
|
||||||
}
|
}
|
||||||
|
|
||||||
return t
|
return t
|
||||||
})
|
})
|
||||||
}, 150)
|
}, 150)
|
||||||
},
|
}
|
||||||
searchTeams() {
|
|
||||||
if (this.searchMode !== SEARCH_MODE_ALL && this.searchMode !== SEARCH_MODE_TEAMS) {
|
const teamSearchTimeout = ref<ReturnType<typeof setTimeout> | null>(null)
|
||||||
this.foundTeams = []
|
|
||||||
|
function searchTeams() {
|
||||||
|
if (
|
||||||
|
searchMode.value !== SEARCH_MODE.ALL &&
|
||||||
|
searchMode.value !== SEARCH_MODE.TEAMS
|
||||||
|
) {
|
||||||
|
foundTeams.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (query.value === '' || selectedCmd.value !== null) {
|
||||||
if (this.query === '' || this.selectedCmd !== null) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (teamSearchTimeout.value !== null) {
|
||||||
if (this.teamSearchTimeout !== null) {
|
clearTimeout(teamSearchTimeout.value)
|
||||||
clearTimeout(this.teamSearchTimeout)
|
teamSearchTimeout.value = null
|
||||||
this.teamSearchTimeout = null
|
|
||||||
}
|
}
|
||||||
|
const { assignees } = parsedQuery.value
|
||||||
const {assignees} = this.parsedQuery
|
teamSearchTimeout.value = setTimeout(async () => {
|
||||||
|
const teamSearchPromises = assignees.map((t) =>
|
||||||
this.teamSearchTimeout = setTimeout(async () => {
|
teamService.getAll({}, { s: t }),
|
||||||
const teamSearchPromises = assignees.map((t) => this.teamService.getAll({}, {s: t}))
|
)
|
||||||
const teamsResult = await Promise.all(teamSearchPromises)
|
const teamsResult = await Promise.all(teamSearchPromises)
|
||||||
this.foundTeams = teamsResult.flatMap(team => {
|
foundTeams.value = teamsResult.flatMap((team) => {
|
||||||
team.title = team.name
|
team.title = team.name
|
||||||
return team
|
return team
|
||||||
})
|
})
|
||||||
}, 150)
|
}, 150)
|
||||||
},
|
}
|
||||||
closeQuickActions() {
|
|
||||||
useBaseStore().setQuickActionsActive(false)
|
function search() {
|
||||||
},
|
searchTasks()
|
||||||
doAction(type, item) {
|
searchTeams()
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchInput = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
async function doAction(type: ACTION_TYPE, item: DoAction) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TYPE_LIST:
|
case ACTION_TYPE.LIST:
|
||||||
this.$router.push({name: 'list.index', params: {listId: item.id}})
|
closeQuickActions()
|
||||||
this.closeQuickActions()
|
await router.push({
|
||||||
|
name: 'list.index',
|
||||||
|
params: { listId: (item as DoAction<IList>).id },
|
||||||
|
})
|
||||||
break
|
break
|
||||||
case TYPE_TASK:
|
case ACTION_TYPE.TASK:
|
||||||
this.$router.push({name: 'task.detail', params: {id: item.id}})
|
closeQuickActions()
|
||||||
this.closeQuickActions()
|
await router.push({
|
||||||
|
name: 'task.detail',
|
||||||
|
params: { id: (item as DoAction<ITask>).id },
|
||||||
|
})
|
||||||
break
|
break
|
||||||
case TYPE_CMD:
|
case ACTION_TYPE.CMD:
|
||||||
this.query = ''
|
query.value = ''
|
||||||
this.selectedCmd = item
|
selectedCmd.value = item as DoAction<Command>
|
||||||
this.$refs.searchInput.focus()
|
searchInput.value?.focus()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
doCmd() {
|
|
||||||
if (this.results.length === 1 && this.results[0].items.length === 1) {
|
async function doCmd() {
|
||||||
this.doAction(this.results[0].type, this.results[0].items[0])
|
if (results.value.length === 1 && results.value[0].items.length === 1) {
|
||||||
|
const result = results.value[0]
|
||||||
|
doAction(result.type, result.items[0])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selectedCmd === null) {
|
if (selectedCmd.value === null || query.value === '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.query === '') {
|
closeQuickActions()
|
||||||
|
await selectedCmd.value.action()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function newTask() {
|
||||||
|
if (currentList.value === null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (this.selectedCmd.action) {
|
|
||||||
case CMD_NEW_TASK:
|
|
||||||
this.newTask()
|
|
||||||
break
|
|
||||||
case CMD_NEW_LIST:
|
|
||||||
this.newList()
|
|
||||||
break
|
|
||||||
case CMD_NEW_NAMESPACE:
|
|
||||||
this.newNamespace()
|
|
||||||
break
|
|
||||||
case CMD_NEW_TEAM:
|
|
||||||
this.newTeam()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async newTask() {
|
|
||||||
if (this.currentList === null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const taskStore = useTaskStore()
|
|
||||||
const task = await taskStore.createNewTask({
|
const task = await taskStore.createNewTask({
|
||||||
title: this.query,
|
title: query.value,
|
||||||
listId: this.currentList.id,
|
listId: currentList.value.id,
|
||||||
})
|
})
|
||||||
this.$message.success({message: this.$t('task.createSuccess')})
|
success({ message: t('task.createSuccess') })
|
||||||
this.$router.push({name: 'task.detail', params: {id: task.id}})
|
await router.push({ name: 'task.detail', params: { id: task.id } })
|
||||||
this.closeQuickActions()
|
}
|
||||||
},
|
|
||||||
async newList() {
|
async function newList() {
|
||||||
if (this.currentList === null) {
|
if (currentList.value === null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const newList = await listStore.createList(new ListModel({
|
||||||
const newList = new ListModel({
|
title: query.value,
|
||||||
title: this.query,
|
namespaceId: currentList.value.namespaceId,
|
||||||
namespaceId: this.currentList.namespaceId,
|
}))
|
||||||
|
success({ message: t('list.create.createdSuccess')})
|
||||||
|
await router.push({
|
||||||
|
name: 'list.index',
|
||||||
|
params: { listId: newList.id },
|
||||||
})
|
})
|
||||||
const listStore = useListStore()
|
}
|
||||||
const list = await listStore.createList(newList)
|
|
||||||
this.$message.success({message: this.$t('list.create.createdSuccess')})
|
|
||||||
this.$router.push({name: 'list.index', params: {listId: list.id}})
|
|
||||||
this.closeQuickActions()
|
|
||||||
},
|
|
||||||
async newNamespace() {
|
|
||||||
const newNamespace = new NamespaceModel({title: this.query})
|
|
||||||
|
|
||||||
await useNamespaceStore().createNamespace(newNamespace)
|
async function newNamespace() {
|
||||||
this.$message.success({message: this.$t('namespace.create.success')})
|
const newNamespace = new NamespaceModel({ title: query.value })
|
||||||
this.closeQuickActions()
|
await namespaceStore.createNamespace(newNamespace)
|
||||||
},
|
success({ message: t('namespace.create.success') })
|
||||||
async newTeam() {
|
}
|
||||||
const newTeam = new TeamModel({name: this.query})
|
|
||||||
const team = await this.teamService.create(newTeam)
|
async function newTeam() {
|
||||||
this.$router.push({
|
const newTeam = new TeamModel({ name: query.value })
|
||||||
|
const team = await teamService.create(newTeam)
|
||||||
|
await router.push({
|
||||||
name: 'teams.edit',
|
name: 'teams.edit',
|
||||||
params: {id: team.id},
|
params: { id: team.id },
|
||||||
})
|
})
|
||||||
this.$message.success({message: this.$t('team.create.success')})
|
success({ message: t('team.create.success') })
|
||||||
this.closeQuickActions()
|
}
|
||||||
},
|
|
||||||
select(parentIndex, index) {
|
|
||||||
|
|
||||||
if (index < 0 && parentIndex === 0) {
|
type BaseButtonInstance = InstanceType<typeof BaseButton>
|
||||||
this.$refs.searchInput.focus()
|
const resultRefs = ref<(BaseButtonInstance | null)[][]>([])
|
||||||
return
|
|
||||||
|
function setResultRefs(el: Element | ComponentPublicInstance | null, index: number, key: number) {
|
||||||
|
if (resultRefs.value[index] === undefined) {
|
||||||
|
resultRefs.value[index] = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resultRefs.value[index][key] = el as (BaseButtonInstance | null)
|
||||||
|
}
|
||||||
|
|
||||||
|
function select(parentIndex: number, index: number) {
|
||||||
|
if (index < 0 && parentIndex === 0) {
|
||||||
|
searchInput.value?.focus()
|
||||||
|
return
|
||||||
|
}
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
parentIndex--
|
parentIndex--
|
||||||
index = this.results[parentIndex].items.length - 1
|
index = results.value[parentIndex].items.length - 1
|
||||||
}
|
}
|
||||||
|
let elems = resultRefs.value[parentIndex][index]
|
||||||
let elems = this.$refs[`result-${parentIndex}_${index}`]
|
if (results.value[parentIndex].items.length === index) {
|
||||||
|
elems = resultRefs.value[parentIndex + 1][0]
|
||||||
if (this.results[parentIndex].items.length === index) {
|
|
||||||
elems = this.$refs[`result-${parentIndex + 1}_0`]
|
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
if (typeof elems === 'undefined' || elems.length === 0) {
|
typeof elems === 'undefined'
|
||||||
|
/* || elems.length === 0 */
|
||||||
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(elems)) {
|
if (Array.isArray(elems)) {
|
||||||
elems[0].focus()
|
elems[0].focus()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
elems?.focus()
|
||||||
|
}
|
||||||
|
|
||||||
elems.focus()
|
function unselectCmd() {
|
||||||
},
|
if (query.value !== '') {
|
||||||
unselectCmd() {
|
|
||||||
if (this.query !== '') {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
selectedCmd.value = null
|
||||||
|
}
|
||||||
|
|
||||||
this.selectedCmd = null
|
function reset() {
|
||||||
},
|
query.value = ''
|
||||||
reset() {
|
selectedCmd.value = null
|
||||||
this.query = ''
|
}
|
||||||
this.selectedCmd = null
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -508,8 +581,9 @@ export default defineComponent({
|
|||||||
top: 3rem;
|
top: 3rem;
|
||||||
transform: translate(-50%, 0);
|
transform: translate(-50%, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.action-input {
|
.action-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
@ -522,29 +596,28 @@ export default defineComponent({
|
|||||||
padding-left: .5rem;
|
padding-left: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.active-cmd {
|
}
|
||||||
|
.active-cmd {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
margin-left: .5rem;
|
margin-left: .5rem;
|
||||||
background-color: var(--grey-100);
|
background-color: var(--grey-100);
|
||||||
color: var(--grey-800);
|
color: var(--grey-800);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.results {
|
.results {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: var(--grey-800);
|
color: var(--grey-800);
|
||||||
|
}
|
||||||
|
|
||||||
.result {
|
.result-title {
|
||||||
&-title {
|
|
||||||
background: var(--grey-100);
|
background: var(--grey-100);
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
display: block;
|
display: block;
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-items {
|
.result-item-button {
|
||||||
button {
|
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@ -559,7 +632,8 @@ export default defineComponent({
|
|||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:focus, &:hover {
|
&:focus,
|
||||||
|
&:hover {
|
||||||
background: var(--grey-100);
|
background: var(--grey-100);
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
@ -567,10 +641,6 @@ export default defineComponent({
|
|||||||
&:active {
|
&:active {
|
||||||
background: var(--grey-100);
|
background: var(--grey-100);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK:
|
// HACK:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user