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
Signed by: dpschen
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
localStorage.setItem('passwordResetToken', this.$route.query.userPasswordReset)
this.$router.push({name: 'user.password-reset.reset'})
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
localStorage.setItem('emailConfirmToken', this.$route.query.userEmailConfirm)
this.$router.push({name: 'user.login'})
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') {
const accountDeletionService = new AccountDeleteService()
accountDeletionService.confirm(this.$route.query.accountDeletionConfirm)
.then(() => {
this.$message.success({message: this.$t('user.deletion.confirmSuccess')})
this.$store.dispatch('auth/refreshUserInfo')
})
async setupAccountDeletionVerification() {
if (typeof this.$route.query.accountDeletionConfirm === 'undefined') {
return
}
const accountDeletionService = new AccountDeleteService()
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)
// create a copy of the list in order to not violate vuex mutations
this.$store.dispatch('lists/updateList', {
...list,
position,
})
.finally(() => {
this.listUpdating[list.id] = false
try {
// create a copy of the list in order to not violate vuex mutations
await this.$store.dispatch('lists/updateList', {
...list,
position,
})
} 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 => {
img.src = url
this.loadedAttachments[cacheKey] = 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 => {
// Filter users from the results who are already assigned
this[`found${kind}`] = response.filter(({id}) => !includesById(this[kind], id))
})
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 = ''
let migrationConfig = { code: this.migratorAuthCode }
if (this.isFileMigrator) {
return this.migrateFile()
if (this.$refs.uploadInput.files.length === 0) {
return
}
migrationConfig = this.$refs.uploadInput.files[0]
}
this.migrationService.migrate({code: this.migratorAuthCode})
.then(r => {
this.message = r.message
this.$store.dispatch('namespaces/loadNamespaces')
})
.finally(() => {
this.isMigrating = false
})
},
migrateFile() {
if (this.$refs.uploadInput.files.length === 0) {
return
try {
const { message } = await this.migrationService.migrate(migrationConfig)
this.message = message
return this.$store.dispatch('namespaces/loadNamespaces')
} finally {
this.isMigrating = false
}
this.migrationService.migrate(this.$refs.uploadInput.files[0])
.then(r => {
this.message = r.message
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(() => {
this.$emit('change', subscription)
this.$message.success({message: this.$t('task.subscription.subscribeSuccess', {entity: this.entity})})
})
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(() => {
this.$emit('change', null)
this.$message.success({message: this.$t('task.subscription.unsubscribeSuccess', {entity: this.entity})})
})
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 => {
t.type = TYPE_TASK
const list = this.$store.getters['lists/getListById'](t.listId) === null ? null : this.$store.getters['lists/getListById'](t.listId)
if (list !== null) {
t.title = `${t.title} (${list.title})`
}
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)
if (list !== null) {
t.title = `${t.title} (${list.title})`
}
return t
})
this.foundTasks = r
})
return t
})
}, 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 => {
t.title = t.name
return t
})
this.foundTeams = r
})
this.teamSearchTimeout = setTimeout(async () => {
const r = await this.teamService.getAll({}, {s: query})
this.foundTeams = r.map(t => {
t.title = t.name
return t
})
}, 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.closeQuickActions()
})
this.$message.success({message: this.$t('task.createSuccess')})
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 => {
this.$message.success({message: this.$t('list.create.createdSuccess')})
this.$router.push({name: 'list.index', params: {listId: r.id}})
this.closeQuickActions()
})
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: list.id}})
this.closeQuickActions()
},
newNamespace() {
async newNamespace() {
const newNamespace = new NamespaceModel({title: this.query})
this.$store.dispatch('namespaces/createNamespace', newNamespace)
.then(() => {
this.$message.success({message: this.$t('namespace.create.success')})
this.closeQuickActions()
})
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 => {
this.$router.push({
name: 'teams.edit',
params: {id: r.id},
})
this.$message.success({message: this.$t('team.create.success')})
this.closeQuickActions()
})
const team = await this.teamService.create(newTeam)
this.$router.push({
name: 'teams.edit',
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(() => {
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.linkShareService.create(newLinkShare)
this.selectedRight = rights.READ
this.name = ''
this.password = ''
this.showNewForm = false
this.$message.success({message: this.$t('list.share.links.createSuccess')})
await this.load(listId)
},
remove(listId) {
async remove(listId) {
const linkshare = new LinkShareModel({
id: this.linkIdToDelete,
listId,
})
this.linkShareService
.delete(linkshare)
.then(() => {
this.$message.success({message: this.$t('list.share.links.deleteSuccess')})
this.load(listId)
})
.finally(() => {
this.showDeleteModal = false
})
try {
await this.linkShareService.delete(linkshare)
this.$message.success({message: this.$t('list.share.links.deleteSuccess')})
await this.load(listId)
} finally {
this.showDeleteModal = false
}
},
copy,
getShareLink(hash) {

View File

@ -272,39 +272,34 @@ export default {
this.load()
},
methods: {
load() {
this.stuffService
.getAll(this.stuffModel)
.then((r) => {
this.sharables = r
r.forEach((s) =>
this.selectedRight[s.id] = s.right,
)
})
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(() => {
this.showDeleteModal = false
for (const i in this.sharables) {
if (
(this.sharables[i].username === this.stuffModel.userId && this.shareType === 'user') ||
(this.sharables[i].id === this.stuffModel.teamId && this.shareType === 'team')
) {
this.sharables.splice(i, 1)
}
}
this.$message.success({message: this.$t('list.share.userTeam.removeSuccess', {type: this.shareTypeName, sharable: this.sharableName})})
})
await this.stuffService.delete(this.stuffModel)
this.showDeleteModal = false
for (const i in this.sharables) {
if (
(this.sharables[i].username === this.stuffModel.userId && this.shareType === 'user') ||
(this.sharables[i].id === this.stuffModel.teamId && this.shareType === 'team')
) {
this.sharables.splice(i, 1)
}
}
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(() => {
this.$message.success({message: this.$t('list.share.userTeam.addedSuccess', {type: this.shareTypeName})})
this.load()
})
await this.stuffService.create(this.stuffModel)
this.$message.success({message: this.$t('list.share.userTeam.addedSuccess', {type: this.shareTypeName})})
await this.load()
},
toggleType(sharable) {
async toggleType(sharable) {
if (
this.selectedRight[sharable.id] !== rights.ADMIN &&
this.selectedRight[sharable.id] !== rights.READ &&
@ -342,35 +335,30 @@ export default {
this.stuffModel.teamId = sharable.id
}
this.stuffService
.update(this.stuffModel)
.then((r) => {
for (const i in this.sharables) {
if (
(this.sharables[i].username ===
this.stuffModel.userId &&
this.shareType === 'user') ||
(this.sharables[i].id === this.stuffModel.teamId &&
this.shareType === 'team')
) {
this.sharables[i].right = r.right
}
}
this.$message.success({message: this.$t('list.share.userTeam.updatedSuccess', {type: this.shareTypeName})})
})
const r = await this.stuffService.update(this.stuffModel)
for (const i in this.sharables) {
if (
(this.sharables[i].username ===
this.stuffModel.userId &&
this.shareType === 'user') ||
(this.sharables[i].id === this.stuffModel.teamId &&
this.shareType === 'team')
) {
this.sharables[i].right = r.right
}
}
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', {
title: this.newTaskTitle,
listId: this.$store.state.auth.settings.defaultListId,
position: this.defaultPosition,
})
.then(task => {
this.$emit('taskAdded', task)
return task
}),
)
const task = await this.$store.dispatch('tasks/createNewTask', {
title: this.newTaskTitle,
listId: this.$store.state.auth.settings.defaultListId,
position: this.defaultPosition,
})
this.$emit('taskAdded', task)
return task
})
return Promise.all(newTasks)
.then(() => {
this.newTaskTitle = ''
})
.catch(e => {
if (e.message === 'NO_LIST') {
this.errorMessage = this.$t('list.create.addListRequired')
return
}
throw e
})
try {
await Promise.all(newTasks)
this.newTaskTitle = ''
} 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
this.initTaskFields()
this.$message.success({message: this.$t('task.detail.updateSuccess')})
})
async editTaskSubmit() {
this.taskEditTask = await this.taskService.update(this.taskEditTask)
this.initTaskFields()
this.$message.success({message: this.$t('task.detail.updateSuccess')})
},
},
}

View File

@ -297,48 +297,41 @@ 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) => {
if (page < this.taskCollectionService.totalPages) {
return getAllTasks(page + 1).then((nextTasks) => {
return tasks.concat(nextTasks)
})
} else {
return tasks
}
})
const getAllTasks = async (page = 1) => {
const tasks = await this.taskCollectionService.getAll({listId: this.listId}, this.params, page)
if (page < this.taskCollectionService.totalPages) {
const nextTasks = await getAllTasks(page + 1)
return tasks.concat(nextTasks)
}
return tasks
}
getAllTasks()
.then((tasks) => {
this.theTasks = tasks
.filter((t) => {
if (t.startDate === null && !t.done) {
this.tasksWithoutDates.push(t)
}
return (
t.startDate >= this.startDate &&
t.endDate <= this.endDate
)
})
.map((t) => {
return this.addGantAttributes(t)
})
.sort(function (a, b) {
if (a.startDate < b.startDate) return -1
if (a.startDate > b.startDate) return 1
return 0
})
const tasks = await getAllTasks()
this.theTasks = tasks
.filter((t) => {
if (t.startDate === null && !t.done) {
this.tasksWithoutDates.push(t)
}
return (
t.startDate >= this.startDate &&
t.endDate <= this.endDate
)
})
.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) {
@ -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,31 +385,28 @@ export default {
offsetDays: newTask.offsetDays,
}
this.taskService
.update(newTask)
.then(r => {
r.endDate = ganttData.endDate
r.durationDays = ganttData.durationDays
r.offsetDays = ganttData.offsetDays
const r = await this.taskService.update(newTask)
r.endDate = ganttData.endDate
r.durationDays = ganttData.durationDays
r.offsetDays = ganttData.offsetDays
// If the task didn't have dates before, we'll update the list
if (didntHaveDates) {
for (const t in this.tasksWithoutDates) {
if (this.tasksWithoutDates[t].id === r.id) {
this.tasksWithoutDates.splice(t, 1)
break
}
}
this.theTasks.push(this.addGantAttributes(r))
} else {
for (const tt in this.theTasks) {
if (this.theTasks[tt].id === r.id) {
this.theTasks[tt] = this.addGantAttributes(r)
break
}
}
// If the task didn't have dates before, we'll update the list
if (didntHaveDates) {
for (const t in this.tasksWithoutDates) {
if (this.tasksWithoutDates[t].id === r.id) {
this.tasksWithoutDates.splice(t, 1)
break
}
})
}
this.theTasks.push(this.addGantAttributes(r))
} else {
for (const tt in this.theTasks) {
if (this.theTasks[tt].id === r.id) {
this.theTasks[tt] = this.addGantAttributes(r)
break
}
}
}
},
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) => {
this.tasksWithoutDates.push(this.addGantAttributes(r))
this.newTaskTitle = ''
this.hideCrateNewTask()
})
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.currentPage = page
this.loadedList = JSON.parse(JSON.stringify(currentList))
})
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) => {
this.$store.commit(
'attachments/removeById',
this.attachmentToDelete.id,
)
this.$message.success(r)
})
.finally(() => {
this.showDeleteModal = false
})
async deleteAttachment() {
try {
const r = await this.attachmentService.delete(this.attachmentToDelete)
this.$store.commit(
'attachments/removeById',
this.attachmentToDelete.id,
)
this.$message.success(r)
} 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({
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'),
}],
])))
},
},
computed: mapState({
userAvatar: state => state.auth.info.getAvatarUrl(48),
enabled: state => state.config.taskCommentsEnabled,
}),
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)
this.newComment.comment = ''
this.$message.success({message: this.$t('task.comment.addedSuccess')})
this.makeActions()
})
.finally(() => {
this.creating = false
})
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')})
} 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) => {
for (const c in this.comments) {
if (this.comments[c].id === this.commentEdit.id) {
this.comments[c] = 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] = comment
}
this.saved = this.commentEdit.id
setTimeout(() => {
this.saved = null
}, 2000)
})
.finally(() => {
this.isCommentEdit = false
this.saving = null
})
}
this.saved = this.commentEdit.id
setTimeout(() => {
this.saved = null
}, 2000)
} 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(() => {
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'),
},
]
})
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
}
},
},

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)
this.saved = true
setTimeout(() => {
this.saved = false
}, 2000)
})
.finally(() => {
this.saving = false
})
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 {
this.saving = false
}
},
},
}

View File

@ -78,40 +78,40 @@ export default {
},
},
methods: {
addAssignee(user) {
this.$store.dispatch('tasks/addAssignee', {user: user, taskId: this.taskId})
.then(() => {
this.$emit('update:modelValue', this.assignees)
this.$message.success({message: this.$t('task.assignee.assignSuccess')})
})
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(() => {
// Remove the assignee from the list
for (const a in this.assignees) {
if (this.assignees[a].id === user.id) {
this.assignees.splice(a, 1)
}
}
this.$message.success({message: this.$t('task.assignee.unassignSuccess')})
})
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) {
this.assignees.splice(a, 1)
}
}
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 => {
// Filter the results to not include users who are already assigned
this.foundUsers = response.filter(({id}) => !includesById(this.assignees, id))
})
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(() => {
bubble()
if (showNotification) {
this.$message.success({message: this.$t('task.label.addSuccess')})
}
})
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(() => {
removeFromState()
this.$message.success({message: this.$t('task.label.removeSuccess')})
})
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)
this.$message.success({message: this.$t('task.label.addCreateSuccess')})
})
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) => {
this.$emit('update:modelValue', task)
this.showSavedMessage = true
setTimeout(() => {
this.showSavedMessage = false
}, 2000)
})
.finally(() => {
this.saving = false
})
try {
const task = await this.$store.dispatch('tasks/update', newTask)
this.$emit('update:modelValue', task)
this.showSavedMessage = true
setTimeout(() => {
this.showSavedMessage = false
}, 2000)
}
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', {
...task,
done: !task.done,
})
.then(() => {
if (task.done) {
playPop()
}
})
.finally(() => {
this.loadingInternal = false
try {
await this.$store.dispatch('tasks/update', {
...task,
done: !task.done,
})
if (task.done) {
playPop()
}
} 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,58 +188,59 @@ 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(() => {
if (!this.relatedTasks[this.newTaskRelationKind]) {
this.relatedTasks[this.newTaskRelationKind] = []
}
this.relatedTasks[this.newTaskRelationKind].push(this.newTaskRelationTask)
this.newTaskRelationTask = null
this.saved = true
this.showNewRelationForm = false
setTimeout(() => {
this.saved = false
}, 2000)
})
await this.taskRelationService.create(rel)
if (!this.relatedTasks[this.newTaskRelationKind]) {
this.relatedTasks[this.newTaskRelationKind] = []
}
this.relatedTasks[this.newTaskRelationKind].push(this.newTaskRelationTask)
this.newTaskRelationTask = null
this.saved = true
this.showNewRelationForm = false
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) {
this.relatedTasks[relationKind].splice(t, 1)
}
}
})
this.saved = true
setTimeout(() => {
this.saved = false
}, 2000)
})
.finally(() => {
this.showDeleteModal = false
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 {
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 => {
if (this.task.done) {
playPop()
}
this.task = t
this.$emit('task-updated', t)
this.$message.success({
message: this.task.done ?
this.$t('task.doneSuccess') :
this.$t('task.undoneSuccess'),
}, [{
title: 'Undo',
callback: () => {
this.task.done = !this.task.done
this.markAsDone(!checked)
},
}])
})
async markAsDone(checked) {
const updateFunc = async () => {
const task = await this.taskService.update(this.task)
if (this.task.done) {
playPop()
}
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() {
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.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
})
this.task = await this.taskService.update(this.task)
this.$emit('task-updated', this.task)
this.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
},
hideDeferDueDatePopup(e) {
if (this.showDefer) {
closeWhenClickedOutside(e, this.$refs.deferDueDate.$el, () => {
this.showDefer = false
})
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(() => {
this.$message.success({message: this.$t('user.settings.avatar.statusUpdateSuccess')})
this.$store.commit('auth/reloadAvatar')
})
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(() => {
this.$message.success({message: this.$t('user.settings.avatar.setSuccess')})
this.$store.commit('auth/reloadAvatar')
})
.finally(() => {
this.loading = false
this.isCropAvatar = false
})
})
} else {
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 {
this.loading = false
this.isCropAvatar = 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(() => {
this.$message.success({message: this.$t('user.export.success')})
this.password = ''
})
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(() => {
this.$message.success({message: this.$t('user.deletion.requestSuccess')})
this.password = ''
})
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(() => {
this.$message.success({message: this.$t('user.deletion.scheduledCancelSuccess')})
this.$store.dispatch('auth/refreshUserInfo')
this.password = ''
})
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) => {
store.dispatch('tasks/addTaskAttachment', {
taskId,
attachment,
})
onSuccess(generateAttachmentUrl(taskId, attachment.id))
})
}
if (r.errors !== null) {
throw Error(r.errors)
}
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)
}
}
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, {
headers: {
Authorization: `Bearer ${getToken()}`,
},
})
.then(r => {
saveToken(r.data.token, persist)
return r
})
.catch(e => {
throw new Error('Error renewing token: ', { cause: e })
try {
const response = await HTTP.post('user/token', null, {
headers: {
Authorization: `Bearer ${getToken()}`,
},
})
saveToken(response.data.token, persist)
return response
} catch(e) {
throw new Error('Error renewing token: ', { cause: e })
}
}

View File

@ -24,16 +24,12 @@ 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)
// Every time we see a reminder, we schedule a notification for it
this.scheduleNotification(d)
return d
})
})
this.cancelScheduledNotifications().then(() => {
// Every time we see a reminder, we schedule a notification for it
this.reminderDates.forEach(d => this.scheduleNotification(d))
})
// Parse the repeat after into something usable
this.parseRepeatAfter()
@ -218,27 +214,26 @@ export default class TaskModel extends AbstractModel {
}
// Register the actual notification
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,
// eslint-disable-next-line no-undef
showTrigger: new TimestampTrigger(date),
badge: '/images/icons/badge-monochrome.png',
icon: '/images/icons/android-chrome-512x512.png',
data: {taskId: this.id},
actions: [
{
action: 'show-task',
title: 'Show task',
},
],
})
.then(() => {
console.debug('Notification scheduled for ' + date)
})
.catch(e => {
console.debug('Error scheduling notification', e)
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,
// eslint-disable-next-line no-undef
showTrigger: new TimestampTrigger(date),
badge: '/images/icons/badge-monochrome.png',
icon: '/images/icons/android-chrome-512x512.png',
data: {taskId: this.id},
actions: [
{
action: 'show-task',
title: 'Show task',
},
],
})
console.debug('Notification scheduled for ' + date)
} 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 => {
const result = this.modelGetFactory(response.data)
result.maxRight = Number(response.headers['x-max-right'])
return result
})
.finally(() => {
cancel()
})
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 {
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 => {
this.resultCount = Number(response.headers['x-pagination-result-count'])
this.totalPages = Number(response.headers['x-pagination-total-pages'])
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(() => {
cancel()
})
if (Array.isArray(response.data)) {
return response.data.map(entry => this.modelGetAllFactory(entry))
}
return this.modelGetAllFactory(response.data)
} 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 => {
const result = this.modelCreateFactory(response.data)
if (typeof model.maxRight !== 'undefined') {
result.maxRight = model.maxRight
}
return result
})
.finally(() => {
cancel()
})
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 {
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 => {
const result = this.modelUpdateFactory(response.data)
if (typeof model.maxRight !== 'undefined') {
result.maxRight = model.maxRight
}
return result
})
.finally(() => {
cancel()
})
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 {
cancel()
}
}
/**
@ -465,27 +461,28 @@ 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(
url,
formData,
{
headers: {
'Content-Type':
try {
const response = await this.http.put(
url,
formData,
{
headers: {
'Content-Type':
'multipart/form-data; boundary=' + formData._boundary,
},
onUploadProgress: progressEvent => {
this.uploadProgress = Math.round((progressEvent.loaded * 100) / progressEvent.total)
},
},
onUploadProgress: progressEvent => {
this.uploadProgress = Math.round((progressEvent.loaded * 100) / progressEvent.total)
},
},
)
.then(response => this.modelCreateFactory(response.data))
.finally(() => {
this.uploadProgress = 0
cancel()
})
)
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]))
})
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]))
})
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(() => {
cancel()
})
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 => {
return this.modelFactory(response.data)
})
.finally(() => {
cancel()
})
try {
const response = await this.http.post(this.paths.reset, model)
return this.modelFactory(response.data)
} finally {
cancel()
}
}
requestResetPassword(model) {
async requestResetPassword(model) {
const cancel = this.setLoading()
return this.http.post(this.paths.requestReset, model)
.then(response => {
return this.modelFactory(response.data)
})
.finally(() => {
cancel()
})
try {
const response = await this.http.post(this.paths.requestReset, model)
return this.modelFactory(response.data)
} 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,53 +94,51 @@ export default {
data.totp_passcode = credentials.totpPasscode
}
return HTTP.post('login', data)
.then(response => {
// Save the token to local storage for later use
saveToken(response.data.token, true)
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) {
if (
e.response &&
e.response.data.code === 1017 &&
!credentials.totpPasscode
) {
ctx.commit('needsTotpPasscode', true)
}
// Tell others the user is autheticated
ctx.dispatch('checkAuth')
})
.catch(e => {
if (
e.response &&
e.response.data.code === 1017 &&
!credentials.totpPasscode
) {
ctx.commit('needsTotpPasscode', true)
}
throw e
})
.finally(() => {
ctx.commit(LOADING, false, {root: true})
})
throw e
} 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', {
username: credentials.username,
email: credentials.email,
password: credentials.password,
})
.then(() => {
return ctx.dispatch('login', credentials)
try {
await HTTP.post('register', {
username: credentials.username,
email: credentials.email,
password: credentials.password,
})
.catch(e => {
if (e.response && e.response.data && e.response.data.message) {
ctx.commit(ERROR_MESSAGE, e.response.data.message, {root: true})
}
return ctx.dispatch('login', credentials)
} 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(() => {
ctx.commit(LOADING, false, {root: true})
})
throw e
} 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 => {
// Save the token to local storage for later use
saveToken(response.data.token, true)
// Tell others the user is autheticated
ctx.dispatch('checkAuth')
})
.finally(() => {
ctx.commit(LOADING, false, {root: true})
})
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 {
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)
ctx.dispatch('checkAuth')
return r.data
})
saveToken(response.data.token, false)
ctx.dispatch('checkAuth')
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', {
headers: {
Authorization: `Bearer ${jwt}`,
},
})
.then(r => {
const info = new UserModel(r.data)
info.type = ctx.state.info.type
info.email = ctx.state.info.email
info.exp = ctx.state.info.exp
try {
ctx.commit('info', info)
ctx.commit('lastUserRefresh')
})
.catch(e => {
throw new Error('Error while refreshing user info:', { cause: e })
const response = await HTTP.get('user', {
headers: {
Authorization: `Bearer ${jwt}`,
},
})
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')
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(() => {
ctx.dispatch('checkAuth')
})
.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')
}
})
try {
await refreshToken(!ctx.state.isLinkShareAuth)
ctx.dispatch('checkAuth')
} 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)
ctx.commit('setListId', listId)
return r
})
.finally(() => cancel())
try {
const response = await bucketService.getAll({listId: listId}, params)
ctx.commit('setBuckets', response)
ctx.commit('setListId', listId)
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})
ctx.commit('setTasksLoadedForBucketPage', {bucketId, page})
if (taskService.totalPages <= page) {
ctx.commit('setAllTasksLoadedForBucket', bucketId)
}
return r
})
.finally(() => {
cancel()
ctx.commit('setBucketLoading', {bucketId: bucketId, loading: false})
})
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 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 => {
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())
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 response
} finally {
cancel()
}
},
updateBucket(ctx, updatedBucketData) {
async updateBucket(ctx, updatedBucketData) {
const cancel = setLoading(ctx, 'kanban')
const bucketIndex = findIndexById(ctx.state.buckets, updatedBucketData.id)
@ -324,22 +327,22 @@ 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 => {
// restore original state
ctx.commit('setBucketByIndex', {bucketIndex, bucket: oldBucket})
const bucketService = new BucketService()
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())
throw e
} 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(() => {
success({message: i18n.global.t('list.kanban.bucketTitleSavedSuccess')})
})
await ctx.dispatch('updateBucket', updatedBucketData)
success({message: i18n.global.t('list.kanban.bucketTitleSavedSuccess')})
},
},
}

View File

@ -42,65 +42,70 @@ 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(() => {
ctx.commit('setList', list)
ctx.commit('namespaces/setListInNamespaceById', list, {root: true})
// the returned list from listService.update is the same!
// in order to not validate vuex mutations we have to create a new copy
const newList = {
...list,
namespaceId: FavoriteListsNamespace,
}
if (list.isFavorite) {
ctx.commit('namespaces/addListToNamespace', newList, {root: true})
} else {
ctx.commit('namespaces/removeListFromNamespaceById', newList, {root: true})
}
ctx.dispatch('namespaces/loadNamespacesIfFavoritesDontExist', null, {root: true})
ctx.dispatch('namespaces/removeFavoritesNamespaceIfEmpty', null, {root: true})
return newList
try {
await listService.update(list)
ctx.commit('setList', list)
ctx.commit('namespaces/setListInNamespaceById', list, {root: true})
// the returned list from listService.update is the same!
// in order to not validate vuex mutations we have to create a new copy
const newList = {
...list,
namespaceId: FavoriteListsNamespace,
}
if (list.isFavorite) {
ctx.commit('namespaces/addListToNamespace', newList, {root: true})
} else {
ctx.commit('namespaces/removeListFromNamespaceById', newList, {root: true})
}
ctx.dispatch('namespaces/loadNamespacesIfFavoritesDontExist', null, {root: true})
ctx.dispatch('namespaces/removeFavoritesNamespaceIfEmpty', null, {root: true})
return newList
} catch(e) {
// Reset the list state to the initial one to avoid confusion for the user
ctx.commit('setList', {
...list,
isFavorite: !list.isFavorite,
})
.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())
throw e
} finally {
cancel()
}
},
deleteList(ctx, list) {
async deleteList(ctx, list) {
const cancel = setLoading(ctx, 'lists')
const listService = new ListService()
return listService.delete(list)
.then(r => {
ctx.commit('removeListById', list)
ctx.commit('namespaces/removeListFromNamespaceById', list, {root: true})
removeListFromHistory({id: list.id})
return r
})
.finally(() => cancel())
try {
const response = await listService.delete(list)
ctx.commit('removeListById', list)
ctx.commit('namespaces/removeListFromNamespaceById', list, {root: true})
removeListFromHistory({id: list.id})
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()
// We always load all namespaces and filter them on the frontend
return namespaceService.getAll({}, {is_archived: true})
.then(r => {
ctx.commit('namespaces', r)
// Put all lists in the list state
const lists = []
r.forEach(n => {
n.lists.forEach(l => {
lists.push(l)
})
})
ctx.commit('lists/setLists', lists, {root: true})
return r
})
.finally(() => {
cancel()
})
try {
// We always load all namespaces and filter them on the frontend
const namespaces = await namespaceService.getAll({}, {is_archived: true})
ctx.commit('namespaces', namespaces)
// Put all lists in the list state
const lists = namespaces.flatMap(({lists}) => lists)
ctx.commit('lists/setLists', lists, {root: true})
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 => {
ctx.commit('removeNamespaceById', namespace.id)
return r
})
.finally(() => cancel())
try {
const response = await namespaceService.delete(namespace)
ctx.commit('removeNamespaceById', namespace.id)
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 => {
task.labels.push(label)
return result
})
const response = await labelTaskService.create(labelTask)
task.labels.push(label)
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(() => {
cancel()
})
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(() => {
cancel()
})
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 => {
ctx.commit('kanban/removeTaskInBucket', task, {root: true})
return t
})
const response = await taskService.delete(task)
ctx.commit('kanban/removeTaskInBucket', task, {root: true})
return response
},
// Adds a task attachment in store.
// This is an action to be able to commit other mutations
addTaskAttachment(ctx, {taskId, attachment}) {
@ -124,106 +120,97 @@ 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 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 add assignee to task in kanban, task not found', t)
return r
}
// FIXME: direct store manipulation (task)
t.task.assignees.push(user)
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
return 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
// 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 add assignee to task in kanban, task not found', t)
return r
}
// FIXME: direct store manipulation (task)
t.task.assignees.push(user)
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 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
}
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 response
}
for (const a in t.task.assignees) {
if (t.task.assignees[a].id === user.id) {
// FIXME: direct store manipulation (task)
t.task.assignees.splice(a, 1)
break
}
}
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
return r
})
},
addLabel(ctx, {label, taskId}) {
const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
const labelTaskService = new LabelTaskService()
return labelTaskService.create(labelTask)
.then(r => {
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 add label to task in kanban, task not found', t)
return r
}
for (const a in t.task.assignees) {
if (t.task.assignees[a].id === user.id) {
// FIXME: direct store manipulation (task)
t.task.labels.push(label)
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
t.task.assignees.splice(a, 1)
break
}
}
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
return response
return r
})
},
removeLabel(ctx, {label, taskId}) {
async addLabel(ctx, {label, taskId}) {
const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
const labelTaskService = new LabelTaskService()
return labelTaskService.delete(labelTask)
.then(r => {
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
}
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
// 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 add label to task in kanban, task not found', t)
return r
}
// FIXME: direct store manipulation (task)
t.task.labels.push(label)
ctx.commit('kanban/setTaskInBucketByIndex', t, { root: true })
return r
},
// Remove the label from the list
for (const l in t.task.labels) {
if (t.task.labels[l].id === label.id) {
// FIXME: direct store manipulation (task)
t.task.labels.splice(l, 1)
break
}
}
async removeLabel(ctx, {label, taskId}) {
const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
const labelTaskService = new LabelTaskService()
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 response
}
return r
})
// Remove the label from the list
for (const l in t.task.labels) {
if (t.task.labels[l].id === label.id) {
// FIXME: direct store manipulation (task)
t.task.labels.splice(l, 1)
break
}
}
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
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,
parsedLabels:parsedTask.labels,
}))
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')
this.$message.success({message: this.$t('filters.delete.success')})
this.$router.push({name: 'namespaces.index'})
})
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.filters = objectToSnakeCase(this.filter.filters)
})
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')
this.$message.success({message: this.$t('filters.edit.success')})
this.filter = r
this.filters = objectToSnakeCase(this.filter.filters)
this.$router.back()
})
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 = 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 => {
this.$router.push({
name: 'labels.index',
params: {id: r.id},
})
this.$message.success({message: this.$t('label.create.success')})
})
const label = this.$store.dispatch('labels/createLabel', this.label)
this.$router.push({
name: 'labels.index',
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,15 +62,12 @@ export default {
this.showError = false
this.list.namespaceId = parseInt(this.$route.params.id)
this.$store
.dispatch('lists/createList', this.list)
.then((r) => {
this.$message.success({message: this.$t('list.create.createdSuccess') })
this.$router.push({
name: 'list.index',
params: { listId: r.id },
})
})
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: 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(() => {
this.listLoaded = this.$route.params.listId
})
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)
this.$message.success({message: this.$t('list.archive.success')})
})
.finally(() => {
this.$router.back()
})
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 {
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.currentPage = page
this.backgroundService.getAll({}, {s: this.backgroundSearchTerm, p: page})
.then(r => {
this.backgroundSearchResult = this.backgroundSearchResult.concat(r)
r.forEach(b => {
this.backgroundService.thumb(b)
.then(t => {
this.backgroundThumbs[b.id] = t
})
})
})
}, 300)
this.backgroundSearchTimeout = await timeout(300)
this.currentPage = page
const r = await this.backgroundService.getAll({}, {s: this.backgroundSearchTerm, p: page})
this.backgroundSearchResult = this.backgroundSearchResult.concat(r)
r.forEach(async b => {
this.backgroundThumbs[b.id] = await this.backgroundService.thumb(b)
})
},
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)
this.$message.success({message: this.$t('list.background.success')})
})
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)
this.$message.success({message: this.$t('list.background.success')})
})
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)
this.$message.success({message: this.$t('list.background.removeSuccess')})
this.$router.back()
})
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(() => {
this.$message.success({message: this.$t('list.delete.success')})
this.$router.push({name: 'home'})
})
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)
this.$message.success({message: this.$t('list.duplicate.success')})
this.$router.push({name: 'list.index', params: {listId: r.list.id}})
})
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: 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(() => {
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()
})
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 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}))
})
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(() => {
this.taskUpdating[task.id] = false
this.oneTaskUpdating = false
// })
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.scrollTaskContainerToBottom(bucketId)
})
this.newTaskText = ''
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(() => {
this.newBucketTitle = ''
this.showNewBucketInput = false
})
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(() => {
this.bucketTitleEditable = false
this.$message.success({message: this.$t('list.kanban.bucketTitleSavedSuccess')})
})
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)
this.$message.success({message: this.$t('namespace.create.success') })
this.$router.back()
})
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)
this.$message.success({message: this.$t('namespace.archive.success')})
})
.finally(() => {
this.$router.back()
})
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 {
this.$router.back()
}
},
},
}

View File

@ -35,12 +35,10 @@ export default {
},
},
methods: {
deleteNamespace() {
this.$store.dispatch('namespaces/deleteNamespace', this.namespace)
.then(() => {
this.$message.success({message: this.$t('namespace.delete.success')})
this.$router.push({name: 'home'})
})
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 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.setTitle(this.title)
})
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: this.namespace.title})
this.setTitle(this.title)
},
save() {
this.namespaceService.update(this.namespace)
.then(r => {
// Update the namespace in the parent
this.$store.commit('namespaces/setNamespaceById', r)
this.$message.success({message: this.$t('namespace.edit.success')})
this.$router.back()
})
async save() {
const namespace = await this.namespaceService.update(this.namespace)
// Update the namespace in the parent
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 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)
})
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,29 +66,30 @@ 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 => {
if (typeof e.response.data.code !== 'undefined' && e.response.data.code === 13001) {
this.authenticateWithPassword = true
return
}
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
}
// TODO: Put this logic in a global errorMessage handler method which checks all auth codes
let errorMessage = this.$t('sharing.error')
if (e.response && e.response.data && e.response.data.message) {
errorMessage = e.response.data.message
}
if (typeof e.response.data.code !== 'undefined' && e.response.data.code === 13002) {
errorMessage = this.$t('sharing.invalidPassword')
}
this.errorMessage = errorMessage
})
.finally(() => {
this.loading = false
})
// TODO: Put this logic in a global errorMessage handler method which checks all auth codes
let errorMessage = this.$t('sharing.error')
if (e.response && e.response.data && e.response.data.message) {
errorMessage = e.response.data.message
}
if (typeof e.response.data.code !== 'undefined' && e.response.data.code === 13002) {
errorMessage = this.$t('sharing.invalidPassword')
}
this.errorMessage = errorMessage
} 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)
// 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
})
// 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.
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)
this.taskColor = this.task.hexColor
this.setActiveFields()
this.setTitle(this.task.title)
})
.finally(() => {
this.$nextTick(() => this.visible = true)
this.scrollToHeading()
})
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.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(() => {
this.$message.success({message: this.$t('task.detail.deleteSuccess')})
this.$router.push({name: 'list.index', params: {listId: this.task.listId}})
})
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.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
})
this.task = await this.taskService.update(this.task)
this.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
},
},
}

View File

@ -219,93 +219,79 @@ 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.title = this.$t('team.edit.title', {team: this.team.name})
this.setTitle(this.title)
})
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.$message.success({message: this.$t('team.edit.success')})
})
this.team = await this.teamService.update(this.team)
this.$message.success({message: this.$t('team.edit.success')})
},
deleteTeam() {
this.teamService
.delete(this.team)
.then(() => {
this.$message.success({message: this.$t('team.edit.delete.success')})
this.$router.push({name: 'teams.index'})
})
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(() => {
this.$message.success({message: this.$t('team.edit.deleteUser.success')})
this.loadTeam()
})
.finally(() => {
this.showUserDeleteModal = false
})
async deleteUser() {
try {
await this.teamMemberService.delete(this.member)
this.$message.success({message: this.$t('team.edit.deleteUser.success')})
this.loadTeam()
} finally {
this.showUserDeleteModal = false
}
},
addUser() {
async addUser() {
const newMember = new TeamMemberModel({
teamId: this.teamId,
username: this.newMember.username,
})
this.teamMemberService
.create(newMember)
.then(() => {
this.loadTeam()
this.$message.success({message: this.$t('team.edit.userAddedSuccess')})
})
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) => {
for (const tm in this.team.members) {
if (this.team.members[tm].id === member.id) {
this.team.members[tm].admin = r.admin
break
}
}
this.$message.success({
message: member.admin ?
this.$t('team.edit.madeAdmin') :
this.$t('team.edit.madeMember'),
})
})
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
break
}
}
this.$message.success({
message: member.admin ?
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) => {
this.$router.push({
name: 'teams.edit',
params: { id: response.id },
})
this.$message.success({message: this.$t('team.create.success') })
})
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,24 +202,24 @@ export default {
credentials.totpPasscode = this.$refs.totpPasscode.value
}
this.$store.dispatch('auth/login', credentials)
.then(() => {
this.$store.commit('auth/needsTotpPasscode', false)
})
.catch(e => {
if (e.response && e.response.data.code === 1017 && !credentials.totpPasscode) {
return
}
try {
await this.$store.dispatch('auth/login', credentials)
this.$store.commit('auth/needsTotpPasscode', false)
} catch(e) {
if (e.response && e.response.data.code === 1017 && !credentials.totpPasscode) {
return
}
const err = getErrorText(e)
if (typeof err[1] !== 'undefined') {
this.$store.commit(ERROR_MESSAGE, err[1])
return
}
const err = getErrorText(e)
if (typeof err[1] !== 'undefined') {
this.$store.commit(ERROR_MESSAGE, err[1])
return
}
this.$store.commit(ERROR_MESSAGE, err[0])
})
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,34 +59,32 @@ export default {
this.$store.commit(ERROR_MESSAGE, '')
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({
name: last.name,
params: last.params,
})
clearLastVisited()
} else {
this.$router.push({name: 'home'})
}
try {
await this.$store.dispatch('auth/openIdAuth', {
provider: this.$route.params.provider,
code: this.$route.query.code,
})
.catch(e => {
const err = getErrorText(e)
if (typeof err[1] !== 'undefined') {
this.$store.commit(ERROR_MESSAGE, err[1])
return
}
const last = getLastVisited()
if (last !== null) {
this.$router.push({
name: last.name,
params: last.params,
})
clearLastVisited()
} else {
this.$router.push({name: 'home'})
}
} catch(e) {
const err = getErrorText(e)
if (typeof err[1] !== 'undefined') {
this.$store.commit(ERROR_MESSAGE, err[1])
return
}
this.$store.commit(ERROR_MESSAGE, err[0])
})
.finally(() => {
localStorage.removeItem('authenticating')
})
this.$store.commit(ERROR_MESSAGE, err[0])
} 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
localStorage.removeItem('passwordResetToken')
})
.catch(e => {
this.errorMsg = e.response.data.message
})
try {
const { message } = this.passwordResetService.resetPassword(passwordReset)
this.successMessage = message
localStorage.removeItem('passwordResetToken')
} 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(() => {
this.isSuccess = true
})
.catch(e => {
this.errorMsg = e.response.data.message
})
try {
await this.passwordResetService.requestResetPassword(this.passwordReset)
this.isSuccess = true
} catch(e) {
this.errorMsg = e.response.data.message
}
},
},
}

View File

@ -385,84 +385,75 @@ 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(() => {
this.$message.success({message: this.$t('user.settings.passwordUpdateSuccess')})
})
await this.passwordUpdateService.update(this.passwordUpdate)
this.$message.success({message: this.$t('user.settings.passwordUpdateSuccess')})
},
updateEmail() {
this.emailUpdateService.update(this.emailUpdate)
.then(() => {
this.$message.success({message: this.$t('user.settings.updateEmailSuccess')})
})
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
this.totpSetQrCode()
})
.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
return
}
throw e
})
},
totpSetQrCode() {
this.totpService.qrcode()
.then(qr => {
const urlCreator = window.URL || window.webkitURL
this.totpQR = urlCreator.createObjectURL(qr)
})
},
totpEnroll() {
this.totpService.enroll()
.then(r => {
this.totpEnrolled = true
this.totp = r
this.totpSetQrCode()
})
},
totpConfirm() {
this.totpService.enable({passcode: this.totpConfirmPasscode})
.then(() => {
this.totp.enabled = true
this.$message.success({message: this.$t('user.settings.totp.confirmSuccess')})
})
},
totpDisable() {
this.totpService.disable({password: this.totpDisablePassword})
.then(() => {
try {
this.totp = await this.totpService.get()
this.totpSetQrCode()
} 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
this.totp = new TotpModel()
this.$message.success({message: this.$t('user.settings.totp.disableSuccess')})
})
return
}
throw e
}
},
updateSettings() {
async totpSetQrCode() {
const qr = await this.totpService.qrcode()
const urlCreator = window.URL || window.webkitURL
this.totpQR = urlCreator.createObjectURL(qr)
},
async totpEnroll() {
this.totp = await this.totpService.enroll()
this.totpEnrolled = true
this.totpSetQrCode()
},
async totpConfirm() {
await this.totpService.enable({passcode: this.totpConfirmPasscode})
this.totp.enabled = true
this.$message.success({message: this.$t('user.settings.totp.confirmSuccess')})
},
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')})
},
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(() => {
this.$store.commit('auth/setUserSettings', this.settings)
this.$message.success({message: this.$t('user.settings.general.savedSuccess')})
})
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))