mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-06-01 19:01:37 +00:00
feat(frontend): apply quick add magic when creating related tasks
Route the create flow through taskStore.createNewTask so titles typed into the related-task input get parsed for labels, priority, assignees, due dates and cross-project targets - matching the main add-task input. Also surface the quick-add-magic hint next to the field.
This commit is contained in:
@@ -36,7 +36,7 @@
|
||||
</label>
|
||||
<div
|
||||
key="field-search"
|
||||
class="field"
|
||||
class="field task-relation-search-field"
|
||||
>
|
||||
<Multiselect
|
||||
v-model="newTaskRelation.task"
|
||||
@@ -77,6 +77,7 @@
|
||||
</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
<QuickAddMagic class="task-relation-quick-add-magic" />
|
||||
</div>
|
||||
<div
|
||||
key="field-kind"
|
||||
@@ -200,6 +201,7 @@ import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import Multiselect from '@/components/input/Multiselect.vue'
|
||||
import FancyCheckbox from '@/components/input/FancyCheckbox.vue'
|
||||
import QuickAddMagic from '@/components/tasks/partials/QuickAddMagic.vue'
|
||||
|
||||
import {error, success} from '@/message'
|
||||
import {useTaskStore} from '@/stores/tasks'
|
||||
@@ -362,7 +364,7 @@ async function removeTaskRelation() {
|
||||
}
|
||||
|
||||
async function createAndRelateTask(title: string) {
|
||||
const newTask = await taskService.create(new TaskModel({title, projectId: props.projectId}))
|
||||
const newTask = await taskStore.createNewTask({title, projectId: props.projectId})
|
||||
newTaskRelation.task = newTask
|
||||
await addTaskRelation()
|
||||
}
|
||||
@@ -459,6 +461,17 @@ async function toggleTaskDone(task: ITask) {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.task-relation-search-field {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.task-relation-quick-add-magic {
|
||||
position: absolute;
|
||||
inset-block-start: .5rem;
|
||||
inset-inline-end: .75rem;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
// FIXME: The height of the actual checkbox in the <FancyCheckbox/> component is too much resulting in a
|
||||
// weired positioning of the checkbox. Setting the height here is a workaround until we fix the styling
|
||||
// of the component.
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import {test, expect} from '../../support/fixtures'
|
||||
import {ProjectFactory} from '../../factories/project'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {UserFactory} from '../../factories/user'
|
||||
import {createDefaultViews} from '../project/prepareProjects'
|
||||
import {login} from '../../support/authenticateUser'
|
||||
|
||||
async function openRelatedTasksForm(page) {
|
||||
await page.locator('.task-view .action-buttons .button').filter({hasText: 'Add Relation'}).click()
|
||||
const input = page.locator('.task-relations .multiselect input').first()
|
||||
await expect(input).toBeVisible()
|
||||
return input
|
||||
}
|
||||
|
||||
test.describe('Related tasks quick add magic', () => {
|
||||
test('Applies a label parsed via *prefix to the new related task', async ({authenticatedPage: page}) => {
|
||||
const project = (await ProjectFactory.create(1, {id: 1, title: 'Project A'}))[0]
|
||||
await createDefaultViews(project.id)
|
||||
const parent = (await TaskFactory.create(1, {id: 1, title: 'Parent task', project_id: project.id}, false))[0]
|
||||
|
||||
await page.goto(`/tasks/${parent.id}`)
|
||||
const input = await openRelatedTasksForm(page)
|
||||
await input.fill('Subtask one *Urgent')
|
||||
await input.press('Enter')
|
||||
|
||||
const relatedTaskLink = page.locator('.task-relations .related-tasks .task a').filter({hasText: 'Subtask one'})
|
||||
await expect(relatedTaskLink).toBeVisible({timeout: 10000})
|
||||
// Quick add magic strips the *Urgent prefix from the title
|
||||
await expect(relatedTaskLink).not.toContainText('*Urgent')
|
||||
|
||||
await relatedTaskLink.click()
|
||||
await expect(page).toHaveURL(/\/tasks\/\d+/)
|
||||
await expect(page.locator('.task-view .details.labels-list .multiselect .input-wrapper span.tag').filter({hasText: 'Urgent'}))
|
||||
.toBeVisible({timeout: 10000})
|
||||
})
|
||||
|
||||
test('Applies a priority parsed via !prefix to the new related task', async ({authenticatedPage: page}) => {
|
||||
const project = (await ProjectFactory.create(1, {id: 1, title: 'Project A'}))[0]
|
||||
await createDefaultViews(project.id)
|
||||
const parent = (await TaskFactory.create(1, {id: 1, title: 'Parent task', project_id: project.id}, false))[0]
|
||||
|
||||
await page.goto(`/tasks/${parent.id}`)
|
||||
const input = await openRelatedTasksForm(page)
|
||||
await input.fill('Important work !4')
|
||||
await input.press('Enter')
|
||||
|
||||
const relatedTaskLink = page.locator('.task-relations .related-tasks .task a').filter({hasText: 'Important work'})
|
||||
await expect(relatedTaskLink).toBeVisible({timeout: 10000})
|
||||
await expect(relatedTaskLink).not.toContainText('!4')
|
||||
|
||||
await relatedTaskLink.click()
|
||||
// Priority 4 is "Urgent"
|
||||
await expect(page.locator('.task-view .columns.details select').first()).toHaveValue('4', {timeout: 10000})
|
||||
})
|
||||
|
||||
test('Creates the related task in another project via +project prefix', async ({authenticatedPage: page}) => {
|
||||
const projectA = (await ProjectFactory.create(1, {id: 1, title: 'Source'}))[0]
|
||||
await createDefaultViews(projectA.id)
|
||||
const projectB = (await ProjectFactory.create(1, {id: 2, title: 'TargetProject'}, false))[0]
|
||||
await createDefaultViews(projectB.id, 5)
|
||||
const parent = (await TaskFactory.create(1, {id: 1, title: 'Parent task', project_id: projectA.id}, false))[0]
|
||||
|
||||
await page.goto(`/tasks/${parent.id}`)
|
||||
const input = await openRelatedTasksForm(page)
|
||||
await input.fill('Cross task +TargetProject')
|
||||
await input.press('Enter')
|
||||
|
||||
const relatedTaskRow = page.locator('.task-relations .related-tasks .task').filter({hasText: 'Cross task'})
|
||||
await expect(relatedTaskRow).toBeVisible({timeout: 10000})
|
||||
await expect(relatedTaskRow.locator('a')).not.toContainText('+TargetProject')
|
||||
// Cross-project marker shows the other project name
|
||||
await expect(relatedTaskRow.locator('.different-project')).toContainText('TargetProject')
|
||||
})
|
||||
|
||||
test('Keeps the title literal when quick add magic is disabled', async ({page, apiContext}) => {
|
||||
const user = (await UserFactory.create(1, {
|
||||
frontend_settings: JSON.stringify({
|
||||
quickAddMagicMode: 'disabled',
|
||||
}),
|
||||
}))[0]
|
||||
const project = (await ProjectFactory.create(1, {id: 1, title: 'Project A', owner_id: user.id}))[0]
|
||||
await createDefaultViews(project.id)
|
||||
const parent = (await TaskFactory.create(1, {id: 1, title: 'Parent task', project_id: project.id, created_by_id: user.id}, false))[0]
|
||||
|
||||
await login(page, apiContext, user)
|
||||
await page.goto(`/tasks/${parent.id}`)
|
||||
|
||||
const input = await openRelatedTasksForm(page)
|
||||
await input.fill('Buy milk *Urgent')
|
||||
await input.press('Enter')
|
||||
|
||||
// With magic disabled, the prefix stays in the title verbatim
|
||||
await expect(page.locator('.task-relations .related-tasks .task a').filter({hasText: 'Buy milk *Urgent'}))
|
||||
.toBeVisible({timeout: 10000})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user