From 270e32290a28f7c94ac9624f00052d888b72a259 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 4 Sep 2023 11:24:10 +0200 Subject: [PATCH] fix(quick add magic): ignore common task indention when adding multiple tasks at once Resolves https://kolaente.dev/vikunja/frontend/issues/3732 --- src/helpers/parseSubtasksViaIndention.test.ts | 50 ++++++++++++++++++- src/helpers/parseSubtasksViaIndention.ts | 28 +++++++++-- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/helpers/parseSubtasksViaIndention.test.ts b/src/helpers/parseSubtasksViaIndention.test.ts index 55017f039..3ebbd1306 100644 --- a/src/helpers/parseSubtasksViaIndention.test.ts +++ b/src/helpers/parseSubtasksViaIndention.test.ts @@ -4,7 +4,7 @@ import {PrefixMode} from '@/modules/parseTaskText' describe('Parse Subtasks via Relation', () => { it('Should not return a parent for a single task', () => { - const tasks = parseSubtasksViaIndention('single task') + const tasks = parseSubtasksViaIndention('single task', PrefixMode.Default) expect(tasks).to.have.length(1) expect(tasks[0].parent).toBeNull() @@ -118,4 +118,52 @@ task two`, PrefixMode.Default) expect(tasks[1].project).to.eq('list') expect(tasks[2].project).to.eq('list') }) + it('Should clean the indention if there is indention on the first line', () => { + const tasks = parseSubtasksViaIndention( +` parent task + sub task one + sub task two`, PrefixMode.Default) + + expect(tasks).to.have.length(3) + expect(tasks[0].parent).toBeNull() + expect(tasks[0].title).to.eq('parent task') + expect(tasks[1].title).to.eq('sub task one') + expect(tasks[1].parent).toBeNull() + expect(tasks[2].title).to.eq('sub task two') + expect(tasks[2].parent).to.eq('sub task one') + }) + it('Should clean the indention if there is indention on the first line but not for subsequent tasks', () => { + const tasks = parseSubtasksViaIndention( + ` parent task + sub task one +first level task one + sub task two`, PrefixMode.Default) + + expect(tasks).to.have.length(4) + expect(tasks[0].parent).toBeNull() + expect(tasks[0].title).to.eq('parent task') + expect(tasks[1].title).to.eq('sub task one') + expect(tasks[1].parent).toBeNull() + expect(tasks[2].title).to.eq('first level task one') + expect(tasks[2].parent).toBeNull() + expect(tasks[3].title).to.eq('sub task two') + expect(tasks[3].parent).to.eq('first level task one') + }) + it('Should clean the indention if there is indention on the first line for subsequent tasks with less indention', () => { + const tasks = parseSubtasksViaIndention( + ` parent task + sub task one + first level task one + sub task two`, PrefixMode.Default) + + expect(tasks).to.have.length(4) + expect(tasks[0].parent).toBeNull() + expect(tasks[0].title).to.eq('parent task') + expect(tasks[1].title).to.eq('sub task one') + expect(tasks[1].parent).toBeNull() + expect(tasks[2].title).to.eq('first level task one') + expect(tasks[2].parent).toBeNull() + expect(tasks[3].title).to.eq('sub task two') + expect(tasks[3].parent).to.eq('first level task one') + }) }) diff --git a/src/helpers/parseSubtasksViaIndention.ts b/src/helpers/parseSubtasksViaIndention.ts index 1956e671f..2a597991a 100644 --- a/src/helpers/parseSubtasksViaIndention.ts +++ b/src/helpers/parseSubtasksViaIndention.ts @@ -17,7 +17,29 @@ const spaceRegex = /^ */ * relation between each other. */ export function parseSubtasksViaIndention(taskTitles: string, prefixMode: PrefixMode): TaskWithParent[] { - const titles = taskTitles.split(/[\r\n]+/) + let titles = taskTitles.split(/[\r\n]+/) + + if (titles.length == 0) { + return [] + } + + const spaceOnFirstLine = /^(\t| )+/ + const spaces = spaceOnFirstLine.exec(titles[0]) + if (spaces !== null) { + let spacesToCut = spaces[0].length + titles = titles.map(title => { + const spacesOnThisLine = spaceOnFirstLine.exec(title) + if (spacesOnThisLine === null) { + // This means the current task title does not start with indention, but the very first one did + // To prevent cutting actual task data we now need to update the number of spaces to cut + spacesToCut = 0 + } + if (spacesOnThisLine !== null && spacesOnThisLine[0].length < spacesToCut) { + spacesToCut = spacesOnThisLine[0].length + } + return title.substring(spacesToCut) + }) + } return titles.map((title, index) => { const task: TaskWithParent = { @@ -32,7 +54,7 @@ export function parseSubtasksViaIndention(taskTitles: string, prefixMode: Prefix return task } - const matched = spaceRegex.exec(title) + const matched = spaceRegex.exec(task.title) const matchedSpaces = matched ? matched[0].length : 0 if (matchedSpaces > 0) { @@ -45,7 +67,7 @@ export function parseSubtasksViaIndention(taskTitles: string, prefixMode: Prefix const parentMatched = spaceRegex.exec(task.parent) parentSpaces = parentMatched ? parentMatched[0].length : 0 } while (parentSpaces >= matchedSpaces) - task.title = cleanupTitle(title.replace(spaceRegex, '')) + task.title = cleanupTitle(task.title.replace(spaceRegex, '')) task.parent = task.parent.replace(spaceRegex, '') if (task.project === null) { // This allows to specify a project once for the parent task and inherit it to all subtasks