feat: use async / await where it makes sense
This commit is contained in:
parent
a776e1d2f3
commit
bb94c1ba3a
74 changed files with 1458 additions and 1662 deletions
35
src/App.vue
35
src/App.vue
|
@ -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')
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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})})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 = []
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
|
|
|
@ -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')})
|
||||
},
|
||||
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
|
|
|
@ -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
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 = ''
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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 = ''
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]))
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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')})
|
||||
},
|
||||
},
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
|
@ -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">
|
||||
|
|
|
@ -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()}})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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'})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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')})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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 },
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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'})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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}})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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}))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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'})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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 = []
|
||||
},
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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') })
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in a new issue