feat: simplify heading blur logic (#727)

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#727
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
This commit is contained in:
dpschen 2021-09-10 12:57:59 +00:00 committed by konrad
parent 0376ef53e3
commit dae441a373
3 changed files with 29 additions and 46 deletions

View File

@ -7,16 +7,17 @@
<h1
class="title input"
:class="{'disabled': !canWrite}"
@focusout="save()"
@keydown.enter.prevent.stop="save()"
@blur="save($event.target.textContent)"
@keydown.enter.prevent.stop="$event.target.blur()"
:contenteditable="canWrite ? 'true' : 'false'"
spellcheck="false"
ref="taskTitle">{{ task.title.trim() }}</h1>
<transition name="fade">
<span class="is-inline-flex is-align-items-center" v-if="loading && saving">
<span class="loader is-inline-block mr-2"></span>
{{ $t('misc.saving') }}
</span>
<span class="has-text-success is-inline-flex is-align-content-center" v-if="!loading && saved">
<span class="has-text-success is-inline-flex is-align-content-center" v-if="!loading && showSavedMessage">
<icon icon="check" class="mr-2"/>
{{ $t('misc.saved') }}
</span>
@ -25,22 +26,22 @@
</template>
<script>
import {LOADING} from '@/store/mutation-types'
import {mapState} from 'vuex'
export default {
name: 'heading',
data() {
return {
task: {title: '', identifier: '', index: ''},
taskTitle: '',
saved: false,
showSavedMessage: false,
saving: false, // Since loading is global state, this variable ensures we're only showing the saving icon when saving the description.
}
},
computed: mapState({
loading: LOADING,
}),
computed: {
...mapState(['loading']),
task() {
return this.value
},
},
props: {
value: {
required: true,
@ -50,50 +51,29 @@ export default {
default: false,
},
},
watch: {
value(newVal) {
this.task = newVal
this.taskTitle = this.task.title
},
},
mounted() {
this.task = this.value
this.taskTitle = this.task.title
},
methods: {
save() {
this.$refs.taskTitle.spellcheck = false
// Pull the task title from the contenteditable
let taskTitle = this.$refs.taskTitle.textContent
this.task.title = taskTitle
// We only want to save if the title was actually change.
// Because the contenteditable does not have a change event,
// we're building it ourselves and only calling saveTask()
save(title) {
// We only want to save if the title was actually changed.
// Because the contenteditable does not have a change event
// we're building it ourselves and only continue
// if the task title changed.
if (this.task.title !== this.taskTitle) {
this.$refs.taskTitle.blur()
this.saveTask()
this.taskTitle = taskTitle
}
},
saveTask() {
// When only saving with enter, the focusout event is called as well. This then leads to the saveTask
// method being called twice, overriding some task attributes in the second run.
// If we simply check if we're already in the process of saving, we can prevent that.
if (this.saving) {
if (title === this.task.title) {
return
}
this.saving = true
this.$store.dispatch('tasks/update', this.task)
.then(() => {
this.$emit('input', this.task)
this.saved = true
const newTask = {
...this.task,
title,
}
this.$store.dispatch('tasks/update', newTask)
.then((task) => {
this.$emit('input', task)
this.showSavedMessage = true
setTimeout(() => {
this.saved = false
this.showSavedMessage = false
}, 2000)
})
.catch(e => {

View File

@ -15,6 +15,7 @@ export default class TaskModel extends AbstractModel {
super(data)
this.id = Number(this.id)
this.title = this.title?.trim()
this.listId = Number(this.listId)
// Make date objects from timestamps

View File

@ -39,6 +39,8 @@ export default class TaskService extends AbstractService {
processModel(model) {
model.title = model.title?.trim()
// Ensure that listId is an int
model.listId = Number(model.listId)