feat: allow quickly creating multiple tasks at once with multiline input (#796)
Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/796 Co-authored-by: konrad <k@knt.li> Co-committed-by: konrad <k@knt.li>
This commit is contained in:
parent
3dfa286a12
commit
442e6b12e0
2 changed files with 49 additions and 7 deletions
|
@ -27,7 +27,7 @@ describe('Task', () => {
|
||||||
|
|
||||||
it('Should be created new', () => {
|
it('Should be created new', () => {
|
||||||
cy.visit('/lists/1/list')
|
cy.visit('/lists/1/list')
|
||||||
cy.get('input.input[placeholder="Add a new task…"')
|
cy.get('.input[placeholder="Add a new task…"')
|
||||||
.type('New Task')
|
.type('New Task')
|
||||||
cy.get('.button')
|
cy.get('.button')
|
||||||
.contains('Add')
|
.contains('Add')
|
||||||
|
@ -43,7 +43,7 @@ describe('Task', () => {
|
||||||
cy.visit('/lists/1/list')
|
cy.visit('/lists/1/list')
|
||||||
cy.get('.list-is-empty-notice')
|
cy.get('.list-is-empty-notice')
|
||||||
.should('not.exist')
|
.should('not.exist')
|
||||||
cy.get('input.input[placeholder="Add a new task…"')
|
cy.get('.input[placeholder="Add a new task…"')
|
||||||
.type('New Task')
|
.type('New Task')
|
||||||
cy.get('.button')
|
cy.get('.button')
|
||||||
.contains('Add')
|
.contains('Add')
|
||||||
|
|
|
@ -2,16 +2,17 @@
|
||||||
<div class="task-add">
|
<div class="task-add">
|
||||||
<div class="field is-grouped">
|
<div class="field is-grouped">
|
||||||
<p class="control has-icons-left is-expanded">
|
<p class="control has-icons-left is-expanded">
|
||||||
<input
|
<textarea
|
||||||
:disabled="taskService.loading || null"
|
:disabled="taskService.loading || null"
|
||||||
@keyup.enter="addTask()"
|
|
||||||
class="input"
|
class="input"
|
||||||
:placeholder="$t('list.list.addPlaceholder')"
|
:placeholder="$t('list.list.addPlaceholder')"
|
||||||
type="text"
|
type="text"
|
||||||
v-focus
|
v-focus
|
||||||
v-model="newTaskTitle"
|
v-model="newTaskTitle"
|
||||||
ref="newTaskInput"
|
ref="newTaskInput"
|
||||||
|
:style="{'height': `${textAreaHeight}px`}"
|
||||||
@keyup="errorMessage = ''"
|
@keyup="errorMessage = ''"
|
||||||
|
@keydown.enter="handleEnter"
|
||||||
/>
|
/>
|
||||||
<span class="icon is-small is-left">
|
<span class="icon is-small is-left">
|
||||||
<icon icon="tasks"/>
|
<icon icon="tasks"/>
|
||||||
|
@ -40,6 +41,12 @@ import TaskService from '../../services/task'
|
||||||
import createTask from '@/components/tasks/mixins/createTask'
|
import createTask from '@/components/tasks/mixins/createTask'
|
||||||
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
|
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
|
||||||
|
|
||||||
|
const INITIAL_SCROLL_HEIGHT = 40
|
||||||
|
|
||||||
|
const cleanupTitle = title => {
|
||||||
|
return title.replace(/^((\* |\+ |- )(\[ \] )?)/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'add-task',
|
name: 'add-task',
|
||||||
data() {
|
data() {
|
||||||
|
@ -47,6 +54,7 @@ export default {
|
||||||
newTaskTitle: '',
|
newTaskTitle: '',
|
||||||
taskService: new TaskService(),
|
taskService: new TaskService(),
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
|
textAreaHeight: INITIAL_SCROLL_HEIGHT,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mixins: [
|
mixins: [
|
||||||
|
@ -61,6 +69,16 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
newTaskTitle(newVal) {
|
||||||
|
let scrollHeight = this.$refs.newTaskInput.scrollHeight
|
||||||
|
if (scrollHeight < INITIAL_SCROLL_HEIGHT || newVal === '') {
|
||||||
|
scrollHeight = INITIAL_SCROLL_HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
this.textAreaHeight = scrollHeight
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addTask() {
|
addTask() {
|
||||||
if (this.newTaskTitle === '') {
|
if (this.newTaskTitle === '') {
|
||||||
|
@ -73,10 +91,20 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.createNewTask(this.newTaskTitle, 0, this.$store.state.auth.settings.defaultListId, this.defaultPosition)
|
const newTasks = []
|
||||||
.then(task => {
|
this.newTaskTitle.split(/[\r\n]+/).forEach(t => {
|
||||||
|
newTasks.push(
|
||||||
|
this.createNewTask(cleanupTitle(t), 0, this.$store.state.auth.settings.defaultListId, this.defaultPosition)
|
||||||
|
.then(task => {
|
||||||
|
this.$emit('taskAdded', task)
|
||||||
|
return task
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
Promise.all(newTasks)
|
||||||
|
.then(() => {
|
||||||
this.newTaskTitle = ''
|
this.newTaskTitle = ''
|
||||||
this.$emit('taskAdded', task)
|
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
if (e === 'NO_LIST') {
|
if (e === 'NO_LIST') {
|
||||||
|
@ -86,6 +114,16 @@ export default {
|
||||||
this.$message.error(e)
|
this.$message.error(e)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
handleEnter(e) {
|
||||||
|
// when pressing shift + enter we want to continue as we normally would. Otherwise, we want to create
|
||||||
|
// the new task(s). The vue event modifier don't allow this, hence this method.
|
||||||
|
if (e.shiftKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
this.addTask()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -98,4 +136,8 @@ export default {
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input, .textarea {
|
||||||
|
transition: border-color $transition;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue