feat: simplify heading blur logic (#727)
Co-authored-by: Dominik Pschenitschni <mail@celement.de> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/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:
parent
0376ef53e3
commit
dae441a373
3 changed files with 29 additions and 46 deletions
|
@ -7,16 +7,17 @@
|
||||||
<h1
|
<h1
|
||||||
class="title input"
|
class="title input"
|
||||||
:class="{'disabled': !canWrite}"
|
:class="{'disabled': !canWrite}"
|
||||||
@focusout="save()"
|
@blur="save($event.target.textContent)"
|
||||||
@keydown.enter.prevent.stop="save()"
|
@keydown.enter.prevent.stop="$event.target.blur()"
|
||||||
:contenteditable="canWrite ? 'true' : 'false'"
|
:contenteditable="canWrite ? 'true' : 'false'"
|
||||||
|
spellcheck="false"
|
||||||
ref="taskTitle">{{ task.title.trim() }}</h1>
|
ref="taskTitle">{{ task.title.trim() }}</h1>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<span class="is-inline-flex is-align-items-center" v-if="loading && saving">
|
<span class="is-inline-flex is-align-items-center" v-if="loading && saving">
|
||||||
<span class="loader is-inline-block mr-2"></span>
|
<span class="loader is-inline-block mr-2"></span>
|
||||||
{{ $t('misc.saving') }}
|
{{ $t('misc.saving') }}
|
||||||
</span>
|
</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"/>
|
<icon icon="check" class="mr-2"/>
|
||||||
{{ $t('misc.saved') }}
|
{{ $t('misc.saved') }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -25,22 +26,22 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {LOADING} from '@/store/mutation-types'
|
|
||||||
import {mapState} from 'vuex'
|
import {mapState} from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'heading',
|
name: 'heading',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
task: {title: '', identifier: '', index: ''},
|
showSavedMessage: false,
|
||||||
taskTitle: '',
|
|
||||||
saved: false,
|
|
||||||
saving: false, // Since loading is global state, this variable ensures we're only showing the saving icon when saving the description.
|
saving: false, // Since loading is global state, this variable ensures we're only showing the saving icon when saving the description.
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: mapState({
|
computed: {
|
||||||
loading: LOADING,
|
...mapState(['loading']),
|
||||||
}),
|
task() {
|
||||||
|
return this.value
|
||||||
|
},
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -50,50 +51,29 @@ export default {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
value(newVal) {
|
|
||||||
this.task = newVal
|
|
||||||
this.taskTitle = this.task.title
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.task = this.value
|
|
||||||
this.taskTitle = this.task.title
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
save() {
|
save(title) {
|
||||||
this.$refs.taskTitle.spellcheck = false
|
// We only want to save if the title was actually changed.
|
||||||
|
// Because the contenteditable does not have a change event
|
||||||
// Pull the task title from the contenteditable
|
// we're building it ourselves and only continue
|
||||||
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()
|
|
||||||
// if the task title changed.
|
// if the task title changed.
|
||||||
if (this.task.title !== this.taskTitle) {
|
if (title === this.task.title) {
|
||||||
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) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.saving = true
|
this.saving = true
|
||||||
|
|
||||||
this.$store.dispatch('tasks/update', this.task)
|
const newTask = {
|
||||||
.then(() => {
|
...this.task,
|
||||||
this.$emit('input', this.task)
|
title,
|
||||||
this.saved = true
|
}
|
||||||
|
|
||||||
|
this.$store.dispatch('tasks/update', newTask)
|
||||||
|
.then((task) => {
|
||||||
|
this.$emit('input', task)
|
||||||
|
this.showSavedMessage = true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.saved = false
|
this.showSavedMessage = false
|
||||||
}, 2000)
|
}, 2000)
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
|
|
|
@ -15,6 +15,7 @@ export default class TaskModel extends AbstractModel {
|
||||||
super(data)
|
super(data)
|
||||||
|
|
||||||
this.id = Number(this.id)
|
this.id = Number(this.id)
|
||||||
|
this.title = this.title?.trim()
|
||||||
this.listId = Number(this.listId)
|
this.listId = Number(this.listId)
|
||||||
|
|
||||||
// Make date objects from timestamps
|
// Make date objects from timestamps
|
||||||
|
|
|
@ -39,6 +39,8 @@ export default class TaskService extends AbstractService {
|
||||||
|
|
||||||
processModel(model) {
|
processModel(model) {
|
||||||
|
|
||||||
|
model.title = model.title?.trim()
|
||||||
|
|
||||||
// Ensure that listId is an int
|
// Ensure that listId is an int
|
||||||
model.listId = Number(model.listId)
|
model.listId = Number(model.listId)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue