feat: use async / await where it makes sense

This commit is contained in:
Dominik Pschenitschni 2021-10-11 19:37:20 +02:00
parent a776e1d2f3
commit bb94c1ba3a
No known key found for this signature in database
GPG key ID: B257AC0149F43A77
74 changed files with 1458 additions and 1662 deletions

View file

@ -54,6 +54,7 @@ export default defineComponent({
this.setupAccountDeletionVerification()
},
beforeCreate() {
// FIXME: async action in beforeCreate, might be not finished when component mounts
this.$store.dispatch('config/update')
.then(() => {
this.$store.dispatch('auth/checkAuth')
@ -88,28 +89,30 @@ export default defineComponent({
window.addEventListener('offline', () => this.$store.commit(ONLINE, navigator.onLine))
},
setupPasswortResetRedirect() {
if (typeof this.$route.query.userPasswordReset !== 'undefined') {
localStorage.removeItem('passwordResetToken') // Delete an eventually preexisting old token
if (typeof this.$route.query.userPasswordReset === 'undefined') {
return
}
localStorage.setItem('passwordResetToken', this.$route.query.userPasswordReset)
this.$router.push({name: 'user.password-reset.reset'})
}
},
setupEmailVerificationRedirect() {
if (typeof this.$route.query.userEmailConfirm !== 'undefined') {
localStorage.removeItem('emailConfirmToken') // Delete an eventually preexisting old token
if (typeof this.$route.query.userEmailConfirm === 'undefined') {
return
}
localStorage.setItem('emailConfirmToken', this.$route.query.userEmailConfirm)
this.$router.push({name: 'user.login'})
}
},
setupAccountDeletionVerification() {
if (typeof this.$route.query.accountDeletionConfirm !== 'undefined') {
async setupAccountDeletionVerification() {
if (typeof this.$route.query.accountDeletionConfirm === 'undefined') {
return
}
const accountDeletionService = new AccountDeleteService()
accountDeletionService.confirm(this.$route.query.accountDeletionConfirm)
.then(() => {
await accountDeletionService.confirm(this.$route.query.accountDeletionConfirm)
this.$message.success({message: this.$t('user.deletion.confirmSuccess')})
this.$store.dispatch('auth/refreshUserInfo')
})
}
},
},
})

View file

@ -195,6 +195,7 @@ export default {
},
},
beforeCreate() {
// FIXME: async action in beforeCreate, might be unfinished when component mounts
this.$store.dispatch('namespaces/loadNamespaces')
.then(namespaces => {
namespaces.forEach(n => {
@ -248,7 +249,8 @@ export default {
this.$store.commit('namespaces/setNamespaceById', newNamespace)
},
saveListPosition(e, namespaceIndex) {
async saveListPosition(e, namespaceIndex) {
const listsActive = this.activeLists[namespaceIndex]
const list = listsActive[e.newIndex]
const listBefore = listsActive[e.newIndex - 1] ?? null
@ -257,14 +259,15 @@ export default {
const position = calculateItemPosition(listBefore !== null ? listBefore.position : null, listAfter !== null ? listAfter.position : null)
try {
// create a copy of the list in order to not violate vuex mutations
this.$store.dispatch('lists/updateList', {
await this.$store.dispatch('lists/updateList', {
...list,
position,
})
.finally(() => {
} finally {
this.listUpdating[list.id] = false
})
}
},
},
}

View file

@ -233,7 +233,7 @@ export default {
// dom tree. If we're calling this right after setting this.preview it could be the images were
// not already made available.
// Some docs at https://stackoverflow.com/q/62865160/10924593
this.$nextTick(() => {
this.$nextTick(async () => {
const attachmentImage = document.getElementsByClassName('attachment-image')
if (attachmentImage) {
for (const img of attachmentImage) {
@ -254,11 +254,9 @@ export default {
this.attachmentService = new AttachmentService()
}
this.attachmentService.getBlobUrl(attachment)
.then(url => {
const url = await this.attachmentService.getBlobUrl(attachment)
img.src = url
this.loadedAttachments[cacheKey] = url
})
}
}

View file

@ -468,7 +468,7 @@ export default {
this.filters.done = true
}
},
prepareRelatedObjectFilter(kind, filterName = null, servicePrefix = null) {
async prepareRelatedObjectFilter(kind, filterName = null, servicePrefix = null) {
if (filterName === null) {
filterName = kind
}
@ -478,12 +478,11 @@ export default {
}
this.prepareSingleValue(filterName)
if (typeof this.filters[filterName] !== 'undefined' && this.filters[filterName] !== '') {
this[`${servicePrefix}Service`].getAll({}, {s: this.filters[filterName]})
.then(r => {
this[kind] = r
})
if (typeof this.filters[filterName] === 'undefined' || this.filters[filterName] === '') {
return
}
this[kind] = await this[`${servicePrefix}Service`].getAll({}, {s: this.filters[filterName]})
},
setDoneFilter() {
if (this.filters.done) {
@ -523,17 +522,16 @@ export default {
clear(kind) {
this[`found${kind}`] = []
},
find(kind, query) {
async find(kind, query) {
if (query === '') {
this.clear(kind)
}
this[`${kind}Service`].getAll({}, {s: query})
.then(response => {
const response = await this[`${kind}Service`].getAll({}, {s: query})
// Filter users from the results who are already assigned
this[`found${kind}`] = response.filter(({id}) => !includesById(this[kind], id))
})
},
add(kind, filterName) {
this.$nextTick(() => {

View file

@ -56,7 +56,7 @@ export default {
},
},
methods: {
loadBackground() {
async loadBackground() {
if (this.list === null || !this.list.backgroundInformation || this.backgroundLoading) {
return
}
@ -64,11 +64,11 @@ export default {
this.backgroundLoading = true
const listService = new ListService()
listService.background(this.list)
.then(b => {
this.background = b
})
.finally(() => this.backgroundLoading = false)
try {
this.background = await listService.background(this.list)
} finally {
this.backgroundLoading = false
}
},
toggleFavoriteList(list) {
// The favorites pseudo list is always favorite

View file

@ -141,43 +141,32 @@ export default {
}
},
methods: {
getAuthUrl() {
this.migrationService.getAuthUrl()
.then(r => {
this.authUrl = r.url
})
async getAuthUrl() {
const { url } = this.migrationService.getAuthUrl()
this.authUrl = url
},
migrate() {
async migrate() {
this.isMigrating = true
this.lastMigrationDate = null
this.message = ''
if (this.isFileMigrator) {
return this.migrateFile()
}
let migrationConfig = { code: this.migratorAuthCode }
this.migrationService.migrate({code: this.migratorAuthCode})
.then(r => {
this.message = r.message
this.$store.dispatch('namespaces/loadNamespaces')
})
.finally(() => {
this.isMigrating = false
})
},
migrateFile() {
if (this.isFileMigrator) {
if (this.$refs.uploadInput.files.length === 0) {
return
}
migrationConfig = this.$refs.uploadInput.files[0]
}
this.migrationService.migrate(this.$refs.uploadInput.files[0])
.then(r => {
this.message = r.message
this.$store.dispatch('namespaces/loadNamespaces')
})
.finally(() => {
try {
const { message } = await this.migrationService.migrate(migrationConfig)
this.message = message
return this.$store.dispatch('namespaces/loadNamespaces')
} finally {
this.isMigrating = false
})
}
},
},
}

View file

@ -89,27 +89,23 @@ export default {
this.unsubscribe()
}
},
subscribe() {
async subscribe() {
const subscription = new SubscriptionModel({
entity: this.entity,
entityId: this.entityId,
})
this.subscriptionService.create(subscription)
.then(() => {
await this.subscriptionService.create(subscription)
this.$emit('change', subscription)
this.$message.success({message: this.$t('task.subscription.subscribeSuccess', {entity: this.entity})})
})
},
unsubscribe() {
async unsubscribe() {
const subscription = new SubscriptionModel({
entity: this.entity,
entityId: this.entityId,
})
this.subscriptionService.delete(subscription)
.then(() => {
await this.subscriptionService.delete(subscription)
this.$emit('change', null)
this.$message.success({message: this.$t('task.subscription.unsubscribeSuccess', {entity: this.entity})})
})
},
},
}

View file

@ -93,11 +93,8 @@ export default {
closeWhenClickedOutside(e, this.$refs.popup, () => this.showNotifications = false)
}
},
loadNotifications() {
this.notificationService.getAll()
.then(r => {
this.allNotifications = r
})
async loadNotifications() {
this.allNotifications = await this.notificationService.getAll()
},
to(n, index) {
const to = {
@ -124,16 +121,13 @@ export default {
break
}
return () => {
return async () => {
if (to.name !== '') {
this.$router.push(to)
}
n.read = true
this.notificationService.update(n)
.then(r => {
this.allNotifications[index] = r
})
this.allNotifications[index] = await this.notificationService.update(n)
}
},
},

View file

@ -282,20 +282,17 @@ export default {
this.taskSearchTimeout = null
}
this.taskSearchTimeout = setTimeout(() => {
this.taskService.getAll({}, {s: query})
.then(r => {
r = r.map(t => {
this.taskSearchTimeout = setTimeout(async () => {
const r = await this.taskService.getAll({}, {s: query})
this.foundTasks = r.map(t => {
t.type = TYPE_TASK
const list = this.$store.getters['lists/getListById'](t.listId) === null ? null : this.$store.getters['lists/getListById'](t.listId)
const list = this.$store.getters['lists/getListById'](t.listId)
if (list !== null) {
t.title = `${t.title} (${list.title})`
}
return t
})
this.foundTasks = r
})
}, 150)
},
searchTeams() {
@ -318,15 +315,12 @@ export default {
this.teamSearchTimeout = null
}
this.teamSearchTimeout = setTimeout(() => {
this.teamService.getAll({}, {s: query})
.then(r => {
r = r.map(t => {
this.teamSearchTimeout = setTimeout(async () => {
const r = await this.teamService.getAll({}, {s: query})
this.foundTeams = r.map(t => {
t.title = t.name
return t
})
this.foundTeams = r
})
}, 150)
},
closeQuickActions() {
@ -378,22 +372,20 @@ export default {
break
}
},
newTask() {
async newTask() {
if (this.currentList === null) {
return
}
this.$store.dispatch('tasks/createNewTask', {
const task = await this.$store.dispatch('tasks/createNewTask', {
title: this.query,
listId: this.currentList.id,
})
.then(r => {
this.$message.success({message: this.$t('task.createSuccess')})
this.$router.push({name: 'task.detail', params: {id: r.id}})
this.$router.push({name: 'task.detail', params: {id: task.id}})
this.closeQuickActions()
})
},
newList() {
async newList() {
if (this.currentList === null) {
return
}
@ -402,33 +394,27 @@ export default {
title: this.query,
namespaceId: this.currentList.namespaceId,
})
this.$store.dispatch('lists/createList', newList)
.then(r => {
const list = await this.$store.dispatch('lists/createList', newList)
this.$message.success({message: this.$t('list.create.createdSuccess')})
this.$router.push({name: 'list.index', params: {listId: r.id}})
this.$router.push({name: 'list.index', params: {listId: list.id}})
this.closeQuickActions()
})
},
newNamespace() {
async newNamespace() {
const newNamespace = new NamespaceModel({title: this.query})
this.$store.dispatch('namespaces/createNamespace', newNamespace)
.then(() => {
await this.$store.dispatch('namespaces/createNamespace', newNamespace)
this.$message.success({message: this.$t('namespace.create.success')})
this.closeQuickActions()
})
},
newTeam() {
async newTeam() {
const newTeam = new TeamModel({name: this.query})
this.teamService.create(newTeam)
.then(r => {
const team = await this.teamService.create(newTeam)
this.$router.push({
name: 'teams.edit',
params: {id: r.id},
params: {id: team.id},
})
this.$message.success({message: this.$t('team.create.success')})
this.closeQuickActions()
})
},
select(parentIndex, index) {

View file

@ -215,50 +215,41 @@ export default {
frontendUrl: (state) => state.config.frontendUrl,
}),
methods: {
load(listId) {
async load(listId) {
// If listId == 0 the list on the calling component wasn't already loaded, so we just bail out here
if (listId === 0) {
return
}
this.linkShareService
.getAll({listId})
.then((r) => {
this.linkShares = r
})
this.linkShares = await this.linkShareService.getAll({listId})
},
add(listId) {
async add(listId) {
const newLinkShare = new LinkShareModel({
right: this.selectedRight,
listId,
name: this.name,
password: this.password,
})
this.linkShareService
.create(newLinkShare)
.then(() => {
await this.linkShareService.create(newLinkShare)
this.selectedRight = rights.READ
this.name = ''
this.password = ''
this.showNewForm = false
this.$message.success({message: this.$t('list.share.links.createSuccess')})
this.load(listId)
})
await this.load(listId)
},
remove(listId) {
async remove(listId) {
const linkshare = new LinkShareModel({
id: this.linkIdToDelete,
listId,
})
this.linkShareService
.delete(linkshare)
.then(() => {
try {
await this.linkShareService.delete(linkshare)
this.$message.success({message: this.$t('list.share.links.deleteSuccess')})
this.load(listId)
})
.finally(() => {
await this.load(listId)
} finally {
this.showDeleteModal = false
})
}
},
copy,
getShareLink(hash) {

View file

@ -272,26 +272,21 @@ export default {
this.load()
},
methods: {
load() {
this.stuffService
.getAll(this.stuffModel)
.then((r) => {
this.sharables = r
r.forEach((s) =>
async load() {
this.sharables = await this.stuffService.getAll(this.stuffModel)
this.sharables.forEach((s) =>
this.selectedRight[s.id] = s.right,
)
})
},
deleteSharable() {
async deleteSharable() {
if (this.shareType === 'user') {
this.stuffModel.userId = this.sharable.username
} else if (this.shareType === 'team') {
this.stuffModel.teamId = this.sharable.id
}
this.stuffService
.delete(this.stuffModel)
.then(() => {
await this.stuffService.delete(this.stuffModel)
this.showDeleteModal = false
for (const i in this.sharables) {
if (
@ -302,9 +297,9 @@ export default {
}
}
this.$message.success({message: this.$t('list.share.userTeam.removeSuccess', {type: this.shareTypeName, sharable: this.sharableName})})
})
},
add(admin) {
async add(admin) {
if (admin === null) {
admin = false
}
@ -319,14 +314,12 @@ export default {
this.stuffModel.teamId = this.sharable.id
}
this.stuffService
.create(this.stuffModel)
.then(() => {
await this.stuffService.create(this.stuffModel)
this.$message.success({message: this.$t('list.share.userTeam.addedSuccess', {type: this.shareTypeName})})
this.load()
})
await this.load()
},
toggleType(sharable) {
async toggleType(sharable) {
if (
this.selectedRight[sharable.id] !== rights.ADMIN &&
this.selectedRight[sharable.id] !== rights.READ &&
@ -342,9 +335,7 @@ export default {
this.stuffModel.teamId = sharable.id
}
this.stuffService
.update(this.stuffModel)
.then((r) => {
const r = await this.stuffService.update(this.stuffModel)
for (const i in this.sharables) {
if (
(this.sharables[i].username ===
@ -357,20 +348,17 @@ export default {
}
}
this.$message.success({message: this.$t('list.share.userTeam.updatedSuccess', {type: this.shareTypeName})})
})
},
find(query) {
async find(query) {
if (query === '') {
this.clearAll()
return
}
this.searchService
.getAll({}, {s: query})
.then((response) => {
this.found = response
})
this.found = await this.searchService.getAll({}, {s: query})
},
clearAll() {
this.found = []
},

View file

@ -82,7 +82,7 @@ export default {
this.initialTextAreaHeight = this.$refs.newTaskInput.scrollHeight + INPUT_BORDER_PX
},
methods: {
addTask() {
async addTask() {
if (this.newTaskTitle === '') {
this.errorMessage = this.$t('list.create.addTitleRequired')
return
@ -93,37 +93,31 @@ export default {
return
}
const newTasks = []
this.newTaskTitle.split(/[\r\n]+/).forEach(t => {
const newTasks = this.newTaskTitle.split(/[\r\n]+/).map(async t => {
const title = cleanupTitle(t)
if (title === '') {
return
}
newTasks.push(
this.$store.dispatch('tasks/createNewTask', {
const task = await this.$store.dispatch('tasks/createNewTask', {
title: this.newTaskTitle,
listId: this.$store.state.auth.settings.defaultListId,
position: this.defaultPosition,
})
.then(task => {
this.$emit('taskAdded', task)
return task
}),
)
})
return Promise.all(newTasks)
.then(() => {
try {
await Promise.all(newTasks)
this.newTaskTitle = ''
})
.catch(e => {
} catch(e) {
if (e.message === 'NO_LIST') {
this.errorMessage = this.$t('list.create.addListRequired')
return
}
throw e
})
}
},
handleEnter(e) {
// when pressing shift + enter we want to continue as we normally would. Otherwise, we want to create

View file

@ -134,14 +134,10 @@ export default {
this.editorActive = false
this.$nextTick(() => (this.editorActive = true))
},
editTaskSubmit() {
this.taskService
.update(this.taskEditTask)
.then((r) => {
this.taskEditTask = r
async editTaskSubmit() {
this.taskEditTask = await this.taskService.update(this.taskEditTask)
this.initTaskFields()
this.$message.success({message: this.$t('task.detail.updateSuccess')})
})
},
},
}

View file

@ -297,30 +297,26 @@ export default {
console.debug('prepareGanttDays; years:', years)
this.days = years
},
parseTasks() {
this.setDates()
this.loadTasks()
},
loadTasks() {
async loadTasks() {
this.theTasks = []
this.tasksWithoutDates = []
const getAllTasks = (page = 1) => {
return this.taskCollectionService
.getAll({listId: this.listId}, this.params, page)
.then((tasks) => {
const getAllTasks = async (page = 1) => {
const tasks = await this.taskCollectionService.getAll({listId: this.listId}, this.params, page)
if (page < this.taskCollectionService.totalPages) {
return getAllTasks(page + 1).then((nextTasks) => {
const nextTasks = await getAllTasks(page + 1)
return tasks.concat(nextTasks)
})
} else {
}
return tasks
}
})
}
getAllTasks()
.then((tasks) => {
const tasks = await getAllTasks()
this.theTasks = tasks
.filter((t) => {
if (t.startDate === null && !t.done) {
@ -331,15 +327,12 @@ export default {
t.endDate <= this.endDate
)
})
.map((t) => {
return this.addGantAttributes(t)
})
.map((t) => this.addGantAttributes(t))
.sort(function (a, b) {
if (a.startDate < b.startDate) return -1
if (a.startDate > b.startDate) return 1
return 0
})
})
},
addGantAttributes(t) {
if (typeof t.durationDays !== 'undefined' && typeof t.offsetDays !== 'undefined') {
@ -351,7 +344,7 @@ export default {
t.offsetDays = Math.floor((t.startDate - this.startDate) / 1000 / 60 / 60 / 24)
return t
},
resizeTask(taskDragged, newRect) {
async resizeTask(taskDragged, newRect) {
if (this.isTaskEdit) {
return
}
@ -392,9 +385,7 @@ export default {
offsetDays: newTask.offsetDays,
}
this.taskService
.update(newTask)
.then(r => {
const r = await this.taskService.update(newTask)
r.endDate = ganttData.endDate
r.durationDays = ganttData.durationDays
r.offsetDays = ganttData.offsetDays
@ -416,7 +407,6 @@ export default {
}
}
}
})
},
editTask(task) {
this.taskToEdit = task
@ -436,7 +426,7 @@ export default {
this.$nextTick(() => (this.newTaskFieldActive = false))
}
},
addNewTask() {
async addNewTask() {
if (!this.newTaskFieldActive) {
return
}
@ -444,13 +434,10 @@ export default {
title: this.newTaskTitle,
listId: this.listId,
})
this.taskService
.create(task)
.then((r) => {
const r = await this.taskService.create(task)
this.tasksWithoutDates.push(this.addGantAttributes(r))
this.newTaskTitle = ''
this.hideCrateNewTask()
})
},
formatYear(date) {
return this.format(date, 'MMMM, yyyy')

View file

@ -38,7 +38,7 @@ export default {
'$route.path': 'loadTasksOnSavedFilter',
},
methods: {
loadTasks(
async loadTasks(
page,
search = '',
params = null,
@ -76,14 +76,9 @@ export default {
}
this.tasks = []
this.taskCollectionService.getAll(list, params, page)
.then(r => {
this.tasks = r
this.tasks = await this.taskCollectionService.getAll(list, params, page)
this.currentPage = page
this.loadedList = JSON.parse(JSON.stringify(currentList))
})
},
loadTasksForPage(e) {

View file

@ -218,21 +218,19 @@ export default {
uploadFiles(files) {
uploadFiles(this.attachmentService, this.taskId, files)
},
deleteAttachment() {
this.attachmentService
.delete(this.attachmentToDelete)
.then((r) => {
async deleteAttachment() {
try {
const r = await this.attachmentService.delete(this.attachmentToDelete)
this.$store.commit(
'attachments/removeById',
this.attachmentToDelete.id,
)
this.$message.success(r)
})
.finally(() => {
} finally{
this.showDeleteModal = false
})
}
},
viewOrDownload(attachment) {
async viewOrDownload(attachment) {
if (
attachment.file.name.endsWith('.jpg') ||
attachment.file.name.endsWith('.png') ||
@ -240,9 +238,7 @@ export default {
attachment.file.name.endsWith('.gif')
) {
this.showImageModal = true
this.attachmentService.getBlobUrl(attachment).then((url) => {
this.attachmentImageBlobUrl = url
})
this.attachmentImageBlobUrl = await this.attachmentService.getBlobUrl(attachment)
} else {
this.downloadAttachment(attachment)
}

View file

@ -134,9 +134,9 @@
<transition name="modal">
<modal
@close="showDeleteModal = false"
@submit="deleteComment()"
v-if="showDeleteModal"
@close="showDeleteModal = false"
@submit="() => deleteComment(commentToDelete)"
>
<template #header><span>{{ $t('task.comment.delete') }}</span></template>
@ -186,7 +186,6 @@ export default {
taskCommentService: new TaskCommentService(),
newComment: new TaskCommentModel(),
editorActive: true,
actions: {},
saved: null,
saving: null,
@ -195,40 +194,46 @@ export default {
},
watch: {
taskId: {
handler(taskId) {
if (!this.enabled) {
return
}
this.loadComments()
this.newComment.taskId = taskId
this.commentEdit.taskId = taskId
this.commentToDelete.taskId = taskId
},
handler: 'loadComments',
immediate: true,
},
canWrite() {
this.makeActions()
},
},
computed: mapState({
computed: {
...mapState({
userAvatar: state => state.auth.info.getAvatarUrl(48),
enabled: state => state.config.taskCommentsEnabled,
}),
actions() {
if (!this.canWrite) {
return {}
}
return Object.fromEntries(this.comments.map((c) => ([
c.id,
[{
action: () => this.toggleDelete(c.id),
title: this.$t('misc.delete'),
}],
])))
},
},
methods: {
attachmentUpload(...args) {
return uploadFile(this.taskId, ...args)
},
loadComments() {
this.taskCommentService
.getAll({taskId: this.taskId})
.then(r => {
this.comments = r
this.makeActions()
})
async loadComments(taskId) {
if (!this.enabled) {
return
}
this.newComment.taskId = taskId
this.commentEdit.taskId = taskId
this.commentToDelete.taskId = taskId
this.comments = await this.taskCommentService.getAll({taskId})
},
addComment() {
async addComment() {
if (this.newComment.comment === '') {
return
}
@ -242,27 +247,27 @@ export default {
this.$nextTick(() => (this.editorActive = true))
this.creating = true
this.taskCommentService
.create(this.newComment)
.then((r) => {
this.comments.push(r)
try {
const comment = await this.taskCommentService.create(this.newComment)
this.comments.push(comment)
this.newComment.comment = ''
this.$message.success({message: this.$t('task.comment.addedSuccess')})
this.makeActions()
})
.finally(() => {
} finally {
this.creating = false
})
}
},
toggleEdit(comment) {
this.isCommentEdit = !this.isCommentEdit
this.commentEdit = comment
},
toggleDelete(commentId) {
this.showDeleteModal = !this.showDeleteModal
this.commentToDelete.id = commentId
},
editComment() {
async editComment() {
if (this.commentEdit.comment === '') {
return
}
@ -270,48 +275,30 @@ export default {
this.saving = this.commentEdit.id
this.commentEdit.taskId = this.taskId
this.taskCommentService
.update(this.commentEdit)
.then((r) => {
try {
const comment = this.taskCommentService.update(this.commentEdit)
for (const c in this.comments) {
if (this.comments[c].id === this.commentEdit.id) {
this.comments[c] = r
this.comments[c] = comment
}
}
this.saved = this.commentEdit.id
setTimeout(() => {
this.saved = null
}, 2000)
})
.finally(() => {
} finally {
this.isCommentEdit = false
this.saving = null
})
}
},
deleteComment() {
this.taskCommentService
.delete(this.commentToDelete)
.then(() => {
for (const a in this.comments) {
if (this.comments[a].id === this.commentToDelete.id) {
this.comments.splice(a, 1)
}
}
})
.finally(() => {
async deleteComment(commentToDelete) {
try {
await this.taskCommentService.delete(commentToDelete)
const index = this.comments.findIndex(({id}) => id === commentToDelete.id)
this.comments.splice(index, 1)
} finally {
this.showDeleteModal = false
})
},
makeActions() {
if (this.canWrite) {
this.comments.forEach((c) => {
this.actions[c.id] = [
{
action: () => this.toggleDelete(c.id),
title: this.$t('misc.delete'),
},
]
})
}
},
},

View file

@ -112,7 +112,8 @@ export default {
this.dueDate = this.dueDate.setDate(this.dueDate.getDate() + days)
this.updateDueDate()
},
updateDueDate() {
async updateDueDate() {
if (!this.dueDate) {
return
}
@ -122,13 +123,10 @@ export default {
}
this.task.dueDate = new Date(this.dueDate)
this.taskService
.update(this.task)
.then((r) => {
this.lastValue = r.dueDate
this.task = r
this.$emit('update:modelValue', r)
})
const task = await this.taskService.update(this.task)
this.lastValue = task.dueDate
this.task = task
this.$emit('update:modelValue', task)
},
},
}

View file

@ -71,21 +71,19 @@ export default {
},
},
methods: {
save() {
async save() {
this.saving = true
this.$store.dispatch('tasks/update', this.task)
.then(t => {
this.task = t
this.$emit('update:modelValue', t)
try {
this.task = await this.$store.dispatch('tasks/update', this.task)
this.$emit('update:modelValue', this.task)
this.saved = true
setTimeout(() => {
this.saved = false
}, 2000)
})
.finally(() => {
} finally {
this.saving = false
})
}
},
},
}

View file

@ -78,16 +78,15 @@ export default {
},
},
methods: {
addAssignee(user) {
this.$store.dispatch('tasks/addAssignee', {user: user, taskId: this.taskId})
.then(() => {
async addAssignee(user) {
await this.$store.dispatch('tasks/addAssignee', {user: user, taskId: this.taskId})
this.$emit('update:modelValue', this.assignees)
this.$message.success({message: this.$t('task.assignee.assignSuccess')})
})
},
removeAssignee(user) {
this.$store.dispatch('tasks/removeAssignee', {user: user, taskId: this.taskId})
.then(() => {
async removeAssignee(user) {
await this.$store.dispatch('tasks/removeAssignee', {user: user, taskId: this.taskId})
// Remove the assignee from the list
for (const a in this.assignees) {
if (this.assignees[a].id === user.id) {
@ -95,23 +94,24 @@ export default {
}
}
this.$message.success({message: this.$t('task.assignee.unassignSuccess')})
})
},
findUser(query) {
async findUser(query) {
if (query === '') {
this.clearAllFoundUsers()
return
}
this.listUserService.getAll({listId: this.listId}, {s: query})
.then(response => {
const response = await this.listUserService.getAll({listId: this.listId}, {s: query})
// Filter the results to not include users who are already assigned
this.foundUsers = response.filter(({id}) => !includesById(this.assignees, id))
})
},
clearAllFoundUsers() {
this.foundUsers = []
},
focus() {
this.$refs.multiselect.focus()
},

View file

@ -93,7 +93,8 @@ export default {
findLabel(query) {
this.query = query
},
addLabel(label, showNotification = true) {
async addLabel(label, showNotification = true) {
const bubble = () => {
this.$emit('update:modelValue', this.labels)
this.$emit('change', this.labels)
@ -104,15 +105,14 @@ export default {
return
}
this.$store.dispatch('tasks/addLabel', {label: label, taskId: this.taskId})
.then(() => {
await this.$store.dispatch('tasks/addLabel', {label: label, taskId: this.taskId})
bubble()
if (showNotification) {
this.$message.success({message: this.$t('task.label.addSuccess')})
}
})
},
removeLabel(label) {
async removeLabel(label) {
const removeFromState = () => {
for (const l in this.labels) {
if (this.labels[l].id === label.id) {
@ -128,24 +128,21 @@ export default {
return
}
this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
.then(() => {
await this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
removeFromState()
this.$message.success({message: this.$t('task.label.removeSuccess')})
})
},
createAndAddLabel(title) {
async createAndAddLabel(title) {
if (this.taskId === 0) {
return
}
const newLabel = new LabelModel({title: title})
this.$store.dispatch('labels/createLabel', newLabel)
.then(r => {
this.addLabel(r, false)
this.labels.push(r)
const label = await this.$store.dispatch('labels/createLabel', newLabel)
this.addLabel(label, false)
this.labels.push(label)
this.$message.success({message: this.$t('task.label.addCreateSuccess')})
})
},
},

View file

@ -58,7 +58,7 @@ export default {
emits: ['update:modelValue'],
methods: {
save(title) {
async 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
@ -74,17 +74,17 @@ export default {
title,
}
this.$store.dispatch('tasks/update', newTask)
.then((task) => {
try {
const task = await this.$store.dispatch('tasks/update', newTask)
this.$emit('update:modelValue', task)
this.showSavedMessage = true
setTimeout(() => {
this.showSavedMessage = false
}, 2000)
})
.finally(() => {
}
finally {
this.saving = false
})
}
},
},
}

View file

@ -6,9 +6,9 @@
'has-light-text': !colorIsDark(task.hexColor) && task.hexColor !== `#${task.defaultColor}` && task.hexColor !== task.defaultColor,
}"
:style="{'background-color': task.hexColor !== '#' && task.hexColor !== `#${task.defaultColor}` ? task.hexColor : false}"
@click.ctrl="() => markTaskAsDone(task)"
@click.ctrl="() => toggleTaskDone(task)"
@click.exact="() => $router.push({ name: 'task.kanban.detail', params: { id: task.id } })"
@click.meta="() => markTaskAsDone(task)"
@click.meta="() => toggleTaskDone(task)"
class="task loader-container draggable"
>
<span class="task-id">
@ -93,20 +93,19 @@ export default {
},
},
methods: {
markTaskAsDone(task) {
async toggleTaskDone(task) {
this.loadingInternal = true
this.$store.dispatch('tasks/update', {
try {
await this.$store.dispatch('tasks/update', {
...task,
done: !task.done,
})
.then(() => {
if (task.done) {
playPop()
}
})
.finally(() => {
} finally {
this.loadingInternal = false
})
}
},
},
}

View file

@ -50,25 +50,25 @@ export default {
},
},
methods: {
findLists(query) {
async findLists(query) {
if (query === '') {
this.clearAll()
return
}
this.listSerivce.getAll({}, {s: query})
.then(response => {
this.foundLists = response
})
this.foundLists = await this.listSerivce.getAll({}, {s: query})
},
clearAll() {
this.foundLists = []
},
select(list) {
this.list = list
this.$emit('selected', list)
this.$emit('update:modelValue', list)
},
namespace(namespaceId) {
const namespace = this.$store.getters['namespaces/getNamespaceById'](namespaceId)
if (namespace !== null) {

View file

@ -188,14 +188,14 @@ export default {
async findTasks(query) {
this.foundTasks = await this.taskService.getAll({}, {s: query})
},
addTaskRelation() {
let rel = new TaskRelationModel({
async addTaskRelation() {
const rel = new TaskRelationModel({
taskId: this.taskId,
otherTaskId: this.newTaskRelationTask.id,
relationKind: this.newTaskRelationKind,
})
this.taskRelationService.create(rel)
.then(() => {
await this.taskRelationService.create(rel)
if (!this.relatedTasks[this.newTaskRelationKind]) {
this.relatedTasks[this.newTaskRelationKind] = []
}
@ -206,40 +206,41 @@ export default {
setTimeout(() => {
this.saved = false
}, 2000)
})
},
removeTaskRelation() {
async removeTaskRelation() {
const rel = new TaskRelationModel({
relationKind: this.relationToDelete.relationKind,
taskId: this.taskId,
otherTaskId: this.relationToDelete.otherTaskId,
})
this.taskRelationService.delete(rel)
.then(() => {
Object.keys(this.relatedTasks).forEach(relationKind => {
for (const t in this.relatedTasks[relationKind]) {
if (this.relatedTasks[relationKind][t].id === this.relationToDelete.otherTaskId && relationKind === this.relationToDelete.relationKind) {
try {
await this.taskRelationService.delete(rel)
Object.entries(this.relatedTasks).some(([relationKind, t]) => {
const found = this.relatedTasks[relationKind][t].id === this.relationToDelete.otherTaskId &&
relationKind === this.relationToDelete.relationKind
if (!found) return false
this.relatedTasks[relationKind].splice(t, 1)
}
}
return true
})
this.saved = true
setTimeout(() => {
this.saved = false
}, 2000)
})
.finally(() => {
} finally {
this.showDeleteModal = false
})
}
},
createAndRelateTask(title) {
async createAndRelateTask(title) {
const newTask = new TaskModel({title: title, listId: this.listId})
this.taskService.create(newTask)
.then(r => {
this.newTaskRelationTask = r
this.addTaskRelation()
})
this.newTaskRelationTask = await this.taskService.create(newTask)
await this.addTaskRelation()
},
relationKindTitle(kind, length) {
return this.$tc(`task.relation.kinds.${kind}`, length)
},

View file

@ -166,50 +166,47 @@ export default {
},
},
methods: {
markAsDone(checked) {
const updateFunc = () => {
this.taskService.update(this.task)
.then(t => {
async markAsDone(checked) {
const updateFunc = async () => {
const task = await this.taskService.update(this.task)
if (this.task.done) {
playPop()
}
this.task = t
this.$emit('task-updated', t)
this.task = task
this.$emit('task-updated', task)
this.$message.success({
message: this.task.done ?
this.$t('task.doneSuccess') :
this.$t('task.undoneSuccess'),
}, [{
title: 'Undo',
callback: () => {
callback() {
this.task.done = !this.task.done
this.markAsDone(!checked)
},
}])
})
}
if (checked) {
setTimeout(updateFunc, 300) // Delay it to show the animation when marking a task as done
} else {
updateFunc() // Don't delay it when un-marking it as it doesn't have an animation the other way around
await updateFunc() // Don't delay it when un-marking it as it doesn't have an animation the other way around
}
},
toggleFavorite() {
async toggleFavorite() {
this.task.isFavorite = !this.task.isFavorite
this.taskService.update(this.task)
.then(t => {
this.task = t
this.$emit('task-updated', t)
this.task = await this.taskService.update(this.task)
this.$emit('task-updated', this.task)
this.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
})
},
hideDeferDueDatePopup(e) {
if (this.showDefer) {
if (!this.showDefer) {
return
}
closeWhenClickedOutside(e, this.$refs.deferDueDate.$el, () => {
this.showDefer = false
})
}
},
},
}

View file

@ -91,34 +91,34 @@ export default {
const { avatarProvider } = await this.avatarService.get({})
this.avatarProvider = avatarProvider
},
updateAvatarStatus() {
async updateAvatarStatus() {
const avatarStatus = new AvatarModel({avatarProvider: this.avatarProvider})
this.avatarService.update(avatarStatus)
.then(() => {
await this.avatarService.update(avatarStatus)
this.$message.success({message: this.$t('user.settings.avatar.statusUpdateSuccess')})
this.$store.commit('auth/reloadAvatar')
})
},
uploadAvatar() {
async uploadAvatar() {
this.loading = true
const {canvas} = this.$refs.cropper.getResult()
if (canvas) {
canvas.toBlob(blob => {
this.avatarService.create(blob)
.then(() => {
if (!canvas) {
this.loading = false
return
}
try {
const blob = await new Promise(resolve => canvas.toBlob(blob => resolve(blob)))
await this.avatarService.create(blob)
this.$message.success({message: this.$t('user.settings.avatar.setSuccess')})
this.$store.commit('auth/reloadAvatar')
})
.finally(() => {
} finally {
this.loading = false
this.isCropAvatar = false
})
})
} else {
this.loading = false
}
},
cropAvatar() {
const avatar = this.$refs.avatarUploadInput.files

View file

@ -43,27 +43,22 @@ export default {
name: 'data-export',
data() {
return {
dataExportService: DataExportService,
dataExportService: new DataExportService(),
password: '',
errPasswordRequired: false,
}
},
created() {
this.dataExportService = new DataExportService()
},
methods: {
requestDataExport() {
async requestDataExport() {
if (this.password === '') {
this.errPasswordRequired = true
this.$refs.passwordInput.focus()
return
}
this.dataExportService.request(this.password)
.then(() => {
await this.dataExportService.request(this.password)
this.$message.success({message: this.$t('user.export.success')})
this.password = ''
})
},
},
}

View file

@ -101,32 +101,29 @@ export default {
deletionScheduledAt: state => parseDateOrNull(state.auth.info.deletionScheduledAt),
}),
methods: {
deleteAccount() {
async deleteAccount() {
if (this.password === '') {
this.errPasswordRequired = true
this.$refs.passwordInput.focus()
return
}
this.accountDeleteService.request(this.password)
.then(() => {
await this.accountDeleteService.request(this.password)
this.$message.success({message: this.$t('user.deletion.requestSuccess')})
this.password = ''
})
},
cancelDeletion() {
async cancelDeletion() {
if (this.password === '') {
this.errPasswordRequired = true
this.$refs.passwordInput.focus()
return
}
this.accountDeleteService.cancel(this.password)
.then(() => {
await this.accountDeleteService.cancel(this.password)
this.$message.success({message: this.$t('user.deletion.scheduledCancelSuccess')})
this.$store.dispatch('auth/refreshUserInfo')
this.password = ''
})
},
},
}

View file

@ -11,24 +11,22 @@ export function uploadFile(taskId: number, file: FileModel, onSuccess: () => Fun
return uploadFiles(attachmentService, taskId, files, onSuccess)
}
export function uploadFiles(attachmentService: AttachmentService, taskId: number, files: FileModel[], onSuccess : Function = () => {}) {
export async function uploadFiles(attachmentService: AttachmentService, taskId: number, files: FileModel[], onSuccess : Function = () => {}) {
const attachmentModel = new AttachmentModel({taskId})
attachmentService.create(attachmentModel, files)
.then(r => {
console.debug(`Uploaded attachments for task ${taskId}, response was`, r)
if (r.success !== null) {
r.success.forEach((attachment: AttachmentModel) => {
const response = await attachmentService.create(attachmentModel, files)
console.debug(`Uploaded attachments for task ${taskId}, response was`, response)
response.success?.map((attachment: AttachmentModel) => {
store.dispatch('tasks/addTaskAttachment', {
taskId,
attachment,
})
onSuccess(generateAttachmentUrl(taskId, attachment.id))
})
if (response.errors !== null) {
throw Error(response.errors)
}
if (r.errors !== null) {
throw Error(r.errors)
}
})
}
export function generateAttachmentUrl(taskId: number, attachmentId: number) : any {

View file

@ -41,19 +41,19 @@ export const removeToken = () => {
* Refreshes an auth token while ensuring it is updated everywhere.
* @returns {Promise<AxiosResponse<any>>}
*/
export const refreshToken = (persist: boolean): Promise<AxiosResponse> => {
export async function refreshToken(persist: boolean): Promise<AxiosResponse> {
const HTTP = HTTPFactory()
return HTTP.post('user/token', null, {
try {
const response = await HTTP.post('user/token', null, {
headers: {
Authorization: `Bearer ${getToken()}`,
},
})
.then(r => {
saveToken(r.data.token, persist)
return r
})
.catch(e => {
saveToken(response.data.token, persist)
return response
} catch(e) {
throw new Error('Error renewing token: ', { cause: e })
})
}
}

View file

@ -24,15 +24,11 @@ export default class TaskModel extends AbstractModel {
this.endDate = parseDateOrNull(this.endDate)
this.doneAt = parseDateOrNull(this.doneAt)
this.reminderDates = this.reminderDates.map(d => new Date(d))
// Cancel all scheduled notifications for this task to be sure to only have available notifications
this.cancelScheduledNotifications()
.then(() => {
this.reminderDates = this.reminderDates.map(d => {
d = new Date(d)
this.cancelScheduledNotifications().then(() => {
// Every time we see a reminder, we schedule a notification for it
this.scheduleNotification(d)
return d
})
this.reminderDates.forEach(d => this.scheduleNotification(d))
})
// Parse the repeat after into something usable
@ -218,6 +214,7 @@ export default class TaskModel extends AbstractModel {
}
// Register the actual notification
try {
registration.showNotification('Vikunja Reminder', {
tag: `vikunja-task-${this.id}`, // Group notifications by task id so we're only showing one notification per task
body: this.title,
@ -233,12 +230,10 @@ export default class TaskModel extends AbstractModel {
},
],
})
.then(() => {
console.debug('Notification scheduled for ' + date)
})
.catch(e => {
console.debug('Error scheduling notification', e)
})
} catch(e) {
throw new Error('Error scheduling notification', e)
}
}
}

View file

@ -285,32 +285,30 @@ export default class AbstractService {
* @param params
* @returns {Q.Promise<unknown>}
*/
getM(url, model = {}, params = {}) {
async getM(url, model = {}, params = {}) {
const cancel = this.setLoading()
model = this.beforeGet(model)
const finalUrl = this.getReplacedRoute(url, model)
return this.http.get(finalUrl, {params})
.then(response => {
try {
const response = await this.http.get(finalUrl, {params})
const result = this.modelGetFactory(response.data)
result.maxRight = Number(response.headers['x-max-right'])
return result
})
.finally(() => {
} finally {
cancel()
})
}
}
getBlobUrl(url, method = 'GET', data = {}) {
return this.http({
async getBlobUrl(url, method = 'GET', data = {}) {
const response = await this.http({
url: url,
method: method,
responseType: 'blob',
data: data,
}).then(response => {
return window.URL.createObjectURL(new Blob([response.data]))
})
return window.URL.createObjectURL(new Blob([response.data]))
}
/**
@ -321,7 +319,7 @@ export default class AbstractService {
* @param page The page to get
* @returns {Q.Promise<any>}
*/
getAll(model = {}, params = {}, page = 1) {
async getAll(model = {}, params = {}, page = 1) {
if (this.paths.getAll === '') {
throw new Error('This model is not able to get data.')
}
@ -332,22 +330,22 @@ export default class AbstractService {
model = this.beforeGet(model)
const finalUrl = this.getReplacedRoute(this.paths.getAll, model)
return this.http.get(finalUrl, {params: params})
.then(response => {
try {
const response = await this.http.get(finalUrl, {params: params})
this.resultCount = Number(response.headers['x-pagination-result-count'])
this.totalPages = Number(response.headers['x-pagination-total-pages'])
if (response.data === null) {
return []
}
if (Array.isArray(response.data)) {
return response.data.map(entry => this.modelGetAllFactory(entry))
}
if (response.data === null) {
return []
}
return this.modelGetAllFactory(response.data)
})
.finally(() => {
} finally {
cancel()
})
}
}
/**
@ -355,7 +353,7 @@ export default class AbstractService {
* @param model
* @returns {Promise<any | never>}
*/
create(model) {
async create(model) {
if (this.paths.create === '') {
throw new Error('This model is not able to create data.')
}
@ -363,17 +361,16 @@ export default class AbstractService {
const cancel = this.setLoading()
const finalUrl = this.getReplacedRoute(this.paths.create, model)
return this.http.put(finalUrl, model)
.then(response => {
try {
const response = await this.http.put(finalUrl, model)
const result = this.modelCreateFactory(response.data)
if (typeof model.maxRight !== 'undefined') {
result.maxRight = model.maxRight
}
return result
})
.finally(() => {
} finally {
cancel()
})
}
}
/**
@ -383,20 +380,19 @@ export default class AbstractService {
* @param model
* @returns {Q.Promise<unknown>}
*/
post(url, model) {
async post(url, model) {
const cancel = this.setLoading()
return this.http.post(url, model)
.then(response => {
try {
const response = await this.http.post(url, model)
const result = this.modelUpdateFactory(response.data)
if (typeof model.maxRight !== 'undefined') {
result.maxRight = model.maxRight
}
return result
})
.finally(() => {
} finally {
cancel()
})
}
}
/**
@ -465,11 +461,12 @@ export default class AbstractService {
* @param formData
* @returns {Q.Promise<unknown>}
*/
uploadFormData(url, formData) {
async uploadFormData(url, formData) {
console.log(formData, formData._boundary)
const cancel = this.setLoading()
return this.http.put(
try {
const response = await this.http.put(
url,
formData,
{
@ -482,10 +479,10 @@ export default class AbstractService {
},
},
)
.then(response => this.modelCreateFactory(response.data))
.finally(() => {
this.modelCreateFactory(response.data)
} finally {
this.uploadProgress = 0
cancel()
})
}
}
}

View file

@ -18,14 +18,12 @@ export default class BackgroundUnsplashService extends AbstractService {
return new ListModel(data)
}
thumb(model) {
return this.http({
async thumb(model) {
const response = await this.http({
url: `/backgrounds/unsplash/images/${model.id}/thumb`,
method: 'GET',
responseType: 'blob',
})
.then(response => {
return window.URL.createObjectURL(new Blob([response.data]))
})
}
}

View file

@ -6,10 +6,13 @@ export default class DataExportService extends AbstractService {
return this.post('/user/export/request', {password: password})
}
download(password) {
async download(password) {
const clear = this.setLoading()
return this.getBlobUrl('/user/export/download', 'POST', {password})
.then(url => downloadBlob(url, 'vikunja-export.zip'))
.finally(() => clear())
try {
const url = await this.getBlobUrl('/user/export/download', 'POST', {password})
downloadBlob(url, 'vikunja-export.zip')
} finally {
clear()
}
}
}

View file

@ -44,28 +44,27 @@ export default class ListService extends AbstractService {
return super.update(newModel)
}
background(list) {
async background(list) {
if (list.background === null) {
return ''
}
return this.http({
const response = await this.http({
url: `/lists/${list.id}/background`,
method: 'GET',
responseType: 'blob',
})
.then(response => {
return window.URL.createObjectURL(new Blob([response.data]))
})
}
removeBackground(list) {
async removeBackground(list) {
const cancel = this.setLoading()
return this.http.delete(`/lists/${list.id}/background`, list)
.then(response => response.data)
.finally(() => {
try {
const response = await this.http.delete(`/lists/${list.id}/background`, list)
return response.data
} finally {
cancel()
})
}
}
}

View file

@ -15,25 +15,23 @@ export default class PasswordResetService extends AbstractService {
return new PasswordResetModel(data)
}
resetPassword(model) {
async resetPassword(model) {
const cancel = this.setLoading()
return this.http.post(this.paths.reset, model)
.then(response => {
try {
const response = await this.http.post(this.paths.reset, model)
return this.modelFactory(response.data)
})
.finally(() => {
} finally {
cancel()
})
}
}
requestResetPassword(model) {
async requestResetPassword(model) {
const cancel = this.setLoading()
return this.http.post(this.paths.requestReset, model)
.then(response => {
try {
const response = await this.http.post(this.paths.requestReset, model)
return this.modelFactory(response.data)
})
.finally(() => {
} finally {
cancel()
})
}
}
}

View file

@ -78,7 +78,7 @@ export default {
},
actions: {
// Logs a user in with a set of credentials.
login(ctx, credentials) {
async login(ctx, credentials) {
const HTTP = HTTPFactory()
ctx.commit(LOADING, true, {root: true})
@ -94,15 +94,14 @@ export default {
data.totp_passcode = credentials.totpPasscode
}
return HTTP.post('login', data)
.then(response => {
try {
const response = await HTTP.post('login', data)
// Save the token to local storage for later use
saveToken(response.data.token, true)
// Tell others the user is autheticated
ctx.dispatch('checkAuth')
})
.catch(e => {
} catch(e) {
if (
e.response &&
e.response.data.code === 1017 &&
@ -112,35 +111,34 @@ export default {
}
throw e
})
.finally(() => {
} finally {
ctx.commit(LOADING, false, {root: true})
})
}
},
// Registers a new user and logs them in.
// Not sure if this is the right place to put the logic in, maybe a seperate js component would be better suited.
register(ctx, credentials) {
async register(ctx, credentials) {
const HTTP = HTTPFactory()
return HTTP.post('register', {
try {
await HTTP.post('register', {
username: credentials.username,
email: credentials.email,
password: credentials.password,
})
.then(() => {
return ctx.dispatch('login', credentials)
})
.catch(e => {
} catch(e) {
if (e.response && e.response.data && e.response.data.message) {
ctx.commit(ERROR_MESSAGE, e.response.data.message, {root: true})
}
throw e
})
.finally(() => {
} finally {
ctx.commit(LOADING, false, {root: true})
})
}
},
openIdAuth(ctx, {provider, code}) {
async openIdAuth(ctx, {provider, code}) {
const HTTP = HTTPFactory()
ctx.commit(LOADING, true, {root: true})
@ -150,29 +148,28 @@ export default {
// Delete an eventually preexisting old token
removeToken()
return HTTP.post(`/auth/openid/${provider}/callback`, data)
.then(response => {
try {
const response = await HTTP.post(`/auth/openid/${provider}/callback`, data)
// Save the token to local storage for later use
saveToken(response.data.token, true)
// Tell others the user is autheticated
ctx.dispatch('checkAuth')
})
.finally(() => {
} finally {
ctx.commit(LOADING, false, {root: true})
})
}
},
linkShareAuth(ctx, {hash, password}) {
async linkShareAuth(ctx, {hash, password}) {
const HTTP = HTTPFactory()
return HTTP.post('/shares/' + hash + '/auth', {
const response = await HTTP.post('/shares/' + hash + '/auth', {
password: password,
})
.then(r => {
saveToken(r.data.token, false)
saveToken(response.data.token, false)
ctx.dispatch('checkAuth')
return r.data
})
return response.data
},
// Populates user information from jwt token saved in local storage in store
checkAuth(ctx) {
@ -205,54 +202,54 @@ export default {
ctx.dispatch('config/redirectToProviderIfNothingElseIsEnabled', null, {root: true})
}
},
refreshUserInfo(ctx) {
async refreshUserInfo(ctx) {
const jwt = getToken()
if (!jwt) {
return
}
const HTTP = HTTPFactory()
// We're not returning the promise here to prevent blocking the initial ui render if the user is
// accessing the site with a token in local storage
HTTP.get('user', {
try {
const response = await HTTP.get('user', {
headers: {
Authorization: `Bearer ${jwt}`,
},
})
.then(r => {
const info = new UserModel(r.data)
const info = new UserModel(response.data)
info.type = ctx.state.info.type
info.email = ctx.state.info.email
info.exp = ctx.state.info.exp
ctx.commit('info', info)
ctx.commit('lastUserRefresh')
})
.catch(e => {
return info
} catch(e) {
throw new Error('Error while refreshing user info:', { cause: e })
})
}
},
// Renews the api token and saves it to local storage
renewToken(ctx) {
// Timeout to avoid race conditions when authenticated as a user (=auth token in localStorage) and as a
// FIXME: Timeout to avoid race conditions when authenticated as a user (=auth token in localStorage) and as a
// link share in another tab. Without the timeout both the token renew and link share auth are executed at
// the same time and one might win over the other.
setTimeout(() => {
setTimeout(async () => {
if (!ctx.state.authenticated) {
return
}
refreshToken(!ctx.state.isLinkShareAuth)
.then(() => {
try {
await refreshToken(!ctx.state.isLinkShareAuth)
ctx.dispatch('checkAuth')
})
.catch(e => {
} catch(e) {
// Don't logout on network errors as the user would then get logged out if they don't have
// internet for a short period of time - such as when the laptop is still reconnecting
if (e.request.status) {
ctx.dispatch('logout')
}
})
}
}, 5000)
},
logout(ctx) {

View file

@ -60,15 +60,14 @@ export default {
},
},
actions: {
update(ctx) {
async update(ctx) {
const HTTP = HTTPFactory()
return HTTP.get('info')
.then(r => {
ctx.commit(CONFIG, r.data)
return r
})
const { data: info } = await HTTP.get('info')
ctx.commit(CONFIG, info)
return info
},
redirectToProviderIfNothingElseIsEnabled(ctx) {
if (ctx.state.auth.local.enabled === false &&
ctx.state.auth.openidConnect.enabled &&

View file

@ -209,7 +209,7 @@ export default {
},
actions: {
loadBucketsForList(ctx, {listId, params}) {
async loadBucketsForList(ctx, {listId, params}) {
const cancel = setLoading(ctx, 'kanban')
// Clear everything to prevent having old buckets in the list if loading the buckets from this list takes a few moments
@ -218,13 +218,14 @@ export default {
params.per_page = TASKS_PER_BUCKET
const bucketService = new BucketService()
return bucketService.getAll({listId: listId}, params)
.then(r => {
ctx.commit('setBuckets', r)
try {
const response = await bucketService.getAll({listId: listId}, params)
ctx.commit('setBuckets', response)
ctx.commit('setListId', listId)
return r
})
.finally(() => cancel())
return response
} finally {
cancel()
}
},
async loadNextTasksForBucket(ctx, {listId, ps = {}, bucketId}) {
@ -270,48 +271,50 @@ export default {
params.per_page = TASKS_PER_BUCKET
const taskService = new TaskCollectionService()
return taskService.getAll({listId: listId}, params, page)
.then(r => {
ctx.commit('addTasksToBucket', {tasks: r, bucketId: bucketId})
try {
const tasks = await taskService.getAll({listId: listId}, params, page)
ctx.commit('addTasksToBucket', {tasks, bucketId: bucketId})
ctx.commit('setTasksLoadedForBucketPage', {bucketId, page})
if (taskService.totalPages <= page) {
ctx.commit('setAllTasksLoadedForBucket', bucketId)
}
return r
})
.finally(() => {
return tasks
} finally {
cancel()
ctx.commit('setBucketLoading', {bucketId: bucketId, loading: false})
})
}
},
createBucket(ctx, bucket) {
async createBucket(ctx, bucket) {
const cancel = setLoading(ctx, 'kanban')
const bucketService = new BucketService()
return bucketService.create(bucket)
.then(r => {
ctx.commit('addBucket', r)
return r
})
.finally(() => cancel())
try {
const createdBucket = await bucketService.create(bucket)
ctx.commit('addBucket', createdBucket)
return createdBucket
} finally {
cancel()
}
},
deleteBucket(ctx, {bucket, params}) {
async deleteBucket(ctx, {bucket, params}) {
const cancel = setLoading(ctx, 'kanban')
const bucketService = new BucketService()
return bucketService.delete(bucket)
.then(r => {
try {
const response = await bucketService.delete(bucket)
ctx.commit('removeBucket', bucket)
// We reload all buckets because tasks are being moved from the deleted bucket
ctx.dispatch('loadBucketsForList', {listId: bucket.listId, params: params})
return r
})
.finally(() => cancel())
return response
} finally {
cancel()
}
},
updateBucket(ctx, updatedBucketData) {
async updateBucket(ctx, updatedBucketData) {
const cancel = setLoading(ctx, 'kanban')
const bucketIndex = findIndexById(ctx.state.buckets, updatedBucketData.id)
@ -325,21 +328,21 @@ export default {
ctx.commit('setBucketByIndex', {bucketIndex, bucket: updatedBucket})
const bucketService = new BucketService()
return bucketService.update(updatedBucket)
.then(r => {
ctx.commit('setBucketByIndex', {bucketIndex, bucket: r})
Promise.resolve(r)
})
.catch(e => {
try {
const returnedBucket = await bucketService.update(updatedBucket)
ctx.commit('setBucketByIndex', {bucketIndex, bucket: returnedBucket})
return returnedBucket
} catch(e) {
// restore original state
ctx.commit('setBucketByIndex', {bucketIndex, bucket: oldBucket})
throw e
})
.finally(() => cancel())
} finally {
cancel()
}
},
updateBucketTitle(ctx, { id, title }) {
async updateBucketTitle(ctx, { id, title }) {
const bucket = findById(ctx.state.buckets, id)
if (bucket.title === title) {
@ -352,9 +355,8 @@ export default {
title,
}
ctx.dispatch('updateBucket', updatedBucketData).then(() => {
await ctx.dispatch('updateBucket', updatedBucketData)
success({message: i18n.global.t('list.kanban.bucketTitleSavedSuccess')})
})
},
},
}

View file

@ -42,25 +42,28 @@ export default {
isFavorite: !list.isFavorite,
})
},
createList(ctx, list) {
async createList(ctx, list) {
const cancel = setLoading(ctx, 'lists')
const listService = new ListService()
return listService.create(list)
.then(r => {
r.namespaceId = list.namespaceId
ctx.commit('namespaces/addListToNamespace', r, {root: true})
ctx.commit('setList', r)
return r
})
.finally(() => cancel())
try {
const createdList = await listService.create(list)
createdList.namespaceId = list.namespaceId
ctx.commit('namespaces/addListToNamespace', createdList, {root: true})
ctx.commit('setList', createdList)
return createdList
} finally {
cancel()
}
},
updateList(ctx, list) {
async updateList(ctx, list) {
const cancel = setLoading(ctx, 'lists')
const listService = new ListService()
return listService.update(list)
.then(() => {
try {
await listService.update(list)
ctx.commit('setList', list)
ctx.commit('namespaces/setListInNamespaceById', list, {root: true})
@ -78,29 +81,31 @@ export default {
ctx.dispatch('namespaces/loadNamespacesIfFavoritesDontExist', null, {root: true})
ctx.dispatch('namespaces/removeFavoritesNamespaceIfEmpty', null, {root: true})
return newList
})
.catch(e => {
} catch(e) {
// Reset the list state to the initial one to avoid confusion for the user
ctx.commit('setList', {
...list,
isFavorite: !list.isFavorite,
})
throw e
})
.finally(() => cancel())
} finally {
cancel()
}
},
deleteList(ctx, list) {
async deleteList(ctx, list) {
const cancel = setLoading(ctx, 'lists')
const listService = new ListService()
return listService.delete(list)
.then(r => {
try {
const response = await listService.delete(list)
ctx.commit('removeListById', list)
ctx.commit('namespaces/removeListFromNamespaceById', list, {root: true})
removeListFromHistory({id: list.id})
return r
})
.finally(() => cancel())
return response
} finally{
cancel()
}
},
},
}

View file

@ -94,63 +94,63 @@ export default {
},
},
actions: {
loadNamespaces(ctx) {
async loadNamespaces(ctx) {
const cancel = setLoading(ctx, 'namespaces')
const namespaceService = new NamespaceService()
try {
// We always load all namespaces and filter them on the frontend
return namespaceService.getAll({}, {is_archived: true})
.then(r => {
ctx.commit('namespaces', r)
const namespaces = await namespaceService.getAll({}, {is_archived: true})
ctx.commit('namespaces', namespaces)
// Put all lists in the list state
const lists = []
r.forEach(n => {
n.lists.forEach(l => {
lists.push(l)
})
})
const lists = namespaces.flatMap(({lists}) => lists)
ctx.commit('lists/setLists', lists, {root: true})
return r
})
.finally(() => {
return namespaces
} finally {
cancel()
})
}
},
loadNamespacesIfFavoritesDontExist(ctx) {
// The first namespace should be the one holding all favorites
if (ctx.state.namespaces[0].id !== -2) {
return ctx.dispatch('loadNamespaces')
}
},
removeFavoritesNamespaceIfEmpty(ctx) {
if (ctx.state.namespaces[0].id === -2 && ctx.state.namespaces[0].lists.length === 0) {
ctx.state.namespaces.splice(0, 1)
}
},
deleteNamespace(ctx, namespace) {
async deleteNamespace(ctx, namespace) {
const cancel = setLoading(ctx, 'namespaces')
const namespaceService = new NamespaceService()
return namespaceService.delete(namespace)
.then(r => {
try {
const response = await namespaceService.delete(namespace)
ctx.commit('removeNamespaceById', namespace.id)
return r
})
.finally(() => cancel())
return response
} finally {
cancel()
}
},
createNamespace(ctx, namespace) {
async createNamespace(ctx, namespace) {
const cancel = setLoading(ctx, 'namespaces')
const namespaceService = new NamespaceService()
return namespaceService.create(namespace)
.then(r => {
ctx.commit('addNamespace', r)
return r
})
.finally(() => cancel())
try {
const createdNamespace = await namespaceService.create(namespace)
ctx.commit('addNamespace', createdNamespace)
return createdNamespace
} finally {
cancel()
}
},
},
}

View file

@ -34,17 +34,15 @@ function validateLabel(labels, label) {
return findPropertyByValue(labels, 'title', label)
}
function addLabelToTask(task, label) {
async function addLabelToTask(task, label) {
const labelTask = new LabelTask({
taskId: task.id,
labelId: label.id,
})
const labelTaskService = new LabelTaskService()
return labelTaskService.create(labelTask)
.then(result => {
const response = await labelTaskService.create(labelTask)
task.labels.push(label)
return result
})
return response
}
async function findAssignees(parsedTaskAssignees) {
@ -67,41 +65,39 @@ export default {
namespaced: true,
state: () => ({}),
actions: {
loadTasks(ctx, params) {
async loadTasks(ctx, params) {
const taskService = new TaskService()
const cancel = setLoading(ctx, 'tasks')
return taskService.getAll({}, params)
.then(r => {
ctx.commit(HAS_TASKS, r.length > 0, {root: true})
return r
})
.finally(() => {
try {
const tasks = await taskService.getAll({}, params)
ctx.commit(HAS_TASKS, tasks.length > 0, {root: true})
return tasks
} finally {
cancel()
})
}
},
update(ctx, task) {
async update(ctx, task) {
const cancel = setLoading(ctx, 'tasks')
const taskService = new TaskService()
return taskService.update(task)
.then(t => {
ctx.commit('kanban/setTaskInBucket', t, {root: true})
return t
})
.finally(() => {
try {
const updatedTask = await taskService.update(task)
ctx.commit('kanban/setTaskInBucket', updatedTask, {root: true})
return updatedTask
} finally {
cancel()
})
}
},
delete(ctx, task) {
async delete(ctx, task) {
const taskService = new TaskService()
return taskService.delete(task)
.then(t => {
const response = await taskService.delete(task)
ctx.commit('kanban/removeTaskInBucket', task, {root: true})
return t
})
return response
},
// Adds a task attachment in store.
// This is an action to be able to commit other mutations
addTaskAttachment(ctx, {taskId, attachment}) {
@ -124,12 +120,11 @@ export default {
ctx.commit('attachments/add', attachment, {root: true})
},
addAssignee(ctx, {user, taskId}) {
async addAssignee(ctx, {user, taskId}) {
const taskAssignee = new TaskAssigneeModel({userId: user.id, taskId: taskId})
const taskAssigneeService = new TaskAssigneeService()
return taskAssigneeService.create(taskAssignee)
.then(r => {
const r = await taskAssigneeService.create(taskAssignee)
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
if (t.task === null) {
// Don't try further adding a label if the task is not in kanban
@ -140,24 +135,22 @@ export default {
}
// FIXME: direct store manipulation (task)
t.task.assignees.push(user)
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
ctx.commit('kanban/setTaskInBucketByIndex', t, { root: true })
return r
})
},
removeAssignee(ctx, {user, taskId}) {
async removeAssignee(ctx, {user, taskId}) {
const taskAssignee = new TaskAssigneeModel({userId: user.id, taskId: taskId})
const taskAssigneeService = new TaskAssigneeService()
return taskAssigneeService.delete(taskAssignee)
.then(r => {
const response = await taskAssigneeService.delete(taskAssignee)
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
if (t.task === null) {
// Don't try further adding a label if the task is not in kanban
// Usually this means the kanban board hasn't been accessed until now.
// Vuex seems to have its difficulties with that, so we just log the error and fail silently.
console.debug('Could not remove assignee from task in kanban, task not found', t)
return r
return response
}
for (const a in t.task.assignees) {
@ -169,17 +162,15 @@ export default {
}
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
return r
})
return response
},
addLabel(ctx, {label, taskId}) {
async addLabel(ctx, {label, taskId}) {
const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
const labelTaskService = new LabelTaskService()
return labelTaskService.create(labelTask)
.then(r => {
const r = await labelTaskService.create(labelTask)
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
if (t.task === null) {
// Don't try further adding a label if the task is not in kanban
@ -190,25 +181,22 @@ export default {
}
// FIXME: direct store manipulation (task)
t.task.labels.push(label)
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
ctx.commit('kanban/setTaskInBucketByIndex', t, { root: true })
return r
})
},
removeLabel(ctx, {label, taskId}) {
async removeLabel(ctx, {label, taskId}) {
const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
const labelTaskService = new LabelTaskService()
return labelTaskService.delete(labelTask)
.then(r => {
const response = await labelTaskService.delete(labelTask)
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
if (t.task === null) {
// Don't try further adding a label if the task is not in kanban
// Usually this means the kanban board hasn't been accessed until now.
// Vuex seems to have its difficulties with that, so we just log the error and fail silently.
console.debug('Could not remove label from task in kanban, task not found', t)
return r
return response
}
// Remove the label from the list
@ -222,8 +210,7 @@ export default {
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
return r
})
return response
},
// Do everything that is involved in finding, creating and adding the label to the task
@ -308,11 +295,11 @@ export default {
})
const taskService = new TaskService()
return taskService.create(task)
.then(task => dispatch('addLabelsToTask', {
task,
const createdTask = await taskService.create(task)
return dispatch('addLabelsToTask', {
task: createdTask,
parsedLabels:parsedTask.labels,
}))
})
},
},
}

View file

@ -1,6 +1,6 @@
<template>
<div class="content has-text-centered">
<h2>
<h2 v-if="userInfo">
{{ $t(`home.welcome${welcome}`, {username: userInfo.name !== '' ? userInfo.name : userInfo.username}) }}!
</h2>
<div class="notification is-danger" v-if="deletionScheduledAt !== null">

View file

@ -93,13 +93,11 @@ export default {
this.$nextTick(() => this.editorActive = true)
},
methods: {
create() {
async create() {
this.savedFilter.filters = this.filters
this.savedFilterService.create(this.savedFilter)
.then(r => {
this.$store.dispatch('namespaces/loadNamespaces')
this.$router.push({name: 'list.index', params: {listId: r.getListId()}})
})
const savedFilter = await this.savedFilterService.create(this.savedFilter)
await this.$store.dispatch('namespaces/loadNamespaces')
this.$router.push({name: 'list.index', params: {listId: savedFilter.getListId()}})
},
},
}

View file

@ -24,17 +24,15 @@ export default {
}
},
methods: {
deleteSavedFilter() {
async deleteSavedFilter() {
// We assume the listId in the route is the pseudolist
const list = new ListModel({id: this.$route.params.listId})
const filter = new SavedFilterModel({id: list.getSavedFilterId()})
this.filterService.delete(filter)
.then(() => {
this.$store.dispatch('namespaces/loadNamespaces')
await this.filterService.delete(filter)
await this.$store.dispatch('namespaces/loadNamespaces')
this.$message.success({message: this.$t('filters.delete.success')})
this.$router.push({name: 'namespaces.index'})
})
},
},
}

View file

@ -95,27 +95,22 @@ export default {
},
},
methods: {
loadSavedFilter() {
async loadSavedFilter() {
// We assume the listId in the route is the pseudolist
const list = new ListModel({id: this.$route.params.listId})
this.filter = new SavedFilterModel({id: list.getSavedFilterId()})
this.filterService.get(this.filter)
.then(r => {
this.filter = r
this.filter = await this.filterService.get(this.filter)
this.filters = objectToSnakeCase(this.filter.filters)
})
},
save() {
async save() {
this.filter.filters = this.filters
this.filterService.update(this.filter)
.then(r => {
this.$store.dispatch('namespaces/loadNamespaces')
const filter = await this.filterService.update(this.filter)
await this.$store.dispatch('namespaces/loadNamespaces')
this.$message.success({message: this.$t('filters.edit.success')})
this.filter = r
this.filter = filter
this.filters = objectToSnakeCase(this.filter.filters)
this.$router.back()
})
},
},
}

View file

@ -60,21 +60,19 @@ export default {
loading: state => state[LOADING] && state[LOADING_MODULE] === 'labels',
}),
methods: {
newLabel() {
async newLabel() {
if (this.label.title === '') {
this.showError = true
return
}
this.showError = false
this.$store.dispatch('labels/createLabel', this.label)
.then(r => {
const label = this.$store.dispatch('labels/createLabel', this.label)
this.$router.push({
name: 'labels.index',
params: {id: r.id},
params: {id: label.id},
})
this.$message.success({message: this.$t('label.create.success')})
})
},
},
}

View file

@ -54,7 +54,7 @@ export default {
this.setTitle(this.$t('list.create.header'))
},
methods: {
newList() {
async newList() {
if (this.list.title === '') {
this.showError = true
return
@ -62,14 +62,11 @@ export default {
this.showError = false
this.list.namespaceId = parseInt(this.$route.params.id)
this.$store
.dispatch('lists/createList', this.list)
.then((r) => {
const list = await this.$store.dispatch('lists/createList', this.list)
this.$message.success({message: this.$t('list.create.createdSuccess') })
this.$router.push({
name: 'list.index',
params: { listId: r.id },
})
params: { listId: list.id },
})
},
},

View file

@ -83,7 +83,8 @@ export default {
this.$router.replace({name: savedListView, params: {id: this.$route.params.listId}})
console.debug('Replaced list view with', savedListView)
},
loadList() {
async loadList() {
if (this.$route.name.includes('.settings.')) {
return
}
@ -139,14 +140,13 @@ export default {
// We create an extra list object instead of creating it in this.list because that would trigger a ui update which would result in bad ux.
const list = new ListModel(listData)
this.listService.get(list)
.then(r => {
this.$store.dispatch(CURRENT_LIST, r)
this.setTitle(this.getListTitle(r))
})
.finally(() => {
try {
const loadedList = await this.listService.get(list)
this.$store.commit(CURRENT_LIST, loadedList)
this.setTitle(this.getListTitle(loadedList))
} finally {
this.listLoaded = this.$route.params.listId
})
}
},
},
}

View file

@ -30,21 +30,20 @@ export default {
},
},
methods: {
archiveList() {
async archiveList() {
const newList = {
...this.list,
isArchived: !this.list.isArchived,
}
this.listService.update(newList)
.then(r => {
this.$store.commit('currentList', r)
this.$store.commit('namespaces/setListInNamespaceById', r)
try {
const list = await this.listService.update(newList)
this.$store.commit('currentList', list)
this.$store.commit('namespaces/setListInNamespaceById', list)
this.$message.success({message: this.$t('list.archive.success')})
})
.finally(() => {
} finally {
this.$router.back()
})
}
},
},
}

View file

@ -71,6 +71,10 @@ import ListService from '@/services/list'
import {CURRENT_LIST} from '@/store/mutation-types'
import CreateEdit from '@/components/misc/create-edit.vue'
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
export default {
name: 'list-setting-background',
components: {CreateEdit},
@ -108,61 +112,53 @@ export default {
this.backgroundThumbs = {}
this.searchBackgrounds()
},
searchBackgrounds(page = 1) {
async searchBackgrounds(page = 1) {
if (this.backgroundSearchTimeout !== null) {
clearTimeout(this.backgroundSearchTimeout)
}
// We're using the timeout to not search on every keypress but with a 300ms delay.
// TODO: use throttle
// FIXME: We're using the timeout to not search on every keypress but with a 300ms delay.
// If another key is pressed within these 300ms, the last search request is dropped and a new one is scheduled.
this.backgroundSearchTimeout = setTimeout(() => {
this.backgroundSearchTimeout = await timeout(300)
this.currentPage = page
this.backgroundService.getAll({}, {s: this.backgroundSearchTerm, p: page})
.then(r => {
const r = await this.backgroundService.getAll({}, {s: this.backgroundSearchTerm, p: page})
this.backgroundSearchResult = this.backgroundSearchResult.concat(r)
r.forEach(b => {
this.backgroundService.thumb(b)
.then(t => {
this.backgroundThumbs[b.id] = t
r.forEach(async b => {
this.backgroundThumbs[b.id] = await this.backgroundService.thumb(b)
})
})
})
}, 300)
},
setBackground(backgroundId) {
async setBackground(backgroundId) {
// Don't set a background if we're in the process of setting one
if (this.backgroundService.loading) {
return
}
this.backgroundService.update({id: backgroundId, listId: this.$route.params.listId})
.then(l => {
this.$store.commit(CURRENT_LIST, l)
this.$store.commit('namespaces/setListInNamespaceById', l)
const list = await this.backgroundService.update({id: backgroundId, listId: this.$route.params.listId})
this.$store.commit(CURRENT_LIST, list)
this.$store.commit('namespaces/setListInNamespaceById', list)
this.$message.success({message: this.$t('list.background.success')})
})
},
uploadBackground() {
async uploadBackground() {
if (this.$refs.backgroundUploadInput.files.length === 0) {
return
}
this.backgroundUploadService.create(this.$route.params.listId, this.$refs.backgroundUploadInput.files[0])
.then(l => {
this.$store.commit(CURRENT_LIST, l)
this.$store.commit('namespaces/setListInNamespaceById', l)
const list = await this.backgroundUploadService.create(this.$route.params.listId, this.$refs.backgroundUploadInput.files[0])
this.$store.commit(CURRENT_LIST, list)
this.$store.commit('namespaces/setListInNamespaceById', list)
this.$message.success({message: this.$t('list.background.success')})
})
},
removeBackground() {
this.listService.removeBackground(this.currentList)
.then(l => {
this.$store.commit(CURRENT_LIST, l)
this.$store.commit('namespaces/setListInNamespaceById', l)
async removeBackground() {
const list = await this.listService.removeBackground(this.currentList)
this.$store.commit(CURRENT_LIST, list)
this.$store.commit('namespaces/setListInNamespaceById', list)
this.$message.success({message: this.$t('list.background.removeSuccess')})
this.$router.back()
})
},
},
}

View file

@ -24,12 +24,10 @@ export default {
},
},
methods: {
deleteList() {
this.$store.dispatch('lists/deleteList', this.list)
.then(() => {
async deleteList() {
await this.$store.dispatch('lists/deleteList', this.list)
this.$message.success({message: this.$t('list.delete.success')})
this.$router.push({name: 'home'})
})
},
},
}

View file

@ -38,18 +38,17 @@ export default {
selectNamespace(namespace) {
this.selectedNamespace = namespace
},
duplicateList() {
async duplicateList() {
const listDuplicate = new ListDuplicateModel({
listId: this.$route.params.listId,
namespaceId: this.selectedNamespace.id,
})
this.listDuplicateService.create(listDuplicate)
.then(r => {
this.$store.commit('namespaces/addListToNamespace', r.list)
this.$store.commit('lists/setList', r.list)
const duplicate = await this.listDuplicateService.create(listDuplicate)
this.$store.commit('namespaces/addListToNamespace', duplicate.list)
this.$store.commit('lists/setList', duplicate.list)
this.$message.success({message: this.$t('list.duplicate.success')})
this.$router.push({name: 'list.index', params: {listId: r.list.id}})
})
this.$router.push({name: 'list.index', params: {listId: duplicate.list.id}})
},
},
}

View file

@ -94,22 +94,19 @@ export default {
},
},
methods: {
loadList() {
async loadList() {
const list = new ListModel({id: this.$route.params.listId})
this.listService.get(list)
.then(r => {
this.list = { ...r }
})
const loadedList = await this.listService.get(list)
this.list = { ...loadedList }
},
save() {
this.$store.dispatch('lists/updateList', this.list)
.then(() => {
async save() {
await this.$store.dispatch('lists/updateList', this.list)
this.$store.commit(CURRENT_LIST, this.list)
this.setTitle(this.$t('list.edit.title', {list: this.list.title}))
this.$message.success({message: this.$t('list.edit.success')})
this.$router.back()
})
},
},
}

View file

@ -56,18 +56,15 @@ export default {
this.loadList()
},
methods: {
loadList() {
async loadList() {
const list = new ListModel({id: this.$route.params.listId})
this.listService.get(list)
.then(r => {
this.list = r
this.$store.commit(CURRENT_LIST, r)
this.list = await this.listService.get(list)
this.$store.commit(CURRENT_LIST, this.list)
// This will trigger the dynamic loading of components once we actually have all the data to pass to them
this.manageTeamsComponent = 'userTeam'
this.manageUsersComponent = 'userTeam'
this.setTitle(this.$t('list.share.title', {list: this.list.title}))
})
},
},
}

View file

@ -408,7 +408,7 @@ export default {
this.$store.dispatch('kanban/updateBucket', newBucket)
},
updateTaskPosition(e) {
async updateTaskPosition(e) {
this.drag = false
// While we could just pass the bucket index in through the function call, this would not give us the
@ -427,34 +427,35 @@ export default {
kanbanPosition: calculateItemPosition(taskBefore !== null ? taskBefore.kanbanPosition : null, taskAfter !== null ? taskAfter.kanbanPosition : null),
}
this.$store.dispatch('tasks/update', newTask)
// .finally(() => {
try {
await this.$store.dispatch('tasks/update', newTask)
} finally {
this.taskUpdating[task.id] = false
this.oneTaskUpdating = false
// })
}
},
toggleShowNewTaskInput(bucketId) {
this.showNewTaskInput[bucketId] = !this.showNewTaskInput[bucketId]
},
addTaskToBucket(bucketId) {
async addTaskToBucket(bucketId) {
if (this.newTaskText === '') {
this.newTaskError[bucketId] = true
return
}
this.newTaskError[bucketId] = false
this.$store.dispatch('tasks/createNewTask', {
const task = await this.$store.dispatch('tasks/createNewTask', {
title: this.newTaskText,
bucketId,
listId: this.$route.params.listId,
})
.then(r => {
this.newTaskText = ''
this.$store.commit('kanban/addTaskToBucket', r)
this.$store.commit('kanban/addTaskToBucket', task)
this.scrollTaskContainerToBottom(bucketId)
})
},
scrollTaskContainerToBottom(bucketId) {
const bucketEl = this.taskContainerRefs[bucketId]
if (!bucketEl) {
@ -462,7 +463,8 @@ export default {
}
bucketEl.scrollTop = bucketEl.scrollHeight
},
createNewBucket() {
async createNewBucket() {
if (this.newBucketTitle === '') {
return
}
@ -472,12 +474,11 @@ export default {
listId: parseInt(this.$route.params.listId),
})
this.$store.dispatch('kanban/createBucket', newBucket)
.then(() => {
await this.$store.dispatch('kanban/createBucket', newBucket)
this.newBucketTitle = ''
this.showNewBucketInput = false
})
},
deleteBucketModal(bucketId) {
if (this.buckets.length <= 1) {
return
@ -486,33 +487,39 @@ export default {
this.bucketToDelete = bucketId
this.showBucketDeleteModal = true
},
deleteBucket() {
this.$store.dispatch('kanban/deleteBucket', {bucket: {
async deleteBucket() {
const bucket = new BucketModel({
id: this.bucketToDelete,
listId: parseInt(this.$route.params.listId),
}, params: this.params})
.then(() => this.$message.success({message: this.$t('list.kanban.deleteBucketSuccess')}))
.finally(() => {
this.showBucketDeleteModal = false
})
try {
await this.$store.dispatch('kanban/deleteBucket', {
bucket,
params: this.params,
})
this.$message.success({message: this.$t('list.kanban.deleteBucketSuccess')})
} finally {
this.showBucketDeleteModal = false
}
},
focusBucketTitle(e) {
// This little helper allows us to drag a bucket around at the title without focusing on it right away.
this.bucketTitleEditable = true
this.$nextTick(() => e.target.focus())
},
saveBucketTitle(bucketId, bucketTitle) {
async saveBucketTitle(bucketId, bucketTitle) {
const updatedBucketData = {
id: bucketId,
title: bucketTitle,
}
this.$store.dispatch('kanban/updateBucketTitle', updatedBucketData)
.then(() => {
await this.$store.dispatch('kanban/updateBucketTitle', updatedBucketData)
this.bucketTitleEditable = false
this.$message.success({message: this.$t('list.kanban.bucketTitleSavedSuccess')})
})
},
updateBuckets(value) {
@ -535,7 +542,8 @@ export default {
this.$store.dispatch('kanban/updateBucket', updatedData)
},
setBucketLimit(bucketId, limit) {
async setBucketLimit(bucketId, limit) {
if (limit < 0) {
return
}
@ -545,27 +553,30 @@ export default {
limit,
}
this.$store.dispatch('kanban/updateBucket', newBucket)
.then(() => this.$message.success({message: this.$t('list.kanban.bucketLimitSavedSuccess')}))
await this.$store.dispatch('kanban/updateBucket', newBucket)
this.$message.success({message: this.$t('list.kanban.bucketLimitSavedSuccess')})
},
shouldAcceptDrop(bucket) {
return bucket.id === this.sourceBucket || // When dragging from a bucket who has its limit reached, dragging should still be possible
bucket.limit === 0 || // If there is no limit set, dragging & dropping should always work
bucket.tasks.length < bucket.limit // Disallow dropping to buckets which have their limit reached
},
dragstart(bucket) {
this.drag = true
this.sourceBucket = bucket.id
},
toggleDoneBucket(bucket) {
async toggleDoneBucket(bucket) {
const newBucket = {
...bucket,
isDoneBucket: !bucket.isDoneBucket,
}
this.$store.dispatch('kanban/updateBucket', newBucket)
.then(() => this.$message.success({message: this.$t('list.kanban.doneBucketSavedSuccess')}))
await this.$store.dispatch('kanban/updateBucket', newBucket)
this.$message.success({message: this.$t('list.kanban.doneBucketSavedSuccess')})
},
collapseBucket(bucket) {
this.collapsedBuckets[bucket.id] = true
saveCollapsedBucketState(this.$route.params.listId, this.collapsedBuckets)

View file

@ -291,7 +291,8 @@ export default {
}
sortTasks(this.tasks)
},
saveTaskPosition(e) {
async saveTaskPosition(e) {
this.drag = false
const task = this.tasks[e.newIndex]
@ -303,10 +304,8 @@ export default {
position: calculateItemPosition(taskBefore !== null ? taskBefore.position : null, taskAfter !== null ? taskAfter.position : null),
}
this.$store.dispatch('tasks/update', newTask)
.then(r => {
this.tasks[e.newIndex] = r
})
const updatedTask = await this.$store.dispatch('tasks/update', newTask)
this.tasks[e.newIndex] = updatedTask
},
},
}

View file

@ -63,20 +63,17 @@ export default {
this.setTitle(this.$t('namespace.create.title'))
},
methods: {
newNamespace() {
async newNamespace() {
if (this.namespace.title === '') {
this.showError = true
return
}
this.showError = false
this.namespaceService
.create(this.namespace)
.then((r) => {
this.$store.commit('namespaces/addNamespace', r)
const namespace = this.namespaceService.create(this.namespace)
this.$store.commit('namespaces/addNamespace', namespace)
this.$message.success({message: this.$t('namespace.create.success') })
this.$router.back()
})
},
},
}

View file

@ -23,6 +23,7 @@ export default {
title: '',
}
},
created() {
this.namespace = this.$store.getters['namespaces/getNamespaceById'](this.$route.params.id)
this.title = this.namespace.isArchived ?
@ -30,19 +31,18 @@ export default {
this.$t('namespace.archive.titleArchive', { namespace: this.namespace.title })
this.setTitle(this.title)
},
methods: {
archiveNamespace() {
methods: {
async archiveNamespace() {
this.namespace.isArchived = !this.namespace.isArchived
this.namespaceService.update(this.namespace)
.then(r => {
this.$store.commit('namespaces/setNamespaceById', r)
try {
const namespace = await this.namespaceService.update(this.namespace)
this.$store.commit('namespaces/setNamespaceById', namespace)
this.$message.success({message: this.$t('namespace.archive.success')})
})
.finally(() => {
} finally {
this.$router.back()
})
}
},
},
}

View file

@ -35,12 +35,10 @@ export default {
},
},
methods: {
deleteNamespace() {
this.$store.dispatch('namespaces/deleteNamespace', this.namespace)
.then(() => {
async deleteNamespace() {
await this.$store.dispatch('namespaces/deleteNamespace', this.namespace)
this.$message.success({message: this.$t('namespace.delete.success')})
this.$router.push({name: 'home'})
})
},
},
}

View file

@ -93,8 +93,8 @@ export default {
},
},
methods: {
loadNamespace() {
// This makes the editor trigger its mounted function again which makes it forget every input
async loadNamespace() {
// HACK: This makes the editor trigger its mounted function again which makes it forget every input
// it currently has in its textarea. This is a counter-hack to a hack inside of vue-easymde
// which made it impossible to detect change from the outside. Therefore the component would
// not update if new content from the outside was made available.
@ -103,24 +103,20 @@ export default {
this.$nextTick(() => this.editorActive = true)
const namespace = new NamespaceModel({id: this.$route.params.id})
this.namespaceService.get(namespace)
.then(r => {
this.namespace = r
this.namespace = await this.namespaceService.get(namespace)
// This will trigger the dynamic loading of components once we actually have all the data to pass to them
this.manageTeamsComponent = 'manageSharing'
this.manageUsersComponent = 'manageSharing'
this.title = this.$t('namespace.edit.title', {namespace: r.title})
this.title = this.$t('namespace.edit.title', {namespace: this.namespace.title})
this.setTitle(this.title)
})
},
save() {
this.namespaceService.update(this.namespace)
.then(r => {
async save() {
const namespace = await this.namespaceService.update(this.namespace)
// Update the namespace in the parent
this.$store.commit('namespaces/setNamespaceById', r)
this.$store.commit('namespaces/setNamespaceById', namespace)
this.$message.success({message: this.$t('namespace.edit.success')})
this.$router.back()
})
},
},
}

View file

@ -57,17 +57,14 @@ export default {
},
},
methods: {
loadNamespace() {
async loadNamespace() {
const namespace = new NamespaceModel({id: this.$route.params.id})
this.namespaceService.get(namespace)
.then(r => {
this.namespace = r
this.namespace = await this.namespaceService.get(namespace)
// This will trigger the dynamic loading of components once we actually have all the data to pass to them
this.manageTeamsComponent = 'manageSharing'
this.manageUsersComponent = 'manageSharing'
this.title = this.$t('namespace.share.title', { namespace: this.namespace.title })
this.setTitle(this.title)
})
},
},
}

View file

@ -57,7 +57,7 @@ export default {
'authLinkShare',
]),
methods: {
auth() {
async auth() {
this.errorMessage = ''
if (this.authLinkShare) {
@ -66,11 +66,13 @@ export default {
this.loading = true
this.$store.dispatch('auth/linkShareAuth', {hash: this.$route.params.share, password: this.password})
.then((r) => {
this.$router.push({name: 'list.list', params: {listId: r.list_id}})
try {
const r = await this.$store.dispatch('auth/linkShareAuth', {
hash: this.$route.params.share,
password: this.password,
})
.catch(e => {
this.$router.push({name: 'list.list', params: {listId: r.list_id}})
} catch(e) {
if (typeof e.response.data.code !== 'undefined' && e.response.data.code === 13001) {
this.authenticateWithPassword = true
return
@ -85,10 +87,9 @@ export default {
errorMessage = this.$t('sharing.invalidPassword')
}
this.errorMessage = errorMessage
})
.finally(() => {
} finally {
this.loading = false
})
}
},
},
}

View file

@ -141,7 +141,7 @@ export default {
},
})
},
loadPendingTasks() {
async loadPendingTasks() {
// Since this route is authentication only, users would get an error message if they access the page unauthenticated.
// Since this component is mounted as the home page before unauthenticated users get redirected
// to the login page, they will almost always see the error message.
@ -163,7 +163,10 @@ export default {
if (this.showAll) {
this.setTitle(this.$t('task.show.titleCurrent'))
} else {
this.setTitle(this.$t('task.show.titleDates', { from: this.cStartDate.toLocaleDateString(), to: this.cEndDate.toLocaleDateString()}))
this.setTitle(this.$t('task.show.titleDates', {
from: this.cStartDate.toLocaleDateString(),
to: this.cEndDate.toLocaleDateString(),
}))
}
const params = {
@ -197,21 +200,19 @@ export default {
}
}
this.$store.dispatch('tasks/loadTasks', params)
.then(r => {
const tasks = await this.$store.dispatch('tasks/loadTasks', params)
// FIXME: sort tasks in computed
// Sort all tasks to put those with a due date before the ones without a due date, the
// soonest before the later ones.
// We can't use the api sorting here because that sorts tasks with a due date after
// ones without a due date.
r.sort((a, b) => {
return a.dueDate === null && b.dueDate === null ? -1 : 1
})
const tasks = r.filter(t => t.dueDate !== null).concat(r.filter(t => t.dueDate === null))
this.tasks = tasks
this.tasks = tasks.sort((a, b) => {
return Number(b.dueDate === null) - Number(a.dueDate === null)
})
},
// FIXME: this modification should happen in the store
updateTasks(updatedTask) {
for (const t in this.tasks) {
if (this.tasks[t].id === updatedTask.id) {
@ -225,18 +226,21 @@ export default {
}
}
},
setDatesToNextWeek() {
this.cStartDate = new Date()
this.cEndDate = new Date((new Date()).getTime() + 7 * 24 * 60 * 60 * 1000)
this.showOverdue = false
this.setDate()
},
setDatesToNextMonth() {
this.cStartDate = new Date()
this.cEndDate = new Date((new Date()).setMonth((new Date()).getMonth() + 1))
this.showOverdue = false
this.setDate()
},
showTodaysTasks() {
const d = new Date()
this.cStartDate = new Date()

View file

@ -561,23 +561,22 @@ export default {
return uploadFile(this.taskId, ...args)
},
loadTask(taskId) {
async loadTask(taskId) {
if (taskId === undefined) {
return
}
this.taskService.get({id: taskId})
.then(r => {
this.task = r
this.$store.commit('attachments/set', r.attachments)
try {
this.task = await this.taskService.get({id: taskId})
this.$store.commit('attachments/set', this.task.attachments)
this.taskColor = this.task.hexColor
this.setActiveFields()
this.setTitle(this.task.title)
})
.finally(() => {
this.$nextTick(() => this.visible = true)
} finally {
this.scrollToHeading()
})
await this.$nextTick()
this.visible = true
}
},
scrollToHeading() {
this.$refs.heading.$el.scrollIntoView({block: 'center'})
@ -633,6 +632,7 @@ export default {
}
this.$message.success({message: this.$t('task.detail.updateSuccess')}, actions)
},
setFieldActive(fieldName) {
this.activeFields[fieldName] = true
this.$nextTick(() => {
@ -651,13 +651,13 @@ export default {
}
})
},
deleteTask() {
this.$store.dispatch('tasks/delete', this.task)
.then(() => {
async deleteTask() {
await this.$store.dispatch('tasks/delete', this.task)
this.$message.success({message: this.$t('task.detail.deleteSuccess')})
this.$router.push({name: 'list.index', params: {listId: this.task.listId}})
})
},
toggleTaskDone() {
this.task.done = !this.task.done
@ -665,36 +665,26 @@ export default {
playPop()
}
this.saveTask(true, () => this.toggleTaskDone())
this.saveTask(true, this.toggleTaskDone)
},
setDescriptionChanged(e) {
if (e.key === 'Enter' || e.key === 'Control') {
return
}
this.descriptionChanged = true
},
saveTaskIfDescriptionChanged() {
// We want to only save the description if it was changed.
// Since we can either trigger this with ctrl+enter or @change, it would be possible to save a task first
// with ctrl+enter and then with @change although nothing changed since the last save when @change gets fired.
// To only save one time we added this method.
if (this.descriptionChanged) {
this.descriptionChanged = false
this.saveTask()
}
},
async changeList(list) {
this.$store.commit('kanban/removeTaskInBucket', this.task)
this.task.listId = list.id
await this.saveTask()
},
toggleFavorite() {
async toggleFavorite() {
this.task.isFavorite = !this.task.isFavorite
this.taskService.update(this.task)
.then(t => {
this.task = t
this.task = await this.taskService.update(this.task)
this.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
})
},
},
}

View file

@ -219,68 +219,57 @@ export default {
userInfo: (state) => state.auth.info,
}),
},
methods: {
loadTeam() {
async loadTeam() {
this.team = new TeamModel({id: this.teamId})
this.teamService
.get(this.team)
.then((response) => {
this.team = response
this.team = await this.teamService.get(this.team)
this.title = this.$t('team.edit.title', {team: this.team.name})
this.setTitle(this.title)
})
},
save() {
async save() {
if (this.team.name === '') {
this.showError = true
return
}
this.showError = false
this.teamService
.update(this.team)
.then((response) => {
this.team = response
this.team = await this.teamService.update(this.team)
this.$message.success({message: this.$t('team.edit.success')})
})
},
deleteTeam() {
this.teamService
.delete(this.team)
.then(() => {
async deleteTeam() {
await this.teamService.delete(this.team)
this.$message.success({message: this.$t('team.edit.delete.success')})
this.$router.push({name: 'teams.index'})
})
},
deleteUser() {
this.teamMemberService
.delete(this.member)
.then(() => {
async deleteUser() {
try {
await this.teamMemberService.delete(this.member)
this.$message.success({message: this.$t('team.edit.deleteUser.success')})
this.loadTeam()
})
.finally(() => {
} finally {
this.showUserDeleteModal = false
})
}
},
addUser() {
async addUser() {
const newMember = new TeamMemberModel({
teamId: this.teamId,
username: this.newMember.username,
})
this.teamMemberService
.create(newMember)
.then(() => {
await this.teamMemberService.create(newMember)
this.loadTeam()
this.$message.success({message: this.$t('team.edit.userAddedSuccess')})
})
},
toggleUserType(member) {
async toggleUserType(member) {
// FIXME: direct manipulation
member.admin = !member.admin
member.teamId = this.teamId
this.teamMemberService
.update(member)
.then((r) => {
const r = await this.teamMemberService.update(member)
for (const tm in this.team.members) {
if (this.team.members[tm].id === member.id) {
this.team.members[tm].admin = r.admin
@ -292,20 +281,17 @@ export default {
this.$t('team.edit.madeAdmin') :
this.$t('team.edit.madeMember'),
})
})
},
findUser(query) {
async findUser(query) {
if (query === '') {
this.clearAll()
return
}
this.userService
.getAll({}, {s: query})
.then((response) => {
this.foundUsers = response
})
this.foundUsers = await this.userService.getAll({}, {s: query})
},
clearAll() {
this.foundUsers = []
},

View file

@ -43,11 +43,8 @@ export default {
this.setTitle(this.$t('team.title'))
},
methods: {
loadTeams() {
this.teamService.getAll()
.then(response => {
this.teams = response
})
async loadTeams() {
this.teams = await this.teamService.getAll()
},
},
}

View file

@ -49,22 +49,19 @@ export default {
this.setTitle(this.$t('team.create.title'))
},
methods: {
newTeam() {
async newTeam() {
if (this.team.name === '') {
this.showError = true
return
}
this.showError = false
this.teamService
.create(this.team)
.then((response) => {
const response = await this.teamService.create(this.team)
this.$router.push({
name: 'teams.edit',
params: { id: response.id },
})
this.$message.success({message: this.$t('team.create.success') })
})
},
},
}

View file

@ -187,7 +187,8 @@ export default {
this.loading = false
}
},
submit() {
async submit() {
this.$store.commit(ERROR_MESSAGE, '')
// Some browsers prevent Vue bindings from working with autofilled values.
// To work around this, we're manually getting the values here instead of relying on vue bindings.
@ -201,11 +202,10 @@ export default {
credentials.totpPasscode = this.$refs.totpPasscode.value
}
this.$store.dispatch('auth/login', credentials)
.then(() => {
try {
await this.$store.dispatch('auth/login', credentials)
this.$store.commit('auth/needsTotpPasscode', false)
})
.catch(e => {
} catch(e) {
if (e.response && e.response.data.code === 1017 && !credentials.totpPasscode) {
return
}
@ -217,8 +217,9 @@ export default {
}
this.$store.commit(ERROR_MESSAGE, err[0])
})
}
},
redirectToProvider(provider) {
redirectToProvider(provider, this.openidConnect.redirectUrl)
},

View file

@ -26,7 +26,7 @@ export default {
this.authenticateWithCode()
},
methods: {
authenticateWithCode() {
async authenticateWithCode() {
// This component gets mounted twice: The first time when the actual auth request hits the frontend,
// the second time after that auth request succeeded and the outer component "content-no-auth" isn't used
// but instead the "content-auth" component is used. Because this component is just a route and thus
@ -59,11 +59,11 @@ export default {
this.$store.commit(ERROR_MESSAGE, '')
this.$store.dispatch('auth/openIdAuth', {
try {
await this.$store.dispatch('auth/openIdAuth', {
provider: this.$route.params.provider,
code: this.$route.query.code,
})
.then(() => {
const last = getLastVisited()
if (last !== null) {
this.$router.push({
@ -74,8 +74,7 @@ export default {
} else {
this.$router.push({name: 'home'})
}
})
.catch(e => {
} catch(e) {
const err = getErrorText(e)
if (typeof err[1] !== 'undefined') {
this.$store.commit(ERROR_MESSAGE, err[1])
@ -83,10 +82,9 @@ export default {
}
this.$store.commit(ERROR_MESSAGE, err[0])
})
.finally(() => {
} finally {
localStorage.removeItem('authenticating')
})
}
},
},
}

View file

@ -85,11 +85,13 @@ export default {
successMessage: '',
}
},
mounted() {
this.setTitle(this.$t('user.auth.resetPassword'))
},
methods: {
submit() {
async submit() {
this.errorMsg = ''
if (this.credentials.password2 !== this.credentials.password) {
@ -98,14 +100,13 @@ export default {
}
let passwordReset = new PasswordResetModel({newPassword: this.credentials.password})
this.passwordResetService.resetPassword(passwordReset)
.then(response => {
this.successMessage = response.message
try {
const { message } = this.passwordResetService.resetPassword(passwordReset)
this.successMessage = message
localStorage.removeItem('passwordResetToken')
})
.catch(e => {
} catch(e) {
this.errorMsg = e.response.data.message
})
}
},
},
}

View file

@ -69,15 +69,14 @@ export default {
this.setTitle(this.$t('user.auth.resetPassword'))
},
methods: {
submit() {
async submit() {
this.errorMsg = ''
this.passwordResetService.requestResetPassword(this.passwordReset)
.then(() => {
try {
await this.passwordResetService.requestResetPassword(this.passwordReset)
this.isSuccess = true
})
.catch(e => {
} catch(e) {
this.errorMsg = e.response.data.message
})
}
},
},
}

View file

@ -385,33 +385,29 @@ export default {
methods: {
copy,
updatePassword() {
async updatePassword() {
if (this.passwordConfirm !== this.passwordUpdate.newPassword) {
this.$message.error({message: this.$t('user.settings.passwordsDontMatch')})
return
}
this.passwordUpdateService.update(this.passwordUpdate)
.then(() => {
await this.passwordUpdateService.update(this.passwordUpdate)
this.$message.success({message: this.$t('user.settings.passwordUpdateSuccess')})
})
},
updateEmail() {
this.emailUpdateService.update(this.emailUpdate)
.then(() => {
async updateEmail() {
await this.emailUpdateService.update(this.emailUpdate)
this.$message.success({message: this.$t('user.settings.updateEmailSuccess')})
})
},
totpStatus() {
async totpStatus() {
if (!this.totpEnabled) {
return
}
this.totpService.get()
.then(r => {
this.totp = r
try {
this.totp = await this.totpService.get()
this.totpSetQrCode()
})
.catch(e => {
} catch(e) {
// Error code 1016 means totp is not enabled, we don't need an error in that case.
if (e.response && e.response.data && e.response.data.code && e.response.data.code === 1016) {
this.totpEnrolled = false
@ -419,50 +415,45 @@ export default {
}
throw e
})
}
},
totpSetQrCode() {
this.totpService.qrcode()
.then(qr => {
async totpSetQrCode() {
const qr = await this.totpService.qrcode()
const urlCreator = window.URL || window.webkitURL
this.totpQR = urlCreator.createObjectURL(qr)
})
},
totpEnroll() {
this.totpService.enroll()
.then(r => {
async totpEnroll() {
this.totp = await this.totpService.enroll()
this.totpEnrolled = true
this.totp = r
this.totpSetQrCode()
})
},
totpConfirm() {
this.totpService.enable({passcode: this.totpConfirmPasscode})
.then(() => {
async totpConfirm() {
await this.totpService.enable({passcode: this.totpConfirmPasscode})
this.totp.enabled = true
this.$message.success({message: this.$t('user.settings.totp.confirmSuccess')})
})
},
totpDisable() {
this.totpService.disable({password: this.totpDisablePassword})
.then(() => {
async totpDisable() {
await this.totpService.disable({password: this.totpDisablePassword})
this.totpEnrolled = false
this.totp = new TotpModel()
this.$message.success({message: this.$t('user.settings.totp.disableSuccess')})
})
},
updateSettings() {
async updateSettings() {
localStorage.setItem(playSoundWhenDoneKey, this.playSoundWhenDone)
saveLanguage(this.language)
setQuickAddMagicMode(this.quickAddMagicMode)
this.settings.defaultListId = this.defaultList ? this.defaultList.id : 0
this.userSettingsService.update(this.settings)
.then(() => {
await this.userSettingsService.update(this.settings)
this.$store.commit('auth/setUserSettings', this.settings)
this.$message.success({message: this.$t('user.settings.general.savedSuccess')})
})
},
anchorHashCheck() {
if (window.location.hash === this.$route.hash) {
const el = document.getElementById(this.$route.hash.slice(1))