From 463d22b07ca75c91a6453c59ebdc3cb56ee61f96 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 4 Dec 2022 20:19:43 +0100 Subject: [PATCH] fix(quick add magic): don't create a new label multiple times if it is used in multiple tasks Resolves https://github.com/go-vikunja/frontend/issues/94 --- src/components/tasks/add-task.vue | 11 +++++++++- src/modules/parseTaskText.ts | 13 +++++++++++- src/stores/tasks.ts | 34 ++++++++++++++++++------------- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/components/tasks/add-task.vue b/src/components/tasks/add-task.vue index 12c12f7a81..a93ce3d6a7 100644 --- a/src/components/tasks/add-task.vue +++ b/src/components/tasks/add-task.vue @@ -53,6 +53,7 @@ import {RELATION_KIND} from '@/types/IRelationKind' import {useAuthStore} from '@/stores/auth' import {useTaskStore} from '@/stores/tasks' import {useAutoHeightTextarea} from '@/composables/useAutoHeightTextarea' +import {getLabelsFromPrefix} from '@/modules/parseTaskText' const props = defineProps({ defaultPosition: { @@ -82,6 +83,7 @@ function resetEmptyTitleError(e) { } const loading = computed(() => taskStore.isLoading) + async function addTask() { if (newTaskTitle.value === '') { errorMessage.value = t('list.create.addTitleRequired') @@ -98,11 +100,18 @@ async function addTask() { // by quick add magic. const createdTasks: { [key: ITask['title']]: ITask } = {} const tasksToCreate = parseSubtasksViaIndention(newTaskTitle.value) + + // We ensure all labels exist prior to passing them down to the create task method + // In the store it will only ever see one task at a time so there's no way to reliably + // check if a new label was created before (because everything happens async). + const allLabels = tasksToCreate.map(({title}) => getLabelsFromPrefix(title) ?? []) + await taskStore.ensureLabelsExist(allLabels.flat()) + const newTasks = tasksToCreate.map(async ({title, list}) => { if (title === '') { return } - + // If the task has a list specified, make sure to use it let listId = null if (list !== null) { diff --git a/src/modules/parseTaskText.ts b/src/modules/parseTaskText.ts index ba4d50b869..6fab191a84 100644 --- a/src/modules/parseTaskText.ts +++ b/src/modules/parseTaskText.ts @@ -72,7 +72,7 @@ export const parseTaskText = (text: string, prefixesMode: PrefixMode = PrefixMod return result } - result.labels = getItemsFromPrefix(text, prefixes.label) + result.labels = getLabelsFromPrefix(text, prefixes.label) ?? [] result.text = cleanupItemText(result.text, result.labels, prefixes.label) result.list = getListFromPrefix(result.text, prefixes.list) @@ -142,6 +142,17 @@ export const getListFromPrefix = (text: string, listPrefix: string | null = null return lists.length > 0 ? lists[0] : null } +export const getLabelsFromPrefix = (text: string, listPrefix: string | null = null): string[] | null => { + if (listPrefix === null) { + const prefixes = PREFIXES[getQuickAddMagicMode()] + if (prefixes === undefined) { + return null + } + listPrefix = prefixes.label + } + return getItemsFromPrefix(text, listPrefix) +} + const getPriority = (text: string, prefix: string): number | null => { const ps = getItemsFromPrefix(text, prefix) if (ps.length === 0) { diff --git a/src/stores/tasks.ts b/src/stores/tasks.ts index a3c3c91194..202ed73717 100644 --- a/src/stores/tasks.ts +++ b/src/stores/tasks.ts @@ -1,5 +1,5 @@ import {computed, ref} from 'vue' -import {defineStore, acceptHMRUpdate} from 'pinia' +import {acceptHMRUpdate, defineStore} from 'pinia' import router from '@/router' import {formatISO} from 'date-fns' @@ -14,8 +14,8 @@ import {parseTaskText} from '@/modules/parseTaskText' import TaskAssigneeModel from '@/models/taskAssignee' import LabelTaskModel from '@/models/labelTask' -import TaskModel from '@/models/task' import LabelTask from '@/models/labelTask' +import TaskModel from '@/models/task' import LabelModel from '@/models/label' import type {ILabel} from '@/modelTypes/ILabel' @@ -306,6 +306,20 @@ export const useTaskStore = defineStore('task', () => { return response } + + async function ensureLabelsExist(labels: string[]): Promise { + const all = [...new Set(labels)] + const mustCreateLabel = all.map(async labelTitle => { + let label = validateLabel(Object.values(labelStore.labels), labelTitle) + if (typeof label === 'undefined') { + // label not found, create it + const labelModel = new LabelModel({title: labelTitle}) + label = await labelStore.createLabel(labelModel) + } + return label + }) + return Promise.all(mustCreateLabel) + } // Do everything that is involved in finding, creating and adding the label to the task async function addLabelsToTask( @@ -316,16 +330,8 @@ export const useTaskStore = defineStore('task', () => { return task } - const labelAddsToWaitFor = parsedLabels.map(async labelTitle => { - let label = validateLabel(Object.values(labelStore.labels), labelTitle) - if (typeof label === 'undefined') { - // label not found, create it - const labelModel = new LabelModel({title: labelTitle}) - label = await labelStore.createLabel(labelModel) - } - - return addLabelToTask(task, label) - }) + const labels = await ensureLabelsExist(parsedLabels) + const labelAddsToWaitFor = labels.map(async l => addLabelToTask(task, l)) // This waits until all labels are created and added to the task await Promise.all(labelAddsToWaitFor) @@ -402,11 +408,10 @@ export const useTaskStore = defineStore('task', () => { const taskService = new TaskService() try { const createdTask = await taskService.create(task) - const result = await addLabelsToTask({ + return await addLabelsToTask({ task: createdTask, parsedLabels: parsedTask.labels, }) - return result } finally { cancel() } @@ -438,6 +443,7 @@ export const useTaskStore = defineStore('task', () => { createNewTask, setCoverImage, findListId, + ensureLabelsExist, } })