Merge pull request 'feature/vue3-async-await' (#832) from dpschen/frontend:feature/vue3-async-await into vue3
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/832
This commit is contained in:
commit
4e893a3196
87 changed files with 1629 additions and 2247 deletions
|
@ -29,6 +29,7 @@
|
|||
"highlight.js": "11.2.0",
|
||||
"is-touch-device": "1.0.1",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"marked": "3.0.7",
|
||||
"register-service-worker": "1.7.2",
|
||||
"snake-case": "3.0.4",
|
||||
|
|
28
src/App.vue
28
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,29 +89,30 @@ export default defineComponent({
|
|||
window.addEventListener('offline', () => this.$store.commit(ONLINE, navigator.onLine))
|
||||
},
|
||||
setupPasswortResetRedirect() {
|
||||
if (typeof this.$route.query.userPasswordReset !== 'undefined') {
|
||||
localStorage.removeItem('passwordResetToken') // Delete an eventually preexisting old token
|
||||
if (typeof this.$route.query.userPasswordReset === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
localStorage.setItem('passwordResetToken', this.$route.query.userPasswordReset)
|
||||
this.$router.push({name: 'user.password-reset.reset'})
|
||||
}
|
||||
},
|
||||
setupEmailVerificationRedirect() {
|
||||
if (typeof this.$route.query.userEmailConfirm !== 'undefined') {
|
||||
localStorage.removeItem('emailConfirmToken') // Delete an eventually preexisting old token
|
||||
if (typeof this.$route.query.userEmailConfirm === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
localStorage.setItem('emailConfirmToken', this.$route.query.userEmailConfirm)
|
||||
this.$router.push({name: 'user.login'})
|
||||
}
|
||||
},
|
||||
setupAccountDeletionVerification() {
|
||||
if (typeof this.$route.query.accountDeletionConfirm !== 'undefined') {
|
||||
async setupAccountDeletionVerification() {
|
||||
if (typeof this.$route.query.accountDeletionConfirm === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
const accountDeletionService = new AccountDeleteService()
|
||||
accountDeletionService.confirm(this.$route.query.accountDeletionConfirm)
|
||||
.then(() => {
|
||||
await accountDeletionService.confirm(this.$route.query.accountDeletionConfirm)
|
||||
this.$message.success({message: this.$t('user.deletion.confirmSuccess')})
|
||||
this.$store.dispatch('auth/refreshUserInfo')
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -128,9 +128,6 @@ export default {
|
|||
},
|
||||
loadLabels() {
|
||||
this.$store.dispatch('labels/loadAllLabels')
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -54,14 +54,14 @@
|
|||
<span
|
||||
@click="toggleLists(n.id)"
|
||||
class="menu-label"
|
||||
v-tooltip="getNamespaceTitle(n) + ' (' + n.lists.filter(l => !l.isArchived).length + ')'">
|
||||
v-tooltip="namespaceTitles[nk]">
|
||||
<span class="name">
|
||||
<span
|
||||
:style="{ backgroundColor: n.hexColor }"
|
||||
class="color-bubble"
|
||||
v-if="n.hexColor !== ''">
|
||||
</span>
|
||||
{{ getNamespaceTitle(n) }} ({{ n.lists.filter(l => !l.isArchived).length }})
|
||||
{{ namespaceTitles[nk] }}
|
||||
</span>
|
||||
</span>
|
||||
<a
|
||||
|
@ -117,7 +117,7 @@
|
|||
@click="navigate"
|
||||
:href="href"
|
||||
class="list-menu-link"
|
||||
:class="{'router-link-exact-active': isActive || currentList.id === l.id}"
|
||||
:class="{'router-link-exact-active': isActive || currentList?.id === l.id}"
|
||||
>
|
||||
<span class="icon handle">
|
||||
<icon icon="grip-lines"/>
|
||||
|
@ -191,10 +191,17 @@ export default {
|
|||
loading: state => state[LOADING] && state[LOADING_MODULE] === 'namespaces',
|
||||
}),
|
||||
activeLists() {
|
||||
return this.namespaces.map(({lists}) => lists.filter(item => !item.isArchived))
|
||||
return this.namespaces.map(({lists}) => lists?.filter(item => !item.isArchived))
|
||||
},
|
||||
namespaceTitles() {
|
||||
return this.namespaces.map((namespace, index) => {
|
||||
const title = this.getNamespaceTitle(namespace)
|
||||
return `${title} (${this.activeLists[index]?.length ?? 0})`
|
||||
})
|
||||
},
|
||||
},
|
||||
beforeCreate() {
|
||||
// FIXME: async action in beforeCreate, might be unfinished when component mounts
|
||||
this.$store.dispatch('namespaces/loadNamespaces')
|
||||
.then(namespaces => {
|
||||
namespaces.forEach(n => {
|
||||
|
@ -218,18 +225,13 @@ export default {
|
|||
return
|
||||
}
|
||||
this.$store.dispatch('lists/toggleListFavorite', list)
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
resize() {
|
||||
// Hide the menu by default on mobile
|
||||
if (window.innerWidth < 770) {
|
||||
this.$store.commit(MENU_ACTIVE, false)
|
||||
} else {
|
||||
this.$store.commit(MENU_ACTIVE, true)
|
||||
}
|
||||
this.$store.commit(MENU_ACTIVE, window.innerWidth >= 770)
|
||||
},
|
||||
toggleLists(namespaceId) {
|
||||
this.listsVisible[namespaceId] = !this.listsVisible[namespaceId] ?? false
|
||||
this.listsVisible[namespaceId] = !this.listsVisible[namespaceId]
|
||||
},
|
||||
updateActiveLists(namespace, activeLists) {
|
||||
// this is a bit hacky: since we do have to filter out the archived items from the list
|
||||
|
@ -249,7 +251,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
|
||||
|
@ -258,17 +261,15 @@ export default {
|
|||
|
||||
const position = calculateItemPosition(listBefore !== null ? listBefore.position : null, listAfter !== null ? listAfter.position : null)
|
||||
|
||||
try {
|
||||
// create a copy of the list in order to not violate vuex mutations
|
||||
this.$store.dispatch('lists/updateList', {
|
||||
await this.$store.dispatch('lists/updateList', {
|
||||
...list,
|
||||
position,
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
} 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 => {
|
||||
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,13 +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
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
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) {
|
||||
|
@ -524,20 +522,16 @@ export default {
|
|||
clear(kind) {
|
||||
this[`found${kind}`] = []
|
||||
},
|
||||
find(kind, query) {
|
||||
async find(kind, query) {
|
||||
|
||||
if (query === '') {
|
||||
this.clear(kind)
|
||||
}
|
||||
|
||||
this[`${kind}Service`].getAll({}, {s: query})
|
||||
.then(response => {
|
||||
const response = await this[`${kind}Service`].getAll({}, {s: query})
|
||||
|
||||
// Filter users from the results who are already assigned
|
||||
this[`found${kind}`] = response.filter(({id}) => !includesById(this[kind], id))
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
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,14 +64,11 @@ export default {
|
|||
this.backgroundLoading = true
|
||||
|
||||
const listService = new ListService()
|
||||
listService.background(this.list)
|
||||
.then(b => {
|
||||
this.background = b
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.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
|
||||
|
@ -80,7 +77,6 @@ export default {
|
|||
return
|
||||
}
|
||||
this.$store.dispatch('lists/toggleListFavorite', list)
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -138,58 +138,35 @@ export default {
|
|||
}
|
||||
this.migrate()
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getAuthUrl() {
|
||||
this.migrationService.getAuthUrl()
|
||||
.then(r => {
|
||||
this.authUrl = r.url
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
async getAuthUrl() {
|
||||
const { url } = await this.migrationService.getAuthUrl()
|
||||
this.authUrl = url
|
||||
},
|
||||
migrate() {
|
||||
|
||||
async migrate() {
|
||||
this.isMigrating = true
|
||||
this.lastMigrationDate = null
|
||||
this.message = ''
|
||||
|
||||
if (this.isFileMigrator) {
|
||||
return this.migrateFile()
|
||||
}
|
||||
let migrationConfig = { code: this.migratorAuthCode }
|
||||
|
||||
this.migrationService.migrate({code: this.migratorAuthCode})
|
||||
.then(r => {
|
||||
this.message = r.message
|
||||
this.$store.dispatch('namespaces/loadNamespaces')
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
this.isMigrating = false
|
||||
})
|
||||
},
|
||||
migrateFile() {
|
||||
if (this.isFileMigrator) {
|
||||
if (this.$refs.uploadInput.files.length === 0) {
|
||||
return
|
||||
}
|
||||
migrationConfig = this.$refs.uploadInput.files[0]
|
||||
}
|
||||
|
||||
this.migrationService.migrate(this.$refs.uploadInput.files[0])
|
||||
.then(r => {
|
||||
this.message = r.message
|
||||
this.$store.dispatch('namespaces/loadNamespaces')
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
try {
|
||||
const { message } = await this.migrationService.migrate(migrationConfig)
|
||||
this.message = message
|
||||
return this.$store.dispatch('namespaces/loadNamespaces')
|
||||
} finally {
|
||||
this.isMigrating = false
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ export default {
|
|||
window.API_URL = urlToCheck.toString()
|
||||
return this.$store.dispatch('config/update')
|
||||
}
|
||||
return Promise.reject(e)
|
||||
throw e
|
||||
})
|
||||
.catch((e) => {
|
||||
// Check if it has a port and if not check if it is reachable at https
|
||||
|
@ -115,7 +115,7 @@ export default {
|
|||
window.API_URL = urlToCheck.toString()
|
||||
return this.$store.dispatch('config/update')
|
||||
}
|
||||
return Promise.reject(e)
|
||||
throw e
|
||||
})
|
||||
.catch((e) => {
|
||||
// Check if it is reachable at /api/v1 and https
|
||||
|
@ -128,7 +128,7 @@ export default {
|
|||
window.API_URL = urlToCheck.toString()
|
||||
return this.$store.dispatch('config/update')
|
||||
}
|
||||
return Promise.reject(e)
|
||||
throw e
|
||||
})
|
||||
.catch((e) => {
|
||||
// Check if it is reachable at port API_DEFAULT_PORT and https
|
||||
|
@ -138,7 +138,7 @@ export default {
|
|||
window.API_URL = urlToCheck.toString()
|
||||
return this.$store.dispatch('config/update')
|
||||
}
|
||||
return Promise.reject(e)
|
||||
throw e
|
||||
})
|
||||
.catch((e) => {
|
||||
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1 and https
|
||||
|
@ -151,7 +151,7 @@ export default {
|
|||
window.API_URL = urlToCheck.toString()
|
||||
return this.$store.dispatch('config/update')
|
||||
}
|
||||
return Promise.reject(e)
|
||||
throw e
|
||||
})
|
||||
.catch((e) => {
|
||||
// Check if it is reachable at port API_DEFAULT_PORT and http
|
||||
|
@ -161,7 +161,7 @@ export default {
|
|||
window.API_URL = urlToCheck.toString()
|
||||
return this.$store.dispatch('config/update')
|
||||
}
|
||||
return Promise.reject(e)
|
||||
throw e
|
||||
})
|
||||
.catch((e) => {
|
||||
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1 and http
|
||||
|
@ -174,7 +174,7 @@ export default {
|
|||
window.API_URL = urlToCheck.toString()
|
||||
return this.$store.dispatch('config/update')
|
||||
}
|
||||
return Promise.reject(e)
|
||||
throw e
|
||||
})
|
||||
.catch(() => {
|
||||
// Still not found, url is still invalid
|
||||
|
|
|
@ -89,33 +89,23 @@ export default {
|
|||
this.unsubscribe()
|
||||
}
|
||||
},
|
||||
subscribe() {
|
||||
async subscribe() {
|
||||
const subscription = new SubscriptionModel({
|
||||
entity: this.entity,
|
||||
entityId: this.entityId,
|
||||
})
|
||||
this.subscriptionService.create(subscription)
|
||||
.then(() => {
|
||||
await this.subscriptionService.create(subscription)
|
||||
this.$emit('change', subscription)
|
||||
this.$message.success({message: this.$t('task.subscription.subscribeSuccess', {entity: this.entity})})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
unsubscribe() {
|
||||
async unsubscribe() {
|
||||
const subscription = new SubscriptionModel({
|
||||
entity: this.entity,
|
||||
entityId: this.entityId,
|
||||
})
|
||||
this.subscriptionService.delete(subscription)
|
||||
.then(() => {
|
||||
await this.subscriptionService.delete(subscription)
|
||||
this.$emit('change', null)
|
||||
this.$message.success({message: this.$t('task.subscription.unsubscribeSuccess', {entity: this.entity})})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -93,14 +93,8 @@ export default {
|
|||
closeWhenClickedOutside(e, this.$refs.popup, () => this.showNotifications = false)
|
||||
}
|
||||
},
|
||||
loadNotifications() {
|
||||
this.notificationService.getAll()
|
||||
.then(r => {
|
||||
this.allNotifications = r
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
async loadNotifications() {
|
||||
this.allNotifications = await this.notificationService.getAll()
|
||||
},
|
||||
to(n, index) {
|
||||
const to = {
|
||||
|
@ -127,17 +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
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
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 => {
|
||||
this.taskSearchTimeout = setTimeout(async () => {
|
||||
const r = await this.taskService.getAll({}, {s: query})
|
||||
this.foundTasks = r.map(t => {
|
||||
t.type = TYPE_TASK
|
||||
const list = this.$store.getters['lists/getListById'](t.listId) === null ? null : this.$store.getters['lists/getListById'](t.listId)
|
||||
const list = this.$store.getters['lists/getListById'](t.listId)
|
||||
if (list !== null) {
|
||||
t.title = `${t.title} (${list.title})`
|
||||
}
|
||||
|
||||
return t
|
||||
})
|
||||
this.foundTasks = r
|
||||
})
|
||||
}, 150)
|
||||
},
|
||||
searchTeams() {
|
||||
|
@ -318,15 +315,12 @@ export default {
|
|||
this.teamSearchTimeout = null
|
||||
}
|
||||
|
||||
this.teamSearchTimeout = setTimeout(() => {
|
||||
this.teamService.getAll({}, {s: query})
|
||||
.then(r => {
|
||||
r = r.map(t => {
|
||||
this.teamSearchTimeout = setTimeout(async () => {
|
||||
const r = await this.teamService.getAll({}, {s: query})
|
||||
this.foundTeams = r.map(t => {
|
||||
t.title = t.name
|
||||
return t
|
||||
})
|
||||
this.foundTeams = r
|
||||
})
|
||||
}, 150)
|
||||
},
|
||||
closeQuickActions() {
|
||||
|
@ -378,25 +372,20 @@ export default {
|
|||
break
|
||||
}
|
||||
},
|
||||
newTask() {
|
||||
async newTask() {
|
||||
if (this.currentList === null) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$store.dispatch('tasks/createNewTask', {
|
||||
const task = await this.$store.dispatch('tasks/createNewTask', {
|
||||
title: this.query,
|
||||
listId: this.currentList.id,
|
||||
})
|
||||
.then(r => {
|
||||
this.$message.success({message: this.$t('task.createSuccess')})
|
||||
this.$router.push({name: 'task.detail', params: {id: r.id}})
|
||||
this.$router.push({name: 'task.detail', params: {id: task.id}})
|
||||
this.closeQuickActions()
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
newList() {
|
||||
async newList() {
|
||||
if (this.currentList === null) {
|
||||
return
|
||||
}
|
||||
|
@ -405,42 +394,27 @@ export default {
|
|||
title: this.query,
|
||||
namespaceId: this.currentList.namespaceId,
|
||||
})
|
||||
this.$store.dispatch('lists/createList', newList)
|
||||
.then(r => {
|
||||
const list = await this.$store.dispatch('lists/createList', newList)
|
||||
this.$message.success({message: this.$t('list.create.createdSuccess')})
|
||||
this.$router.push({name: 'list.index', params: {listId: r.id}})
|
||||
this.$router.push({name: 'list.index', params: {listId: list.id}})
|
||||
this.closeQuickActions()
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
newNamespace() {
|
||||
async newNamespace() {
|
||||
const newNamespace = new NamespaceModel({title: this.query})
|
||||
|
||||
this.$store.dispatch('namespaces/createNamespace', newNamespace)
|
||||
.then(() => {
|
||||
await this.$store.dispatch('namespaces/createNamespace', newNamespace)
|
||||
this.$message.success({message: this.$t('namespace.create.success')})
|
||||
this.closeQuickActions()
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
newTeam() {
|
||||
async newTeam() {
|
||||
const newTeam = new TeamModel({name: this.query})
|
||||
this.teamService.create(newTeam)
|
||||
.then(r => {
|
||||
const team = await this.teamService.create(newTeam)
|
||||
this.$router.push({
|
||||
name: 'teams.edit',
|
||||
params: {id: r.id},
|
||||
params: {id: team.id},
|
||||
})
|
||||
this.$message.success({message: this.$t('team.create.success')})
|
||||
this.closeQuickActions()
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
select(parentIndex, index) {
|
||||
|
||||
|
|
|
@ -215,59 +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
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
this.linkShares = await this.linkShareService.getAll({listId})
|
||||
},
|
||||
add(listId) {
|
||||
async add(listId) {
|
||||
const newLinkShare = new LinkShareModel({
|
||||
right: this.selectedRight,
|
||||
listId,
|
||||
name: this.name,
|
||||
password: this.password,
|
||||
})
|
||||
this.linkShareService
|
||||
.create(newLinkShare)
|
||||
.then(() => {
|
||||
await this.linkShareService.create(newLinkShare)
|
||||
this.selectedRight = rights.READ
|
||||
this.name = ''
|
||||
this.password = ''
|
||||
this.showNewForm = false
|
||||
this.$message.success({message: this.$t('list.share.links.createSuccess')})
|
||||
this.load(listId)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
await this.load(listId)
|
||||
},
|
||||
remove(listId) {
|
||||
async remove(listId) {
|
||||
const linkshare = new LinkShareModel({
|
||||
id: this.linkIdToDelete,
|
||||
listId,
|
||||
})
|
||||
this.linkShareService
|
||||
.delete(linkshare)
|
||||
.then(() => {
|
||||
try {
|
||||
await this.linkShareService.delete(linkshare)
|
||||
this.$message.success({message: this.$t('list.share.links.deleteSuccess')})
|
||||
this.load(listId)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
await this.load(listId)
|
||||
} finally {
|
||||
this.showDeleteModal = false
|
||||
})
|
||||
}
|
||||
},
|
||||
copy,
|
||||
getShareLink(hash) {
|
||||
|
|
|
@ -272,29 +272,21 @@ export default {
|
|||
this.load()
|
||||
},
|
||||
methods: {
|
||||
load() {
|
||||
this.stuffService
|
||||
.getAll(this.stuffModel)
|
||||
.then((r) => {
|
||||
this.sharables = r
|
||||
r.forEach((s) =>
|
||||
async load() {
|
||||
this.sharables = await this.stuffService.getAll(this.stuffModel)
|
||||
this.sharables.forEach((s) =>
|
||||
this.selectedRight[s.id] = s.right,
|
||||
)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
deleteSharable() {
|
||||
|
||||
async deleteSharable() {
|
||||
if (this.shareType === 'user') {
|
||||
this.stuffModel.userId = this.sharable.username
|
||||
} else if (this.shareType === 'team') {
|
||||
this.stuffModel.teamId = this.sharable.id
|
||||
}
|
||||
|
||||
this.stuffService
|
||||
.delete(this.stuffModel)
|
||||
.then(() => {
|
||||
await this.stuffService.delete(this.stuffModel)
|
||||
this.showDeleteModal = false
|
||||
for (const i in this.sharables) {
|
||||
if (
|
||||
|
@ -305,12 +297,9 @@ export default {
|
|||
}
|
||||
}
|
||||
this.$message.success({message: this.$t('list.share.userTeam.removeSuccess', {type: this.shareTypeName, sharable: this.sharableName})})
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
add(admin) {
|
||||
|
||||
async add(admin) {
|
||||
if (admin === null) {
|
||||
admin = false
|
||||
}
|
||||
|
@ -325,17 +314,12 @@ export default {
|
|||
this.stuffModel.teamId = this.sharable.id
|
||||
}
|
||||
|
||||
this.stuffService
|
||||
.create(this.stuffModel)
|
||||
.then(() => {
|
||||
await this.stuffService.create(this.stuffModel)
|
||||
this.$message.success({message: this.$t('list.share.userTeam.addedSuccess', {type: this.shareTypeName})})
|
||||
this.load()
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
await this.load()
|
||||
},
|
||||
toggleType(sharable) {
|
||||
|
||||
async toggleType(sharable) {
|
||||
if (
|
||||
this.selectedRight[sharable.id] !== rights.ADMIN &&
|
||||
this.selectedRight[sharable.id] !== rights.READ &&
|
||||
|
@ -351,9 +335,7 @@ export default {
|
|||
this.stuffModel.teamId = sharable.id
|
||||
}
|
||||
|
||||
this.stuffService
|
||||
.update(this.stuffModel)
|
||||
.then((r) => {
|
||||
const r = await this.stuffService.update(this.stuffModel)
|
||||
for (const i in this.sharables) {
|
||||
if (
|
||||
(this.sharables[i].username ===
|
||||
|
@ -366,26 +348,17 @@ export default {
|
|||
}
|
||||
}
|
||||
this.$message.success({message: this.$t('list.share.userTeam.updatedSuccess', {type: this.shareTypeName})})
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
find(query) {
|
||||
|
||||
async find(query) {
|
||||
if (query === '') {
|
||||
this.clearAll()
|
||||
return
|
||||
}
|
||||
|
||||
this.searchService
|
||||
.getAll({}, {s: query})
|
||||
.then((response) => {
|
||||
this.found = response
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
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', {
|
||||
const task = await this.$store.dispatch('tasks/createNewTask', {
|
||||
title: this.newTaskTitle,
|
||||
listId: this.$store.state.auth.settings.defaultListId,
|
||||
position: this.defaultPosition,
|
||||
})
|
||||
.then(task => {
|
||||
this.$emit('taskAdded', task)
|
||||
return task
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
Promise.all(newTasks)
|
||||
.then(() => {
|
||||
try {
|
||||
await Promise.all(newTasks)
|
||||
this.newTaskTitle = ''
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 'NO_LIST') {
|
||||
} catch(e) {
|
||||
if (e.message === 'NO_LIST') {
|
||||
this.errorMessage = this.$t('list.create.addListRequired')
|
||||
return
|
||||
}
|
||||
this.$message.error(e)
|
||||
})
|
||||
throw e
|
||||
}
|
||||
},
|
||||
handleEnter(e) {
|
||||
// when pressing shift + enter we want to continue as we normally would. Otherwise, we want to create
|
||||
|
|
|
@ -134,17 +134,10 @@ export default {
|
|||
this.editorActive = false
|
||||
this.$nextTick(() => (this.editorActive = true))
|
||||
},
|
||||
editTaskSubmit() {
|
||||
this.taskService
|
||||
.update(this.taskEditTask)
|
||||
.then((r) => {
|
||||
this.taskEditTask = r
|
||||
async editTaskSubmit() {
|
||||
this.taskEditTask = await this.taskService.update(this.taskEditTask)
|
||||
this.initTaskFields()
|
||||
this.$message.success({message: this.$t('task.detail.updateSuccess')})
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -297,33 +297,26 @@ export default {
|
|||
console.debug('prepareGanttDays; years:', years)
|
||||
this.days = years
|
||||
},
|
||||
|
||||
parseTasks() {
|
||||
this.setDates()
|
||||
this.loadTasks()
|
||||
},
|
||||
loadTasks() {
|
||||
|
||||
async loadTasks() {
|
||||
this.theTasks = []
|
||||
this.tasksWithoutDates = []
|
||||
|
||||
const getAllTasks = (page = 1) => {
|
||||
return this.taskCollectionService
|
||||
.getAll({listId: this.listId}, this.params, page)
|
||||
.then((tasks) => {
|
||||
const getAllTasks = async (page = 1) => {
|
||||
const tasks = await this.taskCollectionService.getAll({listId: this.listId}, this.params, page)
|
||||
if (page < this.taskCollectionService.totalPages) {
|
||||
return getAllTasks(page + 1).then((nextTasks) => {
|
||||
const nextTasks = await getAllTasks(page + 1)
|
||||
return tasks.concat(nextTasks)
|
||||
})
|
||||
} else {
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
}
|
||||
|
||||
getAllTasks()
|
||||
.then((tasks) => {
|
||||
const tasks = await getAllTasks()
|
||||
this.theTasks = tasks
|
||||
.filter((t) => {
|
||||
if (t.startDate === null && !t.done) {
|
||||
|
@ -334,18 +327,12 @@ export default {
|
|||
t.endDate <= this.endDate
|
||||
)
|
||||
})
|
||||
.map((t) => {
|
||||
return this.addGantAttributes(t)
|
||||
})
|
||||
.map((t) => this.addGantAttributes(t))
|
||||
.sort(function (a, b) {
|
||||
if (a.startDate < b.startDate) return -1
|
||||
if (a.startDate > b.startDate) return 1
|
||||
return 0
|
||||
})
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
addGantAttributes(t) {
|
||||
if (typeof t.durationDays !== 'undefined' && typeof t.offsetDays !== 'undefined') {
|
||||
|
@ -357,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
|
||||
}
|
||||
|
@ -398,9 +385,7 @@ export default {
|
|||
offsetDays: newTask.offsetDays,
|
||||
}
|
||||
|
||||
this.taskService
|
||||
.update(newTask)
|
||||
.then(r => {
|
||||
const r = await this.taskService.update(newTask)
|
||||
r.endDate = ganttData.endDate
|
||||
r.durationDays = ganttData.durationDays
|
||||
r.offsetDays = ganttData.offsetDays
|
||||
|
@ -422,10 +407,6 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
editTask(task) {
|
||||
this.taskToEdit = task
|
||||
|
@ -445,7 +426,7 @@ export default {
|
|||
this.$nextTick(() => (this.newTaskFieldActive = false))
|
||||
}
|
||||
},
|
||||
addNewTask() {
|
||||
async addNewTask() {
|
||||
if (!this.newTaskFieldActive) {
|
||||
return
|
||||
}
|
||||
|
@ -453,16 +434,10 @@ export default {
|
|||
title: this.newTaskTitle,
|
||||
listId: this.listId,
|
||||
})
|
||||
this.taskService
|
||||
.create(task)
|
||||
.then((r) => {
|
||||
const r = await this.taskService.create(task)
|
||||
this.tasksWithoutDates.push(this.addGantAttributes(r))
|
||||
this.newTaskTitle = ''
|
||||
this.hideCrateNewTask()
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
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,17 +76,9 @@ export default {
|
|||
}
|
||||
|
||||
this.tasks = []
|
||||
|
||||
this.taskCollectionService.getAll(list, params, page)
|
||||
.then(r => {
|
||||
this.tasks = r
|
||||
this.tasks = await this.taskCollectionService.getAll(list, params, page)
|
||||
this.currentPage = page
|
||||
|
||||
this.loadedList = JSON.parse(JSON.stringify(currentList))
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
|
||||
loadTasksForPage(e) {
|
||||
|
|
|
@ -218,24 +218,19 @@ export default {
|
|||
uploadFiles(files) {
|
||||
uploadFiles(this.attachmentService, this.taskId, files)
|
||||
},
|
||||
deleteAttachment() {
|
||||
this.attachmentService
|
||||
.delete(this.attachmentToDelete)
|
||||
.then((r) => {
|
||||
async deleteAttachment() {
|
||||
try {
|
||||
const r = await this.attachmentService.delete(this.attachmentToDelete)
|
||||
this.$store.commit(
|
||||
'attachments/removeById',
|
||||
this.attachmentToDelete.id,
|
||||
)
|
||||
this.$message.success(r)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
} finally{
|
||||
this.showDeleteModal = false
|
||||
})
|
||||
}
|
||||
},
|
||||
viewOrDownload(attachment) {
|
||||
async viewOrDownload(attachment) {
|
||||
if (
|
||||
attachment.file.name.endsWith('.jpg') ||
|
||||
attachment.file.name.endsWith('.png') ||
|
||||
|
@ -243,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,43 +194,46 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
taskId: {
|
||||
handler(taskId) {
|
||||
if (!this.enabled) {
|
||||
return
|
||||
}
|
||||
|
||||
this.loadComments()
|
||||
this.newComment.taskId = taskId
|
||||
this.commentEdit.taskId = taskId
|
||||
this.commentToDelete.taskId = taskId
|
||||
},
|
||||
handler: 'loadComments',
|
||||
immediate: true,
|
||||
},
|
||||
canWrite() {
|
||||
this.makeActions()
|
||||
},
|
||||
},
|
||||
computed: mapState({
|
||||
computed: {
|
||||
...mapState({
|
||||
userAvatar: state => state.auth.info.getAvatarUrl(48),
|
||||
enabled: state => state.config.taskCommentsEnabled,
|
||||
}),
|
||||
actions() {
|
||||
if (!this.canWrite) {
|
||||
return {}
|
||||
}
|
||||
return Object.fromEntries(this.comments.map((c) => ([
|
||||
c.id,
|
||||
[{
|
||||
action: () => this.toggleDelete(c.id),
|
||||
title: this.$t('misc.delete'),
|
||||
}],
|
||||
])))
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
attachmentUpload(...args) {
|
||||
return uploadFile(this.taskId, ...args)
|
||||
},
|
||||
|
||||
loadComments() {
|
||||
this.taskCommentService
|
||||
.getAll({taskId: this.taskId})
|
||||
.then(r => {
|
||||
this.comments = r
|
||||
this.makeActions()
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
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
|
||||
}
|
||||
|
@ -245,30 +247,27 @@ export default {
|
|||
this.$nextTick(() => (this.editorActive = true))
|
||||
this.creating = true
|
||||
|
||||
this.taskCommentService
|
||||
.create(this.newComment)
|
||||
.then((r) => {
|
||||
this.comments.push(r)
|
||||
try {
|
||||
const comment = await this.taskCommentService.create(this.newComment)
|
||||
this.comments.push(comment)
|
||||
this.newComment.comment = ''
|
||||
this.$message.success({message: this.$t('task.comment.addedSuccess')})
|
||||
this.makeActions()
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
} finally {
|
||||
this.creating = false
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
toggleEdit(comment) {
|
||||
this.isCommentEdit = !this.isCommentEdit
|
||||
this.commentEdit = comment
|
||||
},
|
||||
|
||||
toggleDelete(commentId) {
|
||||
this.showDeleteModal = !this.showDeleteModal
|
||||
this.commentToDelete.id = commentId
|
||||
},
|
||||
editComment() {
|
||||
|
||||
async editComment() {
|
||||
if (this.commentEdit.comment === '') {
|
||||
return
|
||||
}
|
||||
|
@ -276,54 +275,30 @@ export default {
|
|||
this.saving = this.commentEdit.id
|
||||
|
||||
this.commentEdit.taskId = this.taskId
|
||||
this.taskCommentService
|
||||
.update(this.commentEdit)
|
||||
.then((r) => {
|
||||
try {
|
||||
const comment = this.taskCommentService.update(this.commentEdit)
|
||||
for (const c in this.comments) {
|
||||
if (this.comments[c].id === this.commentEdit.id) {
|
||||
this.comments[c] = r
|
||||
this.comments[c] = comment
|
||||
}
|
||||
}
|
||||
this.saved = this.commentEdit.id
|
||||
setTimeout(() => {
|
||||
this.saved = null
|
||||
}, 2000)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
} finally {
|
||||
this.isCommentEdit = false
|
||||
this.saving = null
|
||||
})
|
||||
}
|
||||
},
|
||||
deleteComment() {
|
||||
this.taskCommentService
|
||||
.delete(this.commentToDelete)
|
||||
.then(() => {
|
||||
for (const a in this.comments) {
|
||||
if (this.comments[a].id === this.commentToDelete.id) {
|
||||
this.comments.splice(a, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
async deleteComment(commentToDelete) {
|
||||
try {
|
||||
await this.taskCommentService.delete(commentToDelete)
|
||||
const index = this.comments.findIndex(({id}) => id === commentToDelete.id)
|
||||
this.comments.splice(index, 1)
|
||||
} finally {
|
||||
this.showDeleteModal = false
|
||||
})
|
||||
},
|
||||
makeActions() {
|
||||
if (this.canWrite) {
|
||||
this.comments.forEach((c) => {
|
||||
this.actions[c.id] = [
|
||||
{
|
||||
action: () => this.toggleDelete(c.id),
|
||||
title: this.$t('misc.delete'),
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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,16 +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)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
const task = await this.taskService.update(this.task)
|
||||
this.lastValue = task.dueDate
|
||||
this.task = task
|
||||
this.$emit('update:modelValue', task)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -71,24 +71,19 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
save() {
|
||||
async save() {
|
||||
this.saving = true
|
||||
|
||||
this.$store.dispatch('tasks/update', this.task)
|
||||
.then(t => {
|
||||
this.task = t
|
||||
this.$emit('update:modelValue', t)
|
||||
try {
|
||||
this.task = await this.$store.dispatch('tasks/update', this.task)
|
||||
this.$emit('update:modelValue', this.task)
|
||||
this.saved = true
|
||||
setTimeout(() => {
|
||||
this.saved = false
|
||||
}, 2000)
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
} finally {
|
||||
this.saving = false
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -78,19 +78,15 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
addAssignee(user) {
|
||||
this.$store.dispatch('tasks/addAssignee', {user: user, taskId: this.taskId})
|
||||
.then(() => {
|
||||
async addAssignee(user) {
|
||||
await this.$store.dispatch('tasks/addAssignee', {user: user, taskId: this.taskId})
|
||||
this.$emit('update:modelValue', this.assignees)
|
||||
this.$message.success({message: this.$t('task.assignee.assignSuccess')})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
removeAssignee(user) {
|
||||
this.$store.dispatch('tasks/removeAssignee', {user: user, taskId: this.taskId})
|
||||
.then(() => {
|
||||
|
||||
async removeAssignee(user) {
|
||||
await this.$store.dispatch('tasks/removeAssignee', {user: user, taskId: this.taskId})
|
||||
|
||||
// Remove the assignee from the list
|
||||
for (const a in this.assignees) {
|
||||
if (this.assignees[a].id === user.id) {
|
||||
|
@ -98,29 +94,24 @@ export default {
|
|||
}
|
||||
}
|
||||
this.$message.success({message: this.$t('task.assignee.unassignSuccess')})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
findUser(query) {
|
||||
|
||||
async findUser(query) {
|
||||
if (query === '') {
|
||||
this.clearAllFoundUsers()
|
||||
return
|
||||
}
|
||||
|
||||
this.listUserService.getAll({listId: this.listId}, {s: query})
|
||||
.then(response => {
|
||||
const response = await this.listUserService.getAll({listId: this.listId}, {s: query})
|
||||
|
||||
// Filter the results to not include users who are already assigned
|
||||
this.foundUsers = response.filter(({id}) => !includesById(this.assignees, id))
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
|
||||
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,18 +105,14 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
this.$store.dispatch('tasks/addLabel', {label: label, taskId: this.taskId})
|
||||
.then(() => {
|
||||
await this.$store.dispatch('tasks/addLabel', {label: label, taskId: this.taskId})
|
||||
bubble()
|
||||
if (showNotification) {
|
||||
this.$message.success({message: this.$t('task.label.addSuccess')})
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
removeLabel(label) {
|
||||
|
||||
async removeLabel(label) {
|
||||
const removeFromState = () => {
|
||||
for (const l in this.labels) {
|
||||
if (this.labels[l].id === label.id) {
|
||||
|
@ -131,30 +128,21 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
|
||||
.then(() => {
|
||||
await this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
|
||||
removeFromState()
|
||||
this.$message.success({message: this.$t('task.label.removeSuccess')})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
createAndAddLabel(title) {
|
||||
|
||||
async createAndAddLabel(title) {
|
||||
if (this.taskId === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const newLabel = new LabelModel({title: title})
|
||||
this.$store.dispatch('labels/createLabel', newLabel)
|
||||
.then(r => {
|
||||
this.addLabel(r, false)
|
||||
this.labels.push(r)
|
||||
const label = await this.$store.dispatch('labels/createLabel', newLabel)
|
||||
this.addLabel(label, false)
|
||||
this.labels.push(label)
|
||||
this.$message.success({message: this.$t('task.label.addCreateSuccess')})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
|
||||
},
|
||||
|
|
|
@ -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,20 +74,17 @@ export default {
|
|||
title,
|
||||
}
|
||||
|
||||
this.$store.dispatch('tasks/update', newTask)
|
||||
.then((task) => {
|
||||
try {
|
||||
const task = await this.$store.dispatch('tasks/update', newTask)
|
||||
this.$emit('update:modelValue', task)
|
||||
this.showSavedMessage = true
|
||||
setTimeout(() => {
|
||||
this.showSavedMessage = false
|
||||
}, 2000)
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
}
|
||||
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,23 +93,19 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
markTaskAsDone(task) {
|
||||
async toggleTaskDone(task) {
|
||||
this.loadingInternal = true
|
||||
this.$store.dispatch('tasks/update', {
|
||||
try {
|
||||
await this.$store.dispatch('tasks/update', {
|
||||
...task,
|
||||
done: !task.done,
|
||||
})
|
||||
.then(() => {
|
||||
if (task.done) {
|
||||
playPop()
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
} finally {
|
||||
this.loadingInternal = false
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -50,28 +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
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
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) {
|
||||
|
|
|
@ -185,23 +185,17 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
findTasks(query) {
|
||||
this.taskService.getAll({}, {s: query})
|
||||
.then(response => {
|
||||
this.foundTasks = response
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
async findTasks(query) {
|
||||
this.foundTasks = await this.taskService.getAll({}, {s: query})
|
||||
},
|
||||
addTaskRelation() {
|
||||
let rel = new TaskRelationModel({
|
||||
|
||||
async addTaskRelation() {
|
||||
const rel = new TaskRelationModel({
|
||||
taskId: this.taskId,
|
||||
otherTaskId: this.newTaskRelationTask.id,
|
||||
relationKind: this.newTaskRelationKind,
|
||||
})
|
||||
this.taskRelationService.create(rel)
|
||||
.then(() => {
|
||||
await this.taskRelationService.create(rel)
|
||||
if (!this.relatedTasks[this.newTaskRelationKind]) {
|
||||
this.relatedTasks[this.newTaskRelationKind] = []
|
||||
}
|
||||
|
@ -212,49 +206,41 @@ export default {
|
|||
setTimeout(() => {
|
||||
this.saved = false
|
||||
}, 2000)
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
removeTaskRelation() {
|
||||
|
||||
async removeTaskRelation() {
|
||||
const rel = new TaskRelationModel({
|
||||
relationKind: this.relationToDelete.relationKind,
|
||||
taskId: this.taskId,
|
||||
otherTaskId: this.relationToDelete.otherTaskId,
|
||||
})
|
||||
this.taskRelationService.delete(rel)
|
||||
.then(() => {
|
||||
Object.keys(this.relatedTasks).forEach(relationKind => {
|
||||
for (const t in this.relatedTasks[relationKind]) {
|
||||
if (this.relatedTasks[relationKind][t].id === this.relationToDelete.otherTaskId && relationKind === this.relationToDelete.relationKind) {
|
||||
try {
|
||||
await this.taskRelationService.delete(rel)
|
||||
|
||||
Object.entries(this.relatedTasks).some(([relationKind, t]) => {
|
||||
const found = this.relatedTasks[relationKind][t].id === this.relationToDelete.otherTaskId &&
|
||||
relationKind === this.relationToDelete.relationKind
|
||||
if (!found) return false
|
||||
|
||||
this.relatedTasks[relationKind].splice(t, 1)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
this.saved = true
|
||||
setTimeout(() => {
|
||||
this.saved = false
|
||||
}, 2000)
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
} finally {
|
||||
this.showDeleteModal = false
|
||||
})
|
||||
}
|
||||
},
|
||||
createAndRelateTask(title) {
|
||||
|
||||
async createAndRelateTask(title) {
|
||||
const newTask = new TaskModel({title: title, listId: this.listId})
|
||||
this.taskService.create(newTask)
|
||||
.then(r => {
|
||||
this.newTaskRelationTask = r
|
||||
this.addTaskRelation()
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
this.newTaskRelationTask = await this.taskService.create(newTask)
|
||||
await this.addTaskRelation()
|
||||
},
|
||||
|
||||
relationKindTitle(kind, length) {
|
||||
return this.$tc(`task.relation.kinds.${kind}`, length)
|
||||
},
|
||||
|
|
|
@ -166,56 +166,47 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
markAsDone(checked) {
|
||||
const updateFunc = () => {
|
||||
this.taskService.update(this.task)
|
||||
.then(t => {
|
||||
async markAsDone(checked) {
|
||||
const updateFunc = async () => {
|
||||
const task = await this.taskService.update(this.task)
|
||||
if (this.task.done) {
|
||||
playPop()
|
||||
}
|
||||
this.task = t
|
||||
this.$emit('task-updated', t)
|
||||
this.task = task
|
||||
this.$emit('task-updated', task)
|
||||
this.$message.success({
|
||||
message: this.task.done ?
|
||||
this.$t('task.doneSuccess') :
|
||||
this.$t('task.undoneSuccess'),
|
||||
}, [{
|
||||
title: 'Undo',
|
||||
callback: () => {
|
||||
callback() {
|
||||
this.task.done = !this.task.done
|
||||
this.markAsDone(!checked)
|
||||
},
|
||||
}])
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
setTimeout(updateFunc, 300) // Delay it to show the animation when marking a task as done
|
||||
} else {
|
||||
updateFunc() // Don't delay it when un-marking it as it doesn't have an animation the other way around
|
||||
await updateFunc() // Don't delay it when un-marking it as it doesn't have an animation the other way around
|
||||
}
|
||||
},
|
||||
toggleFavorite() {
|
||||
|
||||
async toggleFavorite() {
|
||||
this.task.isFavorite = !this.task.isFavorite
|
||||
this.taskService.update(this.task)
|
||||
.then(t => {
|
||||
this.task = t
|
||||
this.$emit('task-updated', t)
|
||||
this.task = await this.taskService.update(this.task)
|
||||
this.$emit('task-updated', this.task)
|
||||
this.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
hideDeferDueDatePopup(e) {
|
||||
if (this.showDefer) {
|
||||
if (!this.showDefer) {
|
||||
return
|
||||
}
|
||||
closeWhenClickedOutside(e, this.$refs.deferDueDate.$el, () => {
|
||||
this.showDefer = false
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -87,43 +87,38 @@ export default {
|
|||
Cropper,
|
||||
},
|
||||
methods: {
|
||||
avatarStatus() {
|
||||
this.avatarService.get({})
|
||||
.then(r => {
|
||||
this.avatarProvider = r.avatarProvider
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
async avatarStatus() {
|
||||
const { avatarProvider } = await this.avatarService.get({})
|
||||
this.avatarProvider = avatarProvider
|
||||
},
|
||||
updateAvatarStatus() {
|
||||
|
||||
async updateAvatarStatus() {
|
||||
const avatarStatus = new AvatarModel({avatarProvider: this.avatarProvider})
|
||||
this.avatarService.update(avatarStatus)
|
||||
.then(() => {
|
||||
await this.avatarService.update(avatarStatus)
|
||||
this.$message.success({message: this.$t('user.settings.avatar.statusUpdateSuccess')})
|
||||
this.$store.commit('auth/reloadAvatar')
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
uploadAvatar() {
|
||||
|
||||
async uploadAvatar() {
|
||||
this.loading = true
|
||||
const {canvas} = this.$refs.cropper.getResult()
|
||||
|
||||
if (canvas) {
|
||||
canvas.toBlob(blob => {
|
||||
this.avatarService.create(blob)
|
||||
.then(() => {
|
||||
if (!canvas) {
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const blob = await new Promise(resolve => canvas.toBlob(blob => resolve(blob)))
|
||||
await this.avatarService.create(blob)
|
||||
this.$message.success({message: this.$t('user.settings.avatar.setSuccess')})
|
||||
this.$store.commit('auth/reloadAvatar')
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
.finally(() => {
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.isCropAvatar = false
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
cropAvatar() {
|
||||
const avatar = this.$refs.avatarUploadInput.files
|
||||
|
||||
|
|
|
@ -43,28 +43,22 @@ export default {
|
|||
name: 'data-export',
|
||||
data() {
|
||||
return {
|
||||
dataExportService: DataExportService,
|
||||
dataExportService: new DataExportService(),
|
||||
password: '',
|
||||
errPasswordRequired: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.dataExportService = new DataExportService()
|
||||
},
|
||||
methods: {
|
||||
requestDataExport() {
|
||||
async requestDataExport() {
|
||||
if (this.password === '') {
|
||||
this.errPasswordRequired = true
|
||||
this.$refs.passwordInput.focus()
|
||||
return
|
||||
}
|
||||
|
||||
this.dataExportService.request(this.password)
|
||||
.then(() => {
|
||||
await this.dataExportService.request(this.password)
|
||||
this.$message.success({message: this.$t('user.export.success')})
|
||||
this.password = ''
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -101,34 +101,29 @@ export default {
|
|||
deletionScheduledAt: state => parseDateOrNull(state.auth.info.deletionScheduledAt),
|
||||
}),
|
||||
methods: {
|
||||
deleteAccount() {
|
||||
async deleteAccount() {
|
||||
if (this.password === '') {
|
||||
this.errPasswordRequired = true
|
||||
this.$refs.passwordInput.focus()
|
||||
return
|
||||
}
|
||||
|
||||
this.accountDeleteService.request(this.password)
|
||||
.then(() => {
|
||||
await this.accountDeleteService.request(this.password)
|
||||
this.$message.success({message: this.$t('user.deletion.requestSuccess')})
|
||||
this.password = ''
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
cancelDeletion() {
|
||||
|
||||
async cancelDeletion() {
|
||||
if (this.password === '') {
|
||||
this.errPasswordRequired = true
|
||||
this.$refs.passwordInput.focus()
|
||||
return
|
||||
}
|
||||
|
||||
this.accountDeleteService.cancel(this.password)
|
||||
.then(() => {
|
||||
await this.accountDeleteService.cancel(this.password)
|
||||
this.$message.success({message: this.$t('user.deletion.scheduledCancelSuccess')})
|
||||
this.$store.dispatch('auth/refreshUserInfo')
|
||||
this.password = ''
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -11,24 +11,22 @@ export function uploadFile(taskId: number, file: FileModel, onSuccess: () => Fun
|
|||
return uploadFiles(attachmentService, taskId, files, onSuccess)
|
||||
}
|
||||
|
||||
export function uploadFiles(attachmentService: AttachmentService, taskId: number, files: FileModel[], onSuccess : Function = () => {}) {
|
||||
export async function uploadFiles(attachmentService: AttachmentService, taskId: number, files: FileModel[], onSuccess : Function = () => {}) {
|
||||
const attachmentModel = new AttachmentModel({taskId})
|
||||
attachmentService.create(attachmentModel, files)
|
||||
.then(r => {
|
||||
console.debug(`Uploaded attachments for task ${taskId}, response was`, r)
|
||||
if (r.success !== null) {
|
||||
r.success.forEach((attachment: AttachmentModel) => {
|
||||
const response = await attachmentService.create(attachmentModel, files)
|
||||
console.debug(`Uploaded attachments for task ${taskId}, response was`, response)
|
||||
|
||||
response.success?.map((attachment: AttachmentModel) => {
|
||||
store.dispatch('tasks/addTaskAttachment', {
|
||||
taskId,
|
||||
attachment,
|
||||
})
|
||||
onSuccess(generateAttachmentUrl(taskId, attachment.id))
|
||||
})
|
||||
|
||||
if (response.errors !== null) {
|
||||
throw Error(response.errors)
|
||||
}
|
||||
if (r.errors !== null) {
|
||||
throw Error(r.errors)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function generateAttachmentUrl(taskId: number, attachmentId: number) : any {
|
||||
|
|
|
@ -41,21 +41,19 @@ export const removeToken = () => {
|
|||
* Refreshes an auth token while ensuring it is updated everywhere.
|
||||
* @returns {Promise<AxiosResponse<any>>}
|
||||
*/
|
||||
export const refreshToken = (persist: boolean): Promise<AxiosResponse> => {
|
||||
export async function refreshToken(persist: boolean): Promise<AxiosResponse> {
|
||||
const HTTP = HTTPFactory()
|
||||
return HTTP.post('user/token', null, {
|
||||
try {
|
||||
const response = await HTTP.post('user/token', null, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
},
|
||||
})
|
||||
.then(r => {
|
||||
saveToken(r.data.token, persist)
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => {
|
||||
// eslint-disable-next-line
|
||||
console.log('Error renewing token: ', e)
|
||||
return Promise.reject(e)
|
||||
})
|
||||
saveToken(response.data.token, persist)
|
||||
return response
|
||||
|
||||
} catch(e) {
|
||||
throw new Error('Error renewing token: ', { cause: e })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,3 +27,14 @@ export function filterLabelsByQuery(state: labelState, labelsToHide: label[], qu
|
|||
return !labelIds.includes(id) && title.toLowerCase().includes(labelQuery)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the labels by id if found
|
||||
* @param {Object} state
|
||||
* @param {Array} ids
|
||||
* @returns {Array}
|
||||
*/
|
||||
export function getLabelsByIds(state: labelState, ids: number[]) {
|
||||
return Object.values(state.labels).filter(({id}) => ids.includes(id))
|
||||
}
|
|
@ -33,7 +33,7 @@ export const getMigratorFromSlug = (slug: string): Migrator => {
|
|||
isFileMigrator: true,
|
||||
}
|
||||
default:
|
||||
throw Error('Unknown migrator slug ' + slug)
|
||||
throw new Error('Unknown migrator slug ' + slug)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,14 +27,13 @@ const setI18nLanguage = lang => {
|
|||
}
|
||||
|
||||
export const loadLanguageAsync = lang => {
|
||||
if (
|
||||
// If the same language
|
||||
if (i18n.global.locale === lang) {
|
||||
return Promise.resolve(setI18nLanguage(lang))
|
||||
}
|
||||
|
||||
i18n.global.locale === lang ||
|
||||
// If the language was already loaded
|
||||
if (loadedLanguages.includes(lang)) {
|
||||
return Promise.resolve(setI18nLanguage(lang))
|
||||
loadedLanguages.includes(lang)
|
||||
) {
|
||||
return setI18nLanguage(lang)
|
||||
}
|
||||
|
||||
// If the language hasn't been loaded yet
|
||||
|
|
22
src/main.ts
22
src/main.ts
|
@ -96,10 +96,30 @@ app.config.errorHandler = (err, vm, info) => {
|
|||
// if (import.meta.env.PROD) {
|
||||
// error(err)
|
||||
// } else {
|
||||
console.error(err, vm, info)
|
||||
// console.error(err, vm, info)
|
||||
error(err)
|
||||
// }
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
app.config.warnHandler = (msg, vm, info) => {
|
||||
error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// https://stackoverflow.com/a/52076738/15522256
|
||||
window.addEventListener('error', (err) => {
|
||||
error(err)
|
||||
})
|
||||
|
||||
|
||||
window.addEventListener('unhandledrejection', (err) => {
|
||||
// event.promise contains the promise object
|
||||
// event.reason contains the reason for the rejection
|
||||
error(err)
|
||||
})
|
||||
|
||||
app.config.globalProperties.$message = {
|
||||
error,
|
||||
success,
|
||||
|
|
|
@ -24,15 +24,11 @@ export default class TaskModel extends AbstractModel {
|
|||
this.endDate = parseDateOrNull(this.endDate)
|
||||
this.doneAt = parseDateOrNull(this.doneAt)
|
||||
|
||||
this.reminderDates = this.reminderDates.map(d => new Date(d))
|
||||
// Cancel all scheduled notifications for this task to be sure to only have available notifications
|
||||
this.cancelScheduledNotifications()
|
||||
.then(() => {
|
||||
this.reminderDates = this.reminderDates.map(d => {
|
||||
d = new Date(d)
|
||||
this.cancelScheduledNotifications().then(() => {
|
||||
// Every time we see a reminder, we schedule a notification for it
|
||||
this.scheduleNotification(d)
|
||||
return d
|
||||
})
|
||||
this.reminderDates.forEach(d => this.scheduleNotification(d))
|
||||
})
|
||||
|
||||
// Parse the repeat after into something usable
|
||||
|
@ -218,6 +214,7 @@ export default class TaskModel extends AbstractModel {
|
|||
}
|
||||
|
||||
// Register the actual notification
|
||||
try {
|
||||
registration.showNotification('Vikunja Reminder', {
|
||||
tag: `vikunja-task-${this.id}`, // Group notifications by task id so we're only showing one notification per task
|
||||
body: this.title,
|
||||
|
@ -233,12 +230,10 @@ export default class TaskModel extends AbstractModel {
|
|||
},
|
||||
],
|
||||
})
|
||||
.then(() => {
|
||||
console.debug('Notification scheduled for ' + date)
|
||||
})
|
||||
.catch(e => {
|
||||
console.debug('Error scheduling notification', e)
|
||||
})
|
||||
} catch(e) {
|
||||
throw new Error('Error scheduling notification', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -105,19 +105,6 @@ export default class AbstractService {
|
|||
return true
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// Global error handler
|
||||
///////////////////
|
||||
|
||||
/**
|
||||
* Handles the error and rejects the promise.
|
||||
* @param error
|
||||
* @returns {Promise<never>}
|
||||
*/
|
||||
errorHandler(error) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Helper functions
|
||||
///////////////
|
||||
|
@ -284,7 +271,7 @@ export default class AbstractService {
|
|||
*/
|
||||
get(model, params = {}) {
|
||||
if (this.paths.get === '') {
|
||||
return Promise.reject({message: 'This model is not able to get data.'})
|
||||
throw new Error('This model is not able to get data.')
|
||||
}
|
||||
|
||||
return this.getM(this.paths.get, model, params)
|
||||
|
@ -298,35 +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: params})
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
try {
|
||||
const response = await this.http.get(finalUrl, {params})
|
||||
const result = this.modelGetFactory(response.data)
|
||||
result.maxRight = Number(response.headers['x-max-right'])
|
||||
return Promise.resolve(result)
|
||||
})
|
||||
.finally(() => {
|
||||
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]))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -337,9 +319,9 @@ 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 === '') {
|
||||
return Promise.reject({message: 'This model is not able to get data.'})
|
||||
throw new Error('This model is not able to get data.')
|
||||
}
|
||||
|
||||
params.page = page
|
||||
|
@ -348,27 +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})
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
try {
|
||||
const response = await this.http.get(finalUrl, {params: params})
|
||||
this.resultCount = Number(response.headers['x-pagination-result-count'])
|
||||
this.totalPages = Number(response.headers['x-pagination-total-pages'])
|
||||
|
||||
if (Array.isArray(response.data)) {
|
||||
return Promise.resolve(response.data.map(entry => {
|
||||
return this.modelGetAllFactory(entry)
|
||||
}))
|
||||
}
|
||||
if (response.data === null) {
|
||||
return Promise.resolve([])
|
||||
return []
|
||||
}
|
||||
return Promise.resolve(this.modelGetAllFactory(response.data))
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
if (Array.isArray(response.data)) {
|
||||
return response.data.map(entry => this.modelGetAllFactory(entry))
|
||||
}
|
||||
return this.modelGetAllFactory(response.data)
|
||||
} finally {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -376,28 +353,24 @@ export default class AbstractService {
|
|||
* @param model
|
||||
* @returns {Promise<any | never>}
|
||||
*/
|
||||
create(model) {
|
||||
async create(model) {
|
||||
if (this.paths.create === '') {
|
||||
return Promise.reject({message: 'This model is not able to create data.'})
|
||||
throw new Error('This model is not able to create data.')
|
||||
}
|
||||
|
||||
const cancel = this.setLoading()
|
||||
const finalUrl = this.getReplacedRoute(this.paths.create, model)
|
||||
|
||||
return this.http.put(finalUrl, model)
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
try {
|
||||
const response = await this.http.put(finalUrl, model)
|
||||
const result = this.modelCreateFactory(response.data)
|
||||
if (typeof model.maxRight !== 'undefined') {
|
||||
result.maxRight = model.maxRight
|
||||
}
|
||||
return Promise.resolve(result)
|
||||
})
|
||||
.finally(() => {
|
||||
return result
|
||||
} finally {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -407,23 +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)
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
try {
|
||||
const response = await this.http.post(url, model)
|
||||
const result = this.modelUpdateFactory(response.data)
|
||||
if (typeof model.maxRight !== 'undefined') {
|
||||
result.maxRight = model.maxRight
|
||||
}
|
||||
return Promise.resolve(result)
|
||||
})
|
||||
.finally(() => {
|
||||
return result
|
||||
} finally {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -433,7 +402,7 @@ export default class AbstractService {
|
|||
*/
|
||||
update(model) {
|
||||
if (this.paths.update === '') {
|
||||
return Promise.reject({message: 'This model is not able to update data.'})
|
||||
throw new Error('This model is not able to update data.')
|
||||
}
|
||||
|
||||
const finalUrl = this.getReplacedRoute(this.paths.update, model)
|
||||
|
@ -445,24 +414,20 @@ export default class AbstractService {
|
|||
* @param model
|
||||
* @returns {Q.Promise<any>}
|
||||
*/
|
||||
delete(model) {
|
||||
async delete(model) {
|
||||
if (this.paths.delete === '') {
|
||||
return Promise.reject({message: 'This model is not able to delete data.'})
|
||||
throw new Error('This model is not able to delete data.')
|
||||
}
|
||||
|
||||
const cancel = this.setLoading()
|
||||
const finalUrl = this.getReplacedRoute(this.paths.delete, model)
|
||||
|
||||
return this.http.delete(finalUrl, model)
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
return Promise.resolve(response.data)
|
||||
})
|
||||
.finally(() => {
|
||||
try {
|
||||
const {data} = await this.http.delete(finalUrl, model)
|
||||
return data
|
||||
} finally {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -496,11 +461,12 @@ export default class AbstractService {
|
|||
* @param formData
|
||||
* @returns {Q.Promise<unknown>}
|
||||
*/
|
||||
uploadFormData(url, formData) {
|
||||
async uploadFormData(url, formData) {
|
||||
console.log(formData, formData._boundary)
|
||||
|
||||
const cancel = this.setLoading()
|
||||
return this.http.put(
|
||||
try {
|
||||
const response = await this.http.put(
|
||||
url,
|
||||
formData,
|
||||
{
|
||||
|
@ -513,15 +479,10 @@ export default class AbstractService {
|
|||
},
|
||||
},
|
||||
)
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
return Promise.resolve(this.modelCreateFactory(response.data))
|
||||
})
|
||||
.finally(() => {
|
||||
this.modelCreateFactory(response.data)
|
||||
} finally {
|
||||
this.uploadProgress = 0
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,9 +37,9 @@ export default class AttachmentService extends AbstractService {
|
|||
return AbstractService.prototype.getBlobUrl.call(this, '/tasks/' + model.taskId + '/attachments/' + model.id)
|
||||
}
|
||||
|
||||
download(model) {
|
||||
this.getBlobUrl(model)
|
||||
.then(url => downloadBlob(url, model.file.name))
|
||||
async download(model) {
|
||||
const url = await this.getBlobUrl(model)
|
||||
return downloadBlob(url, model.file.name)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,17 +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]))
|
||||
})
|
||||
.catch(e => {
|
||||
return e
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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,36 +44,27 @@ export default class ListService extends AbstractService {
|
|||
return super.update(newModel)
|
||||
}
|
||||
|
||||
background(list) {
|
||||
async background(list) {
|
||||
if (list.background === null) {
|
||||
return Promise.resolve('')
|
||||
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]))
|
||||
})
|
||||
.catch(e => {
|
||||
return e
|
||||
})
|
||||
}
|
||||
|
||||
removeBackground(list) {
|
||||
async removeBackground(list) {
|
||||
const cancel = this.setLoading()
|
||||
|
||||
return this.http.delete(`/lists/${list.id}/background`, list)
|
||||
.then(response => {
|
||||
return Promise.resolve(response.data)
|
||||
})
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.finally(() => {
|
||||
try {
|
||||
const response = await this.http.delete(`/lists/${list.id}/background`, list)
|
||||
return response.data
|
||||
} finally {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,31 +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)
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
return Promise.resolve(this.modelFactory(response.data))
|
||||
})
|
||||
.finally(() => {
|
||||
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)
|
||||
.catch(error => {
|
||||
return this.errorHandler(error)
|
||||
})
|
||||
.then(response => {
|
||||
return Promise.resolve(this.modelFactory(response.data))
|
||||
})
|
||||
.finally(() => {
|
||||
try {
|
||||
const response = await this.http.post(this.paths.requestReset, model)
|
||||
return this.modelFactory(response.data)
|
||||
} finally {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,13 +26,12 @@ export default class TotpService extends AbstractService {
|
|||
return this.post(`${this.urlPrefix}/disable`, model)
|
||||
}
|
||||
|
||||
qrcode() {
|
||||
return this.http({
|
||||
async qrcode() {
|
||||
const response = await this.http({
|
||||
url: `${this.urlPrefix}/qrcode`,
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
}).then(response => {
|
||||
return Promise.resolve(new Blob([response.data]))
|
||||
})
|
||||
return new Blob([response.data])
|
||||
}
|
||||
}
|
|
@ -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 => {
|
||||
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')
|
||||
return Promise.resolve()
|
||||
})
|
||||
.catch(e => {
|
||||
if (e.response) {
|
||||
if (e.response.data.code === 1017 && !credentials.totpPasscode) {
|
||||
} catch(e) {
|
||||
if (
|
||||
e.response &&
|
||||
e.response.data.code === 1017 &&
|
||||
!credentials.totpPasscode
|
||||
) {
|
||||
ctx.commit('needsTotpPasscode', true)
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(e)
|
||||
})
|
||||
.finally(() => {
|
||||
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', {
|
||||
try {
|
||||
await HTTP.post('register', {
|
||||
username: credentials.username,
|
||||
email: credentials.email,
|
||||
password: credentials.password,
|
||||
})
|
||||
.then(() => {
|
||||
return ctx.dispatch('login', credentials)
|
||||
})
|
||||
.catch(e => {
|
||||
} catch(e) {
|
||||
if (e.response && e.response.data && e.response.data.message) {
|
||||
ctx.commit(ERROR_MESSAGE, e.response.data.message, {root: true})
|
||||
}
|
||||
|
||||
return Promise.reject(e)
|
||||
})
|
||||
.finally(() => {
|
||||
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,42 +148,35 @@ export default {
|
|||
|
||||
// Delete an eventually preexisting old token
|
||||
removeToken()
|
||||
return HTTP.post(`/auth/openid/${provider}/callback`, data)
|
||||
.then(response => {
|
||||
try {
|
||||
const response = await HTTP.post(`/auth/openid/${provider}/callback`, data)
|
||||
// Save the token to local storage for later use
|
||||
saveToken(response.data.token, true)
|
||||
|
||||
// Tell others the user is autheticated
|
||||
ctx.dispatch('checkAuth')
|
||||
return Promise.resolve()
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
.finally(() => {
|
||||
} finally {
|
||||
ctx.commit(LOADING, false, {root: true})
|
||||
})
|
||||
}
|
||||
},
|
||||
linkShareAuth(ctx, {hash, password}) {
|
||||
|
||||
async linkShareAuth(ctx, {hash, password}) {
|
||||
const HTTP = HTTPFactory()
|
||||
return HTTP.post('/shares/' + hash + '/auth', {
|
||||
const response = await HTTP.post('/shares/' + hash + '/auth', {
|
||||
password: password,
|
||||
})
|
||||
.then(r => {
|
||||
saveToken(r.data.token, false)
|
||||
saveToken(response.data.token, false)
|
||||
ctx.dispatch('checkAuth')
|
||||
return Promise.resolve(r.data)
|
||||
}).catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Populates user information from jwt token saved in local storage in store
|
||||
checkAuth(ctx) {
|
||||
|
||||
// This function can be called from multiple places at the same time and shortly after one another.
|
||||
// To prevent hitting the api too frequently or race conditions, we check at most once per minute.
|
||||
if (ctx.state.lastUserInfoRefresh !== null && ctx.state.lastUserInfoRefresh > (new Date()).setMinutes((new Date()).getMinutes() + 1)) {
|
||||
return Promise.resolve()
|
||||
return
|
||||
}
|
||||
|
||||
const jwt = getToken()
|
||||
|
@ -195,14 +186,13 @@ export default {
|
|||
.split('.')[1]
|
||||
.replace('-', '+')
|
||||
.replace('_', '/')
|
||||
const info = new UserModel(JSON.parse(window.atob(base64)))
|
||||
const info = new UserModel(JSON.parse(atob(base64)))
|
||||
const ts = Math.round((new Date()).getTime() / 1000)
|
||||
authenticated = info.exp >= ts
|
||||
ctx.commit('info', info)
|
||||
|
||||
if (authenticated) {
|
||||
ctx.dispatch('refreshUserInfo')
|
||||
ctx.commit('authenticated', authenticated)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,57 +201,55 @@ export default {
|
|||
ctx.commit('info', null)
|
||||
ctx.dispatch('config/redirectToProviderIfNothingElseIsEnabled', null, {root: true})
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
},
|
||||
refreshUserInfo(ctx) {
|
||||
|
||||
async refreshUserInfo(ctx) {
|
||||
const jwt = getToken()
|
||||
if (!jwt) {
|
||||
return
|
||||
}
|
||||
|
||||
const HTTP = HTTPFactory()
|
||||
// We're not returning the promise here to prevent blocking the initial ui render if the user is
|
||||
// accessing the site with a token in local storage
|
||||
HTTP.get('user', {
|
||||
try {
|
||||
|
||||
const response = await HTTP.get('user', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
})
|
||||
.then(r => {
|
||||
const info = new UserModel(r.data)
|
||||
const info = new UserModel(response.data)
|
||||
info.type = ctx.state.info.type
|
||||
info.email = ctx.state.info.email
|
||||
info.exp = ctx.state.info.exp
|
||||
|
||||
ctx.commit('info', info)
|
||||
ctx.commit('lastUserRefresh')
|
||||
})
|
||||
.catch(e => {
|
||||
console.error('Error while refreshing user info:', e)
|
||||
})
|
||||
return info
|
||||
} catch(e) {
|
||||
throw new Error('Error while refreshing user info:', { cause: e })
|
||||
}
|
||||
},
|
||||
|
||||
// Renews the api token and saves it to local storage
|
||||
renewToken(ctx) {
|
||||
// Timeout to avoid race conditions when authenticated as a user (=auth token in localStorage) and as a
|
||||
// FIXME: Timeout to avoid race conditions when authenticated as a user (=auth token in localStorage) and as a
|
||||
// link share in another tab. Without the timeout both the token renew and link share auth are executed at
|
||||
// the same time and one might win over the other.
|
||||
setTimeout(() => {
|
||||
setTimeout(async () => {
|
||||
if (!ctx.state.authenticated) {
|
||||
return
|
||||
}
|
||||
|
||||
refreshToken(!ctx.state.isLinkShareAuth)
|
||||
.then(() => {
|
||||
try {
|
||||
await refreshToken(!ctx.state.isLinkShareAuth)
|
||||
ctx.dispatch('checkAuth')
|
||||
})
|
||||
.catch(e => {
|
||||
} catch(e) {
|
||||
// Don't logout on network errors as the user would then get logged out if they don't have
|
||||
// internet for a short period of time - such as when the laptop is still reconnecting
|
||||
if (e.request.status) {
|
||||
ctx.dispatch('logout')
|
||||
}
|
||||
})
|
||||
}
|
||||
}, 5000)
|
||||
},
|
||||
logout(ctx) {
|
||||
|
|
|
@ -60,16 +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 Promise.resolve(r)
|
||||
})
|
||||
.catch(e => Promise.reject(e))
|
||||
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,33 +218,29 @@ export default {
|
|||
params.per_page = TASKS_PER_BUCKET
|
||||
|
||||
const bucketService = new BucketService()
|
||||
return bucketService.getAll({listId: listId}, params)
|
||||
.then(r => {
|
||||
ctx.commit('setBuckets', r)
|
||||
try {
|
||||
const response = await bucketService.getAll({listId: listId}, params)
|
||||
ctx.commit('setBuckets', response)
|
||||
ctx.commit('setListId', listId)
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
.finally(() => {
|
||||
return response
|
||||
} finally {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
loadNextTasksForBucket(ctx, {listId, ps = {}, bucketId}) {
|
||||
async loadNextTasksForBucket(ctx, {listId, ps = {}, bucketId}) {
|
||||
const bucketIndex = findIndexById(ctx.state.buckets, bucketId)
|
||||
|
||||
const isLoading = ctx.state.bucketLoading[bucketIndex] ?? false
|
||||
if (isLoading) {
|
||||
return Promise.resolve()
|
||||
return
|
||||
}
|
||||
|
||||
const page = (ctx.state.taskPagesPerBucket[bucketIndex] ?? 1) + 1
|
||||
|
||||
const alreadyLoaded = ctx.state.allTasksLoadedForBucket[bucketIndex] ?? false
|
||||
if (alreadyLoaded) {
|
||||
return Promise.resolve()
|
||||
return
|
||||
}
|
||||
|
||||
const cancel = setLoading(ctx, 'kanban')
|
||||
|
@ -275,61 +271,50 @@ export default {
|
|||
params.per_page = TASKS_PER_BUCKET
|
||||
|
||||
const taskService = new TaskCollectionService()
|
||||
return taskService.getAll({listId: listId}, params, page)
|
||||
.then(r => {
|
||||
ctx.commit('addTasksToBucket', {tasks: r, bucketId: bucketId})
|
||||
try {
|
||||
|
||||
const tasks = await taskService.getAll({listId: listId}, params, page)
|
||||
ctx.commit('addTasksToBucket', {tasks, bucketId: bucketId})
|
||||
ctx.commit('setTasksLoadedForBucketPage', {bucketId, page})
|
||||
if (taskService.totalPages <= page) {
|
||||
ctx.commit('setAllTasksLoadedForBucket', bucketId)
|
||||
}
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
.finally(() => {
|
||||
return tasks
|
||||
} finally {
|
||||
cancel()
|
||||
ctx.commit('setBucketLoading', {bucketId: bucketId, loading: false})
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
createBucket(ctx, bucket) {
|
||||
async createBucket(ctx, bucket) {
|
||||
const cancel = setLoading(ctx, 'kanban')
|
||||
|
||||
const bucketService = new BucketService()
|
||||
return bucketService.create(bucket)
|
||||
.then(r => {
|
||||
ctx.commit('addBucket', r)
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
.finally(() => {
|
||||
try {
|
||||
const createdBucket = await bucketService.create(bucket)
|
||||
ctx.commit('addBucket', createdBucket)
|
||||
return createdBucket
|
||||
} finally {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
deleteBucket(ctx, {bucket, params}) {
|
||||
async deleteBucket(ctx, {bucket, params}) {
|
||||
const cancel = setLoading(ctx, 'kanban')
|
||||
|
||||
const bucketService = new BucketService()
|
||||
return bucketService.delete(bucket)
|
||||
.then(r => {
|
||||
try {
|
||||
const response = await bucketService.delete(bucket)
|
||||
ctx.commit('removeBucket', bucket)
|
||||
// We reload all buckets because tasks are being moved from the deleted bucket
|
||||
ctx.dispatch('loadBucketsForList', {listId: bucket.listId, params: params})
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
.finally(() => {
|
||||
return response
|
||||
} finally {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
updateBucket(ctx, updatedBucketData) {
|
||||
async updateBucket(ctx, updatedBucketData) {
|
||||
const cancel = setLoading(ctx, 'kanban')
|
||||
|
||||
const bucketIndex = findIndexById(ctx.state.buckets, updatedBucketData.id)
|
||||
|
@ -343,21 +328,21 @@ export default {
|
|||
ctx.commit('setBucketByIndex', {bucketIndex, bucket: updatedBucket})
|
||||
|
||||
const bucketService = new BucketService()
|
||||
return bucketService.update(updatedBucket)
|
||||
.then(r => {
|
||||
ctx.commit('setBucketByIndex', {bucketIndex, bucket: r})
|
||||
Promise.resolve(r)
|
||||
})
|
||||
.catch(e => {
|
||||
try {
|
||||
const returnedBucket = await bucketService.update(updatedBucket)
|
||||
ctx.commit('setBucketByIndex', {bucketIndex, bucket: returnedBucket})
|
||||
return returnedBucket
|
||||
} catch(e) {
|
||||
// restore original state
|
||||
ctx.commit('setBucketByIndex', {bucketIndex, bucket: oldBucket})
|
||||
|
||||
return Promise.reject(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) {
|
||||
|
@ -370,9 +355,8 @@ export default {
|
|||
title,
|
||||
}
|
||||
|
||||
ctx.dispatch('updateBucket', updatedBucketData).then(() => {
|
||||
await ctx.dispatch('updateBucket', updatedBucketData)
|
||||
success({message: i18n.global.t('list.kanban.bucketTitleSavedSuccess')})
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,15 +1,18 @@
|
|||
import LabelService from '@/services/label'
|
||||
import {setLoading} from '@/store/helper'
|
||||
import {filterLabelsByQuery} from '@/helpers/labels'
|
||||
import { success } from '@/message'
|
||||
import {i18n} from '@/i18n'
|
||||
import {getLabelsByIds, filterLabelsByQuery} from '@/helpers/labels'
|
||||
|
||||
/**
|
||||
* Returns the labels by id if found
|
||||
* @param {Object} state
|
||||
* @param {Array} ids
|
||||
* @returns {Array}
|
||||
*/
|
||||
function getLabelsByIds(state, ids) {
|
||||
return Object.values(state.labels).filter(({id}) => ids.includes(id))
|
||||
async function getAllLabels(page = 1) {
|
||||
const labelService = new LabelService()
|
||||
const labels = await labelService.getAll({}, {}, page)
|
||||
if (page < labelService.totalPages) {
|
||||
const nextLabels = await getAllLabels(page + 1)
|
||||
return labels.concat(nextLabels)
|
||||
} else {
|
||||
return labels
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
|
@ -44,75 +47,59 @@ export default {
|
|||
},
|
||||
},
|
||||
actions: {
|
||||
loadAllLabels(ctx, {forceLoad} = {}) {
|
||||
async loadAllLabels(ctx, {forceLoad} = {}) {
|
||||
if (ctx.state.loaded && !forceLoad) {
|
||||
return Promise.resolve()
|
||||
return
|
||||
}
|
||||
|
||||
const cancel = setLoading(ctx, 'labels')
|
||||
const labelService = new LabelService()
|
||||
|
||||
const getAllLabels = (page = 1) => {
|
||||
return labelService.getAll({}, {}, page)
|
||||
.then(labels => {
|
||||
if (page < labelService.totalPages) {
|
||||
return getAllLabels(page + 1)
|
||||
.then(nextLabels => {
|
||||
return labels.concat(nextLabels)
|
||||
})
|
||||
} else {
|
||||
return labels
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
}
|
||||
|
||||
return getAllLabels()
|
||||
.then(r => {
|
||||
ctx.commit('setLabels', r)
|
||||
try {
|
||||
const labels = await getAllLabels()
|
||||
ctx.commit('setLabels', labels)
|
||||
ctx.commit('setLoaded', true)
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => Promise.reject(e))
|
||||
.finally(() => cancel())
|
||||
return labels
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
},
|
||||
deleteLabel(ctx, label) {
|
||||
async deleteLabel(ctx, label) {
|
||||
const cancel = setLoading(ctx, 'labels')
|
||||
const labelService = new LabelService()
|
||||
|
||||
return labelService.delete(label)
|
||||
.then(r => {
|
||||
try {
|
||||
const result = await labelService.delete(label)
|
||||
ctx.commit('removeLabelById', label)
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => Promise.reject(e))
|
||||
.finally(() => cancel())
|
||||
success({message: i18n.global.t('label.deleteSuccess')})
|
||||
return result
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
},
|
||||
updateLabel(ctx, label) {
|
||||
async updateLabel(ctx, label) {
|
||||
const cancel = setLoading(ctx, 'labels')
|
||||
const labelService = new LabelService()
|
||||
|
||||
return labelService.update(label)
|
||||
.then(r => {
|
||||
ctx.commit('setLabel', r)
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => Promise.reject(e))
|
||||
.finally(() => cancel())
|
||||
try {
|
||||
const newLabel = await labelService.update(label)
|
||||
ctx.commit('setLabel', newLabel)
|
||||
success({message: i18n.global.t('label.edit.success')})
|
||||
return newLabel
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
},
|
||||
createLabel(ctx, label) {
|
||||
async createLabel(ctx, label) {
|
||||
const cancel = setLoading(ctx, 'labels')
|
||||
const labelService = new LabelService()
|
||||
|
||||
return labelService.create(label)
|
||||
.then(r => {
|
||||
ctx.commit('setLabel', r)
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => Promise.reject(e))
|
||||
.finally(() => cancel())
|
||||
try {
|
||||
const newLabel = await labelService.create(label)
|
||||
ctx.commit('setLabel', newLabel)
|
||||
return newLabel
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -42,26 +42,28 @@ export default {
|
|||
isFavorite: !list.isFavorite,
|
||||
})
|
||||
},
|
||||
createList(ctx, list) {
|
||||
|
||||
async createList(ctx, list) {
|
||||
const cancel = setLoading(ctx, 'lists')
|
||||
const listService = new ListService()
|
||||
|
||||
return listService.create(list)
|
||||
.then(r => {
|
||||
r.namespaceId = list.namespaceId
|
||||
ctx.commit('namespaces/addListToNamespace', r, {root: true})
|
||||
ctx.commit('setList', r)
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => Promise.reject(e))
|
||||
.finally(() => cancel())
|
||||
try {
|
||||
const createdList = await listService.create(list)
|
||||
createdList.namespaceId = list.namespaceId
|
||||
ctx.commit('namespaces/addListToNamespace', createdList, {root: true})
|
||||
ctx.commit('setList', createdList)
|
||||
return createdList
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
},
|
||||
updateList(ctx, list) {
|
||||
|
||||
async updateList(ctx, list) {
|
||||
const cancel = setLoading(ctx, 'lists')
|
||||
const listService = new ListService()
|
||||
|
||||
return listService.update(list)
|
||||
.then(() => {
|
||||
try {
|
||||
await listService.update(list)
|
||||
ctx.commit('setList', list)
|
||||
ctx.commit('namespaces/setListInNamespaceById', list, {root: true})
|
||||
|
||||
|
@ -78,33 +80,32 @@ export default {
|
|||
}
|
||||
ctx.dispatch('namespaces/loadNamespacesIfFavoritesDontExist', null, {root: true})
|
||||
ctx.dispatch('namespaces/removeFavoritesNamespaceIfEmpty', null, {root: true})
|
||||
return Promise.resolve(newList)
|
||||
})
|
||||
.catch(e => {
|
||||
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,
|
||||
})
|
||||
return Promise.reject(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 => {
|
||||
try {
|
||||
const response = await listService.delete(list)
|
||||
ctx.commit('removeListById', list)
|
||||
ctx.commit('namespaces/removeListFromNamespaceById', list, {root: true})
|
||||
removeListFromHistory({id: list.id})
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
.finally(() => cancel())
|
||||
return response
|
||||
} finally{
|
||||
cancel()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
|
@ -94,67 +94,63 @@ export default {
|
|||
},
|
||||
},
|
||||
actions: {
|
||||
loadNamespaces(ctx) {
|
||||
async loadNamespaces(ctx) {
|
||||
const cancel = setLoading(ctx, 'namespaces')
|
||||
|
||||
const namespaceService = new NamespaceService()
|
||||
try {
|
||||
// We always load all namespaces and filter them on the frontend
|
||||
return namespaceService.getAll({}, {is_archived: true})
|
||||
.then(r => {
|
||||
ctx.commit('namespaces', r)
|
||||
const namespaces = await namespaceService.getAll({}, {is_archived: true})
|
||||
ctx.commit('namespaces', namespaces)
|
||||
|
||||
// Put all lists in the list state
|
||||
const lists = []
|
||||
r.forEach(n => {
|
||||
n.lists.forEach(l => {
|
||||
lists.push(l)
|
||||
})
|
||||
})
|
||||
const lists = namespaces.flatMap(({lists}) => lists)
|
||||
|
||||
ctx.commit('lists/setLists', lists, {root: true})
|
||||
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => Promise.reject(e))
|
||||
.finally(() => {
|
||||
return namespaces
|
||||
} finally {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
loadNamespacesIfFavoritesDontExist(ctx) {
|
||||
// The first namespace should be the one holding all favorites
|
||||
if (ctx.state.namespaces[0].id !== -2) {
|
||||
return ctx.dispatch('loadNamespaces')
|
||||
}
|
||||
},
|
||||
|
||||
removeFavoritesNamespaceIfEmpty(ctx) {
|
||||
if (ctx.state.namespaces[0].id === -2 && ctx.state.namespaces[0].lists.length === 0) {
|
||||
ctx.state.namespaces.splice(0, 1)
|
||||
return Promise.resolve()
|
||||
}
|
||||
},
|
||||
deleteNamespace(ctx, namespace) {
|
||||
|
||||
async deleteNamespace(ctx, namespace) {
|
||||
const cancel = setLoading(ctx, 'namespaces')
|
||||
const namespaceService = new NamespaceService()
|
||||
|
||||
return namespaceService.delete(namespace)
|
||||
.then(r => {
|
||||
try {
|
||||
const response = await namespaceService.delete(namespace)
|
||||
ctx.commit('removeNamespaceById', namespace.id)
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => Promise.reject(e))
|
||||
.finally(() => cancel())
|
||||
return response
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
},
|
||||
createNamespace(ctx, namespace) {
|
||||
|
||||
async createNamespace(ctx, namespace) {
|
||||
const cancel = setLoading(ctx, 'namespaces')
|
||||
const namespaceService = new NamespaceService()
|
||||
|
||||
return namespaceService.create(namespace)
|
||||
.then(r => {
|
||||
ctx.commit('addNamespace', r)
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => Promise.reject(e))
|
||||
.finally(() => cancel())
|
||||
try {
|
||||
const createdNamespace = await namespaceService.create(namespace)
|
||||
ctx.commit('addNamespace', createdNamespace)
|
||||
return createdNamespace
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
|
@ -34,33 +34,30 @@ function validateLabel(labels, label) {
|
|||
return findPropertyByValue(labels, 'title', label)
|
||||
}
|
||||
|
||||
function addLabelToTask(task, label) {
|
||||
async function addLabelToTask(task, label) {
|
||||
const labelTask = new LabelTask({
|
||||
taskId: task.id,
|
||||
labelId: label.id,
|
||||
})
|
||||
const labelTaskService = new LabelTaskService()
|
||||
return labelTaskService.create(labelTask)
|
||||
.then(result => {
|
||||
const response = await labelTaskService.create(labelTask)
|
||||
task.labels.push(label)
|
||||
return Promise.resolve(result)
|
||||
})
|
||||
.catch(e => Promise.reject(e))
|
||||
return response
|
||||
}
|
||||
|
||||
function findAssignees(parsedTaskAssignees) {
|
||||
async function findAssignees(parsedTaskAssignees) {
|
||||
if (parsedTaskAssignees.length <= 0) {
|
||||
return Promise.resolve([])
|
||||
return []
|
||||
}
|
||||
|
||||
const userService = new UserService()
|
||||
const assignees = parsedTaskAssignees.map(a =>
|
||||
userService.getAll({}, {s: a})
|
||||
.then(users => validateUsername(users, a)),
|
||||
const assignees = parsedTaskAssignees.map(async a => {
|
||||
const users = await userService.getAll({}, {s: a})
|
||||
return validateUsername(users, a)
|
||||
})
|
||||
|
||||
)
|
||||
|
||||
return Promise.all(assignees).filter((item) => Boolean(item))
|
||||
const validatedUsers = await Promise.all(assignees)
|
||||
return validatedUsers.filter((item) => Boolean(item))
|
||||
}
|
||||
|
||||
|
||||
|
@ -68,50 +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
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
.finally(() => {
|
||||
try {
|
||||
const tasks = await taskService.getAll({}, params)
|
||||
ctx.commit(HAS_TASKS, tasks.length > 0, {root: true})
|
||||
return tasks
|
||||
} finally {
|
||||
cancel()
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
update(ctx, task) {
|
||||
|
||||
async update(ctx, task) {
|
||||
const cancel = setLoading(ctx, 'tasks')
|
||||
|
||||
const taskService = new TaskService()
|
||||
return taskService.update(task)
|
||||
.then(t => {
|
||||
ctx.commit('kanban/setTaskInBucket', t, {root: true})
|
||||
return Promise.resolve(t)
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
.finally(() => {
|
||||
try {
|
||||
const updatedTask = await taskService.update(task)
|
||||
ctx.commit('kanban/setTaskInBucket', updatedTask, {root: true})
|
||||
return updatedTask
|
||||
} finally {
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
},
|
||||
delete(ctx, task) {
|
||||
|
||||
async delete(ctx, task) {
|
||||
const taskService = new TaskService()
|
||||
return taskService.delete(task)
|
||||
.then(t => {
|
||||
const response = await taskService.delete(task)
|
||||
ctx.commit('kanban/removeTaskInBucket', task, {root: true})
|
||||
return Promise.resolve(t)
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
return response
|
||||
},
|
||||
|
||||
// Adds a task attachment in store.
|
||||
// This is an action to be able to commit other mutations
|
||||
addTaskAttachment(ctx, {taskId, attachment}) {
|
||||
|
@ -134,43 +120,37 @@ export default {
|
|||
ctx.commit('attachments/add', attachment, {root: true})
|
||||
},
|
||||
|
||||
addAssignee(ctx, {user, taskId}) {
|
||||
async addAssignee(ctx, {user, taskId}) {
|
||||
const taskAssignee = new TaskAssigneeModel({userId: user.id, taskId: taskId})
|
||||
|
||||
const taskAssigneeService = new TaskAssigneeService()
|
||||
return taskAssigneeService.create(taskAssignee)
|
||||
.then(r => {
|
||||
const r = await taskAssigneeService.create(taskAssignee)
|
||||
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
|
||||
if (t.task === null) {
|
||||
// Don't try further adding a label if the task is not in kanban
|
||||
// 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 Promise.resolve(r)
|
||||
return r
|
||||
}
|
||||
// FIXME: direct store manipulation (task)
|
||||
t.task.assignees.push(user)
|
||||
ctx.commit('kanban/setTaskInBucketByIndex', t, { root: true })
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
return r
|
||||
},
|
||||
removeAssignee(ctx, {user, taskId}) {
|
||||
|
||||
async removeAssignee(ctx, {user, taskId}) {
|
||||
const taskAssignee = new TaskAssigneeModel({userId: user.id, taskId: taskId})
|
||||
|
||||
const taskAssigneeService = new TaskAssigneeService()
|
||||
return taskAssigneeService.delete(taskAssignee)
|
||||
.then(r => {
|
||||
const response = await taskAssigneeService.delete(taskAssignee)
|
||||
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
|
||||
if (t.task === null) {
|
||||
// Don't try further adding a label if the task is not in kanban
|
||||
// Usually this means the kanban board hasn't been accessed until now.
|
||||
// Vuex seems to have its difficulties with that, so we just log the error and fail silently.
|
||||
console.debug('Could not remove assignee from task in kanban, task not found', t)
|
||||
return Promise.resolve(r)
|
||||
return response
|
||||
}
|
||||
|
||||
for (const a in t.task.assignees) {
|
||||
|
@ -182,52 +162,41 @@ export default {
|
|||
}
|
||||
|
||||
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
return response
|
||||
|
||||
},
|
||||
|
||||
addLabel(ctx, {label, taskId}) {
|
||||
async addLabel(ctx, {label, taskId}) {
|
||||
const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
|
||||
|
||||
const labelTaskService = new LabelTaskService()
|
||||
return labelTaskService.create(labelTask)
|
||||
.then(r => {
|
||||
const r = await labelTaskService.create(labelTask)
|
||||
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
|
||||
if (t.task === null) {
|
||||
// Don't try further adding a label if the task is not in kanban
|
||||
// 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 Promise.resolve(r)
|
||||
return r
|
||||
}
|
||||
// FIXME: direct store manipulation (task)
|
||||
t.task.labels.push(label)
|
||||
ctx.commit('kanban/setTaskInBucketByIndex', t, { root: true })
|
||||
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
return r
|
||||
},
|
||||
|
||||
removeLabel(ctx, {label, taskId}) {
|
||||
async removeLabel(ctx, {label, taskId}) {
|
||||
const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
|
||||
|
||||
const labelTaskService = new LabelTaskService()
|
||||
return labelTaskService.delete(labelTask)
|
||||
.then(r => {
|
||||
const response = await labelTaskService.delete(labelTask)
|
||||
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
|
||||
if (t.task === null) {
|
||||
// Don't try further adding a label if the task is not in kanban
|
||||
// Usually this means the kanban board hasn't been accessed until now.
|
||||
// Vuex seems to have its difficulties with that, so we just log the error and fail silently.
|
||||
console.debug('Could not remove label from task in kanban, task not found', t)
|
||||
return Promise.resolve(r)
|
||||
return response
|
||||
}
|
||||
|
||||
// Remove the label from the list
|
||||
|
@ -241,11 +210,7 @@ export default {
|
|||
|
||||
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
|
||||
|
||||
return Promise.resolve(r)
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
return response
|
||||
},
|
||||
|
||||
// Do everything that is involved in finding, creating and adding the label to the task
|
||||
|
@ -256,22 +221,21 @@ export default {
|
|||
|
||||
const {labels} = rootState.labels
|
||||
|
||||
const labelAddsToWaitFor = parsedLabels.map(labelTitle => new Promise((resolve) => {
|
||||
const labelAddsToWaitFor = parsedLabels.map(async labelTitle => {
|
||||
let label = validateLabel(labels, labelTitle)
|
||||
if (typeof label !== 'undefined') {
|
||||
return resolve(label)
|
||||
return label
|
||||
}
|
||||
|
||||
// label not found, create it
|
||||
const labelModel = new LabelModel({title: labelTitle})
|
||||
return dispatch('labels/createLabel', labelModel, {root: true}).then(() => resolve(label))
|
||||
await dispatch('labels/createLabel', labelModel, {root: true})
|
||||
return addLabelToTask(task, label)
|
||||
})
|
||||
.then((label) => addLabelToTask(task, label))
|
||||
.catch(e => Promise.reject(e)),
|
||||
)
|
||||
|
||||
// This waits until all labels are created and added to the task
|
||||
return Promise.all(labelAddsToWaitFor).then(() => task)
|
||||
await Promise.all(labelAddsToWaitFor)
|
||||
return task
|
||||
},
|
||||
|
||||
findListId({ rootGetters }, { list: listName, listId }) {
|
||||
|
@ -296,7 +260,7 @@ export default {
|
|||
|
||||
// 4. If none of the above worked, reject the promise with an error.
|
||||
if (typeof foundListId === 'undefined' || listId === null) {
|
||||
return Promise.reject('NO_LIST')
|
||||
throw new Error('NO_LIST')
|
||||
}
|
||||
|
||||
return foundListId
|
||||
|
@ -331,12 +295,11 @@ export default {
|
|||
})
|
||||
|
||||
const taskService = new TaskService()
|
||||
return taskService.create(task)
|
||||
.then(task => dispatch('addLabelsToTask', {
|
||||
task,
|
||||
const createdTask = await taskService.create(task)
|
||||
return dispatch('addLabelsToTask', {
|
||||
task: createdTask,
|
||||
parsedLabels: parsedTask.labels,
|
||||
}))
|
||||
.catch(e => Promise.reject(e))
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
|
@ -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,14 +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()}})
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
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,18 +24,15 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
deleteSavedFilter() {
|
||||
async deleteSavedFilter() {
|
||||
// We assume the listId in the route is the pseudolist
|
||||
const list = new ListModel({id: this.$route.params.listId})
|
||||
const filter = new SavedFilterModel({id: list.getSavedFilterId()})
|
||||
|
||||
this.filterService.delete(filter)
|
||||
.then(() => {
|
||||
this.$store.dispatch('namespaces/loadNamespaces')
|
||||
await this.filterService.delete(filter)
|
||||
await this.$store.dispatch('namespaces/loadNamespaces')
|
||||
this.$message.success({message: this.$t('filters.delete.success')})
|
||||
this.$router.push({name: 'namespaces.index'})
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -95,29 +95,22 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
loadSavedFilter() {
|
||||
async loadSavedFilter() {
|
||||
// We assume the listId in the route is the pseudolist
|
||||
const list = new ListModel({id: this.$route.params.listId})
|
||||
|
||||
this.filter = new SavedFilterModel({id: list.getSavedFilterId()})
|
||||
this.filterService.get(this.filter)
|
||||
.then(r => {
|
||||
this.filter = r
|
||||
this.filter = await this.filterService.get(this.filter)
|
||||
this.filters = objectToSnakeCase(this.filter.filters)
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
save() {
|
||||
async save() {
|
||||
this.filter.filters = this.filters
|
||||
this.filterService.update(this.filter)
|
||||
.then(r => {
|
||||
this.$store.dispatch('namespaces/loadNamespaces')
|
||||
const filter = await this.filterService.update(this.filter)
|
||||
await this.$store.dispatch('namespaces/loadNamespaces')
|
||||
this.$message.success({message: this.$t('filters.edit.success')})
|
||||
this.filter = r
|
||||
this.filter = filter
|
||||
this.filters = objectToSnakeCase(this.filter.filters)
|
||||
this.$router.back()
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ export default {
|
|||
}
|
||||
},
|
||||
created() {
|
||||
this.loadLabels()
|
||||
this.$store.dispatch('labels/loadAllLabels')
|
||||
},
|
||||
mounted() {
|
||||
this.setTitle(this.$t('label.title'))
|
||||
|
@ -131,29 +131,11 @@ export default {
|
|||
loading: state => state[LOADING] && state[LOADING_MODULE] === 'labels',
|
||||
}),
|
||||
methods: {
|
||||
loadLabels() {
|
||||
this.$store.dispatch('labels/loadAllLabels')
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
deleteLabel(label) {
|
||||
this.$store.dispatch('labels/deleteLabel', label)
|
||||
.then(() => {
|
||||
this.$message.success({message: this.$t('label.deleteSuccess')})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
return this.$store.dispatch('labels/deleteLabel', label)
|
||||
},
|
||||
editLabelSubmit() {
|
||||
this.$store.dispatch('labels/updateLabel', this.labelEditLabel)
|
||||
.then(() => {
|
||||
this.$message.success({message: this.$t('label.edit.success')})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
return this.$store.dispatch('labels/updateLabel', this.labelEditLabel)
|
||||
},
|
||||
editLabel(label) {
|
||||
if (label.createdBy.id !== this.userInfo.id) {
|
||||
|
|
|
@ -60,24 +60,19 @@ export default {
|
|||
loading: state => state[LOADING] && state[LOADING_MODULE] === 'labels',
|
||||
}),
|
||||
methods: {
|
||||
newLabel() {
|
||||
async newLabel() {
|
||||
if (this.label.title === '') {
|
||||
this.showError = true
|
||||
return
|
||||
}
|
||||
this.showError = false
|
||||
|
||||
this.$store.dispatch('labels/createLabel', this.label)
|
||||
.then(r => {
|
||||
const label = this.$store.dispatch('labels/createLabel', this.label)
|
||||
this.$router.push({
|
||||
name: 'labels.index',
|
||||
params: {id: r.id},
|
||||
params: {id: label.id},
|
||||
})
|
||||
this.$message.success({message: this.$t('label.create.success')})
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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,17 +62,11 @@ export default {
|
|||
this.showError = false
|
||||
|
||||
this.list.namespaceId = parseInt(this.$route.params.id)
|
||||
this.$store
|
||||
.dispatch('lists/createList', this.list)
|
||||
.then((r) => {
|
||||
const list = await this.$store.dispatch('lists/createList', this.list)
|
||||
this.$message.success({message: this.$t('list.create.createdSuccess') })
|
||||
this.$router.push({
|
||||
name: 'list.index',
|
||||
params: { listId: r.id },
|
||||
})
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
params: { listId: list.id },
|
||||
})
|
||||
},
|
||||
},
|
||||
|
|
|
@ -51,9 +51,6 @@ export default {
|
|||
listLoaded: 0,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadList()
|
||||
},
|
||||
watch: {
|
||||
// call again the method if the route changes
|
||||
'$route.path': {
|
||||
|
@ -83,7 +80,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,17 +137,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))
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
try {
|
||||
const loadedList = await this.listService.get(list)
|
||||
await this.$store.dispatch(CURRENT_LIST, loadedList)
|
||||
this.setTitle(this.getListTitle(loadedList))
|
||||
} finally {
|
||||
this.listLoaded = this.$route.params.listId
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -30,24 +30,20 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
archiveList() {
|
||||
async archiveList() {
|
||||
const newList = {
|
||||
...this.list,
|
||||
isArchived: !this.list.isArchived,
|
||||
}
|
||||
|
||||
this.listService.update(newList)
|
||||
.then(r => {
|
||||
this.$store.commit('currentList', r)
|
||||
this.$store.commit('namespaces/setListInNamespaceById', r)
|
||||
try {
|
||||
const list = await this.listService.update(newList)
|
||||
this.$store.commit('currentList', list)
|
||||
this.$store.commit('namespaces/setListInNamespaceById', list)
|
||||
this.$message.success({message: this.$t('list.archive.success')})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
} finally {
|
||||
this.$router.back()
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<template v-if="unsplashBackgroundEnabled">
|
||||
<input
|
||||
:class="{'is-loading': backgroundService.loading}"
|
||||
@keyup="() => newBackgroundSearch()"
|
||||
@keyup="() => debounceNewBackgroundSearch()"
|
||||
class="input is-expanded"
|
||||
:placeholder="$t('list.background.searchPlaceholder')"
|
||||
type="text"
|
||||
|
@ -70,18 +70,25 @@ import BackgroundUploadService from '../../../services/backgroundUpload'
|
|||
import ListService from '@/services/list'
|
||||
import {CURRENT_LIST} from '@/store/mutation-types'
|
||||
import CreateEdit from '@/components/misc/create-edit.vue'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
const SEARCH_DEBOUNCE = 300
|
||||
|
||||
export default {
|
||||
name: 'list-setting-background',
|
||||
components: {CreateEdit},
|
||||
data() {
|
||||
return {
|
||||
backgroundService: new BackgroundUnsplashService(),
|
||||
backgroundSearchTerm: '',
|
||||
backgroundSearchResult: [],
|
||||
backgroundService: new BackgroundUnsplashService(),
|
||||
backgroundThumbs: {},
|
||||
currentPage: 1,
|
||||
backgroundSearchTimeout: null,
|
||||
|
||||
// We're using debounce to not search on every keypress but with a delay.
|
||||
debounceNewBackgroundSearch: debounce(this.newBackgroundSearch, SEARCH_DEBOUNCE, {
|
||||
trailing: true,
|
||||
}),
|
||||
|
||||
backgroundUploadService: new BackgroundUploadService(),
|
||||
listService: new ListService(),
|
||||
|
@ -108,73 +115,45 @@ export default {
|
|||
this.backgroundThumbs = {}
|
||||
this.searchBackgrounds()
|
||||
},
|
||||
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.
|
||||
// If another key is pressed within these 300ms, the last search request is dropped and a new one is scheduled.
|
||||
this.backgroundSearchTimeout = setTimeout(() => {
|
||||
async searchBackgrounds(page = 1) {
|
||||
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
|
||||
const result = await this.backgroundService.getAll({}, {s: this.backgroundSearchTerm, p: page})
|
||||
this.backgroundSearchResult = this.backgroundSearchResult.concat(result)
|
||||
result.forEach(async background => {
|
||||
this.backgroundThumbs[background.id] = await this.backgroundService.thumb(background)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
}, 300)
|
||||
},
|
||||
setBackground(backgroundId) {
|
||||
|
||||
async setBackground(backgroundId) {
|
||||
// Don't set a background if we're in the process of setting one
|
||||
if (this.backgroundService.loading) {
|
||||
return
|
||||
}
|
||||
|
||||
this.backgroundService.update({id: backgroundId, listId: this.$route.params.listId})
|
||||
.then(l => {
|
||||
this.$store.commit(CURRENT_LIST, l)
|
||||
this.$store.commit('namespaces/setListInNamespaceById', l)
|
||||
const list = await this.backgroundService.update({id: backgroundId, listId: this.$route.params.listId})
|
||||
await this.$store.dispatch(CURRENT_LIST, list)
|
||||
this.$store.commit('namespaces/setListInNamespaceById', list)
|
||||
this.$message.success({message: this.$t('list.background.success')})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
uploadBackground() {
|
||||
|
||||
async uploadBackground() {
|
||||
if (this.$refs.backgroundUploadInput.files.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.backgroundUploadService.create(this.$route.params.listId, this.$refs.backgroundUploadInput.files[0])
|
||||
.then(l => {
|
||||
this.$store.commit(CURRENT_LIST, l)
|
||||
this.$store.commit('namespaces/setListInNamespaceById', l)
|
||||
const list = await this.backgroundUploadService.create(this.$route.params.listId, this.$refs.backgroundUploadInput.files[0])
|
||||
await this.$store.dispatch(CURRENT_LIST, list)
|
||||
this.$store.commit('namespaces/setListInNamespaceById', list)
|
||||
this.$message.success({message: this.$t('list.background.success')})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
removeBackground() {
|
||||
this.listService.removeBackground(this.currentList)
|
||||
.then(l => {
|
||||
this.$store.commit(CURRENT_LIST, l)
|
||||
this.$store.commit('namespaces/setListInNamespaceById', l)
|
||||
|
||||
async removeBackground() {
|
||||
const list = await this.listService.removeBackground(this.currentList)
|
||||
await this.$store.dispatch(CURRENT_LIST, list)
|
||||
this.$store.commit('namespaces/setListInNamespaceById', list)
|
||||
this.$message.success({message: this.$t('list.background.removeSuccess')})
|
||||
this.$router.back()
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -24,15 +24,10 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
deleteList() {
|
||||
this.$store.dispatch('lists/deleteList', this.list)
|
||||
.then(() => {
|
||||
async deleteList() {
|
||||
await this.$store.dispatch('lists/deleteList', this.list)
|
||||
this.$message.success({message: this.$t('list.delete.success')})
|
||||
this.$router.push({name: 'home'})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -38,21 +38,17 @@ export default {
|
|||
selectNamespace(namespace) {
|
||||
this.selectedNamespace = namespace
|
||||
},
|
||||
duplicateList() {
|
||||
|
||||
async duplicateList() {
|
||||
const listDuplicate = new ListDuplicateModel({
|
||||
listId: this.$route.params.listId,
|
||||
namespaceId: this.selectedNamespace.id,
|
||||
})
|
||||
this.listDuplicateService.create(listDuplicate)
|
||||
.then(r => {
|
||||
this.$store.commit('namespaces/addListToNamespace', r.list)
|
||||
this.$store.commit('lists/setList', r.list)
|
||||
const duplicate = await this.listDuplicateService.create(listDuplicate)
|
||||
this.$store.commit('namespaces/addListToNamespace', duplicate.list)
|
||||
this.$store.commit('lists/setList', duplicate.list)
|
||||
this.$message.success({message: this.$t('list.duplicate.success')})
|
||||
this.$router.push({name: 'list.index', params: {listId: r.list.id}})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
this.$router.push({name: 'list.index', params: {listId: duplicate.list.id}})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -94,28 +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 }
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
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)
|
||||
|
||||
async save() {
|
||||
await this.$store.dispatch('lists/updateList', this.list)
|
||||
await this.$store.dispatch(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()
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -56,21 +56,15 @@ export default {
|
|||
this.loadList()
|
||||
},
|
||||
methods: {
|
||||
loadList() {
|
||||
async loadList() {
|
||||
const list = new ListModel({id: this.$route.params.listId})
|
||||
|
||||
this.listService.get(list)
|
||||
.then(r => {
|
||||
this.list = r
|
||||
this.$store.commit(CURRENT_LIST, r)
|
||||
this.list = await this.listService.get(list)
|
||||
await this.$store.dispatch(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}))
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -409,7 +409,7 @@ export default {
|
|||
this.$store.commit('kanban/setBucketById', 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
|
||||
|
@ -424,36 +424,37 @@ export default {
|
|||
|
||||
const newTask = cloneDeep(task) // cloning the task to avoid vuex store mutations
|
||||
newTask.bucketId = newBucket.id,
|
||||
newTask.kanbanPosition = calculateItemPosition(taskBefore !== null ? taskBefore.kanbanPosition : null, taskAfter !== null ? taskAfter.kanbanPosition : null),
|
||||
newTask.kanbanPosition = calculateItemPosition(taskBefore !== null ? taskBefore.kanbanPosition : null, taskAfter !== null ? taskAfter.kanbanPosition : null)
|
||||
|
||||
this.$store.dispatch('tasks/update', newTask)
|
||||
.finally(() => {
|
||||
try {
|
||||
await this.$store.dispatch('tasks/update', newTask)
|
||||
} finally {
|
||||
this.taskUpdating[task.id] = false
|
||||
this.oneTaskUpdating = false
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
toggleShowNewTaskInput(bucketId) {
|
||||
this.showNewTaskInput[bucketId] = !this.showNewTaskInput[bucketId]
|
||||
},
|
||||
addTaskToBucket(bucketId) {
|
||||
|
||||
async addTaskToBucket(bucketId) {
|
||||
if (this.newTaskText === '') {
|
||||
this.newTaskError[bucketId] = true
|
||||
return
|
||||
}
|
||||
this.newTaskError[bucketId] = false
|
||||
|
||||
this.$store.dispatch('tasks/createNewTask', {
|
||||
const task = await this.$store.dispatch('tasks/createNewTask', {
|
||||
title: this.newTaskText,
|
||||
bucketId,
|
||||
listId: this.$route.params.listId,
|
||||
})
|
||||
.then(r => {
|
||||
this.newTaskText = ''
|
||||
this.$store.commit('kanban/addTaskToBucket', r)
|
||||
this.$store.commit('kanban/addTaskToBucket', task)
|
||||
this.scrollTaskContainerToBottom(bucketId)
|
||||
})
|
||||
},
|
||||
|
||||
scrollTaskContainerToBottom(bucketId) {
|
||||
const bucketEl = this.taskContainerRefs[bucketId]
|
||||
if (!bucketEl) {
|
||||
|
@ -461,7 +462,8 @@ export default {
|
|||
}
|
||||
bucketEl.scrollTop = bucketEl.scrollHeight
|
||||
},
|
||||
createNewBucket() {
|
||||
|
||||
async createNewBucket() {
|
||||
if (this.newBucketTitle === '') {
|
||||
return
|
||||
}
|
||||
|
@ -471,12 +473,11 @@ export default {
|
|||
listId: parseInt(this.$route.params.listId),
|
||||
})
|
||||
|
||||
this.$store.dispatch('kanban/createBucket', newBucket)
|
||||
.then(() => {
|
||||
await this.$store.dispatch('kanban/createBucket', newBucket)
|
||||
this.newBucketTitle = ''
|
||||
this.showNewBucketInput = false
|
||||
})
|
||||
},
|
||||
|
||||
deleteBucketModal(bucketId) {
|
||||
if (this.buckets.length <= 1) {
|
||||
return
|
||||
|
@ -485,33 +486,39 @@ export default {
|
|||
this.bucketToDelete = bucketId
|
||||
this.showBucketDeleteModal = true
|
||||
},
|
||||
deleteBucket() {
|
||||
this.$store.dispatch('kanban/deleteBucket', {bucket: {
|
||||
|
||||
async deleteBucket() {
|
||||
const bucket = new BucketModel({
|
||||
id: this.bucketToDelete,
|
||||
listId: parseInt(this.$route.params.listId),
|
||||
}, params: this.params})
|
||||
.then(() => this.$message.success({message: this.$t('list.kanban.deleteBucketSuccess')}))
|
||||
.finally(() => {
|
||||
this.showBucketDeleteModal = false
|
||||
})
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('kanban/deleteBucket', {
|
||||
bucket,
|
||||
params: this.params,
|
||||
})
|
||||
this.$message.success({message: this.$t('list.kanban.deleteBucketSuccess')})
|
||||
} finally {
|
||||
this.showBucketDeleteModal = false
|
||||
}
|
||||
},
|
||||
|
||||
focusBucketTitle(e) {
|
||||
// This little helper allows us to drag a bucket around at the title without focusing on it right away.
|
||||
this.bucketTitleEditable = true
|
||||
this.$nextTick(() => e.target.focus())
|
||||
},
|
||||
|
||||
saveBucketTitle(bucketId, bucketTitle) {
|
||||
async saveBucketTitle(bucketId, bucketTitle) {
|
||||
const updatedBucketData = {
|
||||
id: bucketId,
|
||||
title: bucketTitle,
|
||||
}
|
||||
|
||||
this.$store.dispatch('kanban/updateBucketTitle', updatedBucketData)
|
||||
.then(() => {
|
||||
await this.$store.dispatch('kanban/updateBucketTitle', updatedBucketData)
|
||||
this.bucketTitleEditable = false
|
||||
this.$message.success({message: this.$t('list.kanban.bucketTitleSavedSuccess')})
|
||||
})
|
||||
},
|
||||
|
||||
updateBuckets(value) {
|
||||
|
@ -534,7 +541,8 @@ export default {
|
|||
|
||||
this.$store.dispatch('kanban/updateBucket', updatedData)
|
||||
},
|
||||
setBucketLimit(bucketId, limit) {
|
||||
|
||||
async setBucketLimit(bucketId, limit) {
|
||||
if (limit < 0) {
|
||||
return
|
||||
}
|
||||
|
@ -544,28 +552,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')}))
|
||||
.catch(e => this.$message.error(e))
|
||||
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)
|
||||
|
|
|
@ -290,7 +290,8 @@ export default {
|
|||
}
|
||||
sortTasks(this.tasks)
|
||||
},
|
||||
saveTaskPosition(e) {
|
||||
|
||||
async saveTaskPosition(e) {
|
||||
this.drag = false
|
||||
|
||||
const task = this.tasks[e.newIndex]
|
||||
|
@ -302,13 +303,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
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
const updatedTask = await this.$store.dispatch('tasks/update', newTask)
|
||||
this.tasks[e.newIndex] = updatedTask
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -63,23 +63,17 @@ export default {
|
|||
this.setTitle(this.$t('namespace.create.title'))
|
||||
},
|
||||
methods: {
|
||||
newNamespace() {
|
||||
async newNamespace() {
|
||||
if (this.namespace.title === '') {
|
||||
this.showError = true
|
||||
return
|
||||
}
|
||||
this.showError = false
|
||||
|
||||
this.namespaceService
|
||||
.create(this.namespace)
|
||||
.then((r) => {
|
||||
this.$store.commit('namespaces/addNamespace', r)
|
||||
const namespace = await this.namespaceService.create(this.namespace)
|
||||
this.$store.commit('namespaces/addNamespace', namespace)
|
||||
this.$message.success({message: this.$t('namespace.create.success') })
|
||||
this.$router.back()
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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,22 +31,18 @@ export default {
|
|||
this.$t('namespace.archive.titleArchive', { namespace: this.namespace.title })
|
||||
this.setTitle(this.title)
|
||||
},
|
||||
methods: {
|
||||
archiveNamespace() {
|
||||
|
||||
methods: {
|
||||
async archiveNamespace() {
|
||||
this.namespace.isArchived = !this.namespace.isArchived
|
||||
|
||||
this.namespaceService.update(this.namespace)
|
||||
.then(r => {
|
||||
this.$store.commit('namespaces/setNamespaceById', r)
|
||||
try {
|
||||
const namespace = await this.namespaceService.update(this.namespace)
|
||||
this.$store.commit('namespaces/setNamespaceById', namespace)
|
||||
this.$message.success({message: this.$t('namespace.archive.success')})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
} finally {
|
||||
this.$router.back()
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -35,15 +35,10 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
deleteNamespace() {
|
||||
this.$store.dispatch('namespaces/deleteNamespace', this.namespace)
|
||||
.then(() => {
|
||||
async deleteNamespace() {
|
||||
await this.$store.dispatch('namespaces/deleteNamespace', this.namespace)
|
||||
this.$message.success({message: this.$t('namespace.delete.success')})
|
||||
this.$router.push({name: 'home'})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
<div class="control">
|
||||
<editor
|
||||
:class="{ 'disabled': namespaceService.loading}"
|
||||
:disabled="namespaceService.loading"
|
||||
:preview-is-default="false"
|
||||
id="namespacedescription"
|
||||
:placeholder="$t('namespace.attributes.descriptionPlaceholder')"
|
||||
|
@ -93,8 +92,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,30 +102,20 @@ export default {
|
|||
this.$nextTick(() => this.editorActive = true)
|
||||
|
||||
const namespace = new NamespaceModel({id: this.$route.params.id})
|
||||
this.namespaceService.get(namespace)
|
||||
.then(r => {
|
||||
this.namespace = r
|
||||
this.namespace = await this.namespaceService.get(namespace)
|
||||
// This will trigger the dynamic loading of components once we actually have all the data to pass to them
|
||||
this.manageTeamsComponent = 'manageSharing'
|
||||
this.manageUsersComponent = 'manageSharing'
|
||||
this.title = this.$t('namespace.edit.title', {namespace: r.title})
|
||||
this.title = this.$t('namespace.edit.title', {namespace: this.namespace.title})
|
||||
this.setTitle(this.title)
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
save() {
|
||||
this.namespaceService.update(this.namespace)
|
||||
.then(r => {
|
||||
|
||||
async save() {
|
||||
const namespace = await this.namespaceService.update(this.namespace)
|
||||
// Update the namespace in the parent
|
||||
this.$store.commit('namespaces/setNamespaceById', r)
|
||||
this.$store.commit('namespaces/setNamespaceById', namespace)
|
||||
this.$message.success({message: this.$t('namespace.edit.success')})
|
||||
this.$router.back()
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -57,20 +57,14 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
loadNamespace() {
|
||||
async loadNamespace() {
|
||||
const namespace = new NamespaceModel({id: this.$route.params.id})
|
||||
this.namespaceService.get(namespace)
|
||||
.then(r => {
|
||||
this.namespace = r
|
||||
this.namespace = await this.namespaceService.get(namespace)
|
||||
// This will trigger the dynamic loading of components once we actually have all the data to pass to them
|
||||
this.manageTeamsComponent = 'manageSharing'
|
||||
this.manageUsersComponent = 'manageSharing'
|
||||
this.title = this.$t('namespace.share.title', { namespace: this.namespace.title })
|
||||
this.setTitle(this.title)
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ export default {
|
|||
'authLinkShare',
|
||||
]),
|
||||
methods: {
|
||||
auth() {
|
||||
async auth() {
|
||||
this.errorMessage = ''
|
||||
|
||||
if (this.authLinkShare) {
|
||||
|
@ -66,11 +66,13 @@ export default {
|
|||
|
||||
this.loading = true
|
||||
|
||||
this.$store.dispatch('auth/linkShareAuth', {hash: this.$route.params.share, password: this.password})
|
||||
.then((r) => {
|
||||
this.$router.push({name: 'list.list', params: {listId: r.list_id}})
|
||||
try {
|
||||
const r = await this.$store.dispatch('auth/linkShareAuth', {
|
||||
hash: this.$route.params.share,
|
||||
password: this.password,
|
||||
})
|
||||
.catch(e => {
|
||||
this.$router.push({name: 'list.list', params: {listId: r.list_id}})
|
||||
} catch(e) {
|
||||
if (typeof e.response.data.code !== 'undefined' && e.response.data.code === 13001) {
|
||||
this.authenticateWithPassword = true
|
||||
return
|
||||
|
@ -85,10 +87,9 @@ export default {
|
|||
errorMessage = this.$t('sharing.invalidPassword')
|
||||
}
|
||||
this.errorMessage = errorMessage
|
||||
})
|
||||
.finally(() => {
|
||||
} finally {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -33,10 +33,7 @@
|
|||
</h3>
|
||||
<div v-if="!showAll" class="mb-4">
|
||||
<x-button type="secondary" @click="showTodaysTasks()" class="mr-2">{{ $t('task.show.today') }}</x-button>
|
||||
<x-button type="secondary" @click="setDatesToNextWeek()" class="mr-2">{{
|
||||
$t('task.show.nextWeek')
|
||||
}}
|
||||
</x-button>
|
||||
<x-button type="secondary" @click="setDatesToNextWeek()" class="mr-2">{{ $t('task.show.nextWeek') }}</x-button>
|
||||
<x-button type="secondary" @click="setDatesToNextMonth()">{{ $t('task.show.nextMonth') }}</x-button>
|
||||
</div>
|
||||
<template v-if="!loading && (!tasks || tasks.length === 0) && showNothingToDo">
|
||||
|
@ -144,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.
|
||||
|
@ -203,27 +200,22 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
this.$store.dispatch('tasks/loadTasks', params)
|
||||
.then(r => {
|
||||
const tasks = await this.$store.dispatch('tasks/loadTasks', params)
|
||||
|
||||
// Sorting tasks with a due date so that the soonest or overdue are displayed at the top of the list.
|
||||
const tasksWithDueDates = r
|
||||
.filter(t => t.dueDate !== null)
|
||||
.sort((a, b) => a.dueDate > b.dueDate ? 1 : -1)
|
||||
|
||||
const tasksWithoutDueDates = r.filter(t => t.dueDate === null)
|
||||
|
||||
const tasks = [
|
||||
...tasksWithDueDates,
|
||||
...tasksWithoutDueDates,
|
||||
]
|
||||
|
||||
this.tasks = tasks
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
// 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) => {
|
||||
const sortByDueDate = b.dueDate - a.dueDate
|
||||
return sortByDueDate === 0
|
||||
? b.id - a.id
|
||||
: sortByDueDate
|
||||
})
|
||||
},
|
||||
|
||||
// FIXME: this modification should happen in the store
|
||||
updateTasks(updatedTask) {
|
||||
for (const t in this.tasks) {
|
||||
if (this.tasks[t].id === updatedTask.id) {
|
||||
|
@ -237,18 +229,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()
|
||||
|
|
|
@ -564,26 +564,22 @@ export default {
|
|||
return uploadFile(this.taskId, ...args)
|
||||
},
|
||||
|
||||
loadTask(taskId) {
|
||||
async loadTask(taskId) {
|
||||
if (taskId === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
this.taskService.get({id: taskId})
|
||||
.then(r => {
|
||||
this.task = r
|
||||
this.$store.commit('attachments/set', r.attachments)
|
||||
try {
|
||||
this.task = await this.taskService.get({id: taskId})
|
||||
this.$store.commit('attachments/set', this.task.attachments)
|
||||
this.taskColor = this.task.hexColor
|
||||
this.setActiveFields()
|
||||
this.setTitle(this.task.title)
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
this.$nextTick(() => this.visible = true)
|
||||
} finally {
|
||||
this.scrollToHeading()
|
||||
})
|
||||
await this.$nextTick()
|
||||
this.visible = true
|
||||
}
|
||||
},
|
||||
scrollToHeading() {
|
||||
this.$refs.heading.$el.scrollIntoView({block: 'center'})
|
||||
|
@ -623,7 +619,6 @@ export default {
|
|||
this.task.endDate = this.task.dueDate
|
||||
}
|
||||
|
||||
try {
|
||||
this.task = await this.$store.dispatch('tasks/update', this.task)
|
||||
this.setActiveFields()
|
||||
|
||||
|
@ -639,10 +634,8 @@ export default {
|
|||
}]
|
||||
}
|
||||
this.$message.success({message: this.$t('task.detail.updateSuccess')}, actions)
|
||||
} catch(e) {
|
||||
this.$message.error(e)
|
||||
}
|
||||
},
|
||||
|
||||
setFieldActive(fieldName) {
|
||||
this.activeFields[fieldName] = true
|
||||
this.$nextTick(() => {
|
||||
|
@ -661,16 +654,13 @@ export default {
|
|||
}
|
||||
})
|
||||
},
|
||||
deleteTask() {
|
||||
this.$store.dispatch('tasks/delete', this.task)
|
||||
.then(() => {
|
||||
|
||||
async deleteTask() {
|
||||
await this.$store.dispatch('tasks/delete', this.task)
|
||||
this.$message.success({message: this.$t('task.detail.deleteSuccess')})
|
||||
this.$router.push({name: 'list.index', params: {listId: this.task.listId}})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
|
||||
toggleTaskDone() {
|
||||
this.task.done = !this.task.done
|
||||
|
||||
|
@ -678,39 +668,26 @@ export default {
|
|||
playPop()
|
||||
}
|
||||
|
||||
this.saveTask(true, () => this.toggleTaskDone())
|
||||
this.saveTask(true, this.toggleTaskDone)
|
||||
},
|
||||
|
||||
setDescriptionChanged(e) {
|
||||
if (e.key === 'Enter' || e.key === 'Control') {
|
||||
return
|
||||
}
|
||||
this.descriptionChanged = true
|
||||
},
|
||||
saveTaskIfDescriptionChanged() {
|
||||
// We want to only save the description if it was changed.
|
||||
// Since we can either trigger this with ctrl+enter or @change, it would be possible to save a task first
|
||||
// with ctrl+enter and then with @change although nothing changed since the last save when @change gets fired.
|
||||
// To only save one time we added this method.
|
||||
if (this.descriptionChanged) {
|
||||
this.descriptionChanged = false
|
||||
this.saveTask()
|
||||
}
|
||||
},
|
||||
|
||||
async changeList(list) {
|
||||
this.$store.commit('kanban/removeTaskInBucket', this.task)
|
||||
this.task.listId = list.id
|
||||
await this.saveTask()
|
||||
},
|
||||
toggleFavorite() {
|
||||
|
||||
async toggleFavorite() {
|
||||
this.task.isFavorite = !this.task.isFavorite
|
||||
this.taskService.update(this.task)
|
||||
.then(t => {
|
||||
this.task = t
|
||||
this.task = await this.taskService.update(this.task)
|
||||
this.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -219,83 +219,57 @@ export default {
|
|||
userInfo: (state) => state.auth.info,
|
||||
}),
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadTeam() {
|
||||
async loadTeam() {
|
||||
this.team = new TeamModel({id: this.teamId})
|
||||
this.teamService
|
||||
.get(this.team)
|
||||
.then((response) => {
|
||||
this.team = response
|
||||
this.team = await this.teamService.get(this.team)
|
||||
this.title = this.$t('team.edit.title', {team: this.team.name})
|
||||
this.setTitle(this.title)
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
save() {
|
||||
|
||||
async save() {
|
||||
if (this.team.name === '') {
|
||||
this.showError = true
|
||||
return
|
||||
}
|
||||
this.showError = false
|
||||
|
||||
this.teamService
|
||||
.update(this.team)
|
||||
.then((response) => {
|
||||
this.team = response
|
||||
this.team = await this.teamService.update(this.team)
|
||||
this.$message.success({message: this.$t('team.edit.success')})
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
deleteTeam() {
|
||||
this.teamService
|
||||
.delete(this.team)
|
||||
.then(() => {
|
||||
|
||||
async deleteTeam() {
|
||||
await this.teamService.delete(this.team)
|
||||
this.$message.success({message: this.$t('team.edit.delete.success')})
|
||||
this.$router.push({name: 'teams.index'})
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
deleteUser() {
|
||||
this.teamMemberService
|
||||
.delete(this.member)
|
||||
.then(() => {
|
||||
|
||||
async deleteUser() {
|
||||
try {
|
||||
await this.teamMemberService.delete(this.member)
|
||||
this.$message.success({message: this.$t('team.edit.deleteUser.success')})
|
||||
this.loadTeam()
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
} finally {
|
||||
this.showUserDeleteModal = false
|
||||
})
|
||||
}
|
||||
},
|
||||
addUser() {
|
||||
|
||||
async addUser() {
|
||||
const newMember = new TeamMemberModel({
|
||||
teamId: this.teamId,
|
||||
username: this.newMember.username,
|
||||
})
|
||||
this.teamMemberService
|
||||
.create(newMember)
|
||||
.then(() => {
|
||||
await this.teamMemberService.create(newMember)
|
||||
this.loadTeam()
|
||||
this.$message.success({message: this.$t('team.edit.userAddedSuccess')})
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
toggleUserType(member) {
|
||||
|
||||
async toggleUserType(member) {
|
||||
// FIXME: direct manipulation
|
||||
member.admin = !member.admin
|
||||
member.teamId = this.teamId
|
||||
this.teamMemberService
|
||||
.update(member)
|
||||
.then((r) => {
|
||||
const r = await this.teamMemberService.update(member)
|
||||
for (const tm in this.team.members) {
|
||||
if (this.team.members[tm].id === member.id) {
|
||||
this.team.members[tm].admin = r.admin
|
||||
|
@ -307,26 +281,17 @@ export default {
|
|||
this.$t('team.edit.madeAdmin') :
|
||||
this.$t('team.edit.madeMember'),
|
||||
})
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
findUser(query) {
|
||||
|
||||
async findUser(query) {
|
||||
if (query === '') {
|
||||
this.clearAll()
|
||||
return
|
||||
}
|
||||
|
||||
this.userService
|
||||
.getAll({}, {s: query})
|
||||
.then((response) => {
|
||||
this.foundUsers = response
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
this.foundUsers = await this.userService.getAll({}, {s: query})
|
||||
},
|
||||
|
||||
clearAll() {
|
||||
this.foundUsers = []
|
||||
},
|
||||
|
|
|
@ -43,14 +43,8 @@ export default {
|
|||
this.setTitle(this.$t('team.title'))
|
||||
},
|
||||
methods: {
|
||||
loadTeams() {
|
||||
this.teamService.getAll()
|
||||
.then(response => {
|
||||
this.teams = response
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
async loadTeams() {
|
||||
this.teams = await this.teamService.getAll()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -49,25 +49,19 @@ export default {
|
|||
this.setTitle(this.$t('team.create.title'))
|
||||
},
|
||||
methods: {
|
||||
newTeam() {
|
||||
async newTeam() {
|
||||
if (this.team.name === '') {
|
||||
this.showError = true
|
||||
return
|
||||
}
|
||||
this.showError = false
|
||||
|
||||
this.teamService
|
||||
.create(this.team)
|
||||
.then((response) => {
|
||||
const response = await this.teamService.create(this.team)
|
||||
this.$router.push({
|
||||
name: 'teams.edit',
|
||||
params: { id: response.id },
|
||||
})
|
||||
this.$message.success({message: this.$t('team.create.success') })
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -57,7 +57,6 @@ export default {
|
|||
}
|
||||
|
||||
this.dataExportService.download(this.password)
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -187,7 +187,8 @@ export default {
|
|||
this.loading = false
|
||||
}
|
||||
},
|
||||
submit() {
|
||||
|
||||
async submit() {
|
||||
this.$store.commit(ERROR_MESSAGE, '')
|
||||
// Some browsers prevent Vue bindings from working with autofilled values.
|
||||
// To work around this, we're manually getting the values here instead of relying on vue bindings.
|
||||
|
@ -201,11 +202,10 @@ export default {
|
|||
credentials.totpPasscode = this.$refs.totpPasscode.value
|
||||
}
|
||||
|
||||
this.$store.dispatch('auth/login', credentials)
|
||||
.then(() => {
|
||||
try {
|
||||
await this.$store.dispatch('auth/login', credentials)
|
||||
this.$store.commit('auth/needsTotpPasscode', false)
|
||||
})
|
||||
.catch(e => {
|
||||
} catch(e) {
|
||||
if (e.response && e.response.data.code === 1017 && !credentials.totpPasscode) {
|
||||
return
|
||||
}
|
||||
|
@ -217,8 +217,9 @@ export default {
|
|||
}
|
||||
|
||||
this.$store.commit(ERROR_MESSAGE, err[0])
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
redirectToProvider(provider) {
|
||||
redirectToProvider(provider, this.openidConnect.redirectUrl)
|
||||
},
|
||||
|
|
|
@ -26,7 +26,7 @@ export default {
|
|||
this.authenticateWithCode()
|
||||
},
|
||||
methods: {
|
||||
authenticateWithCode() {
|
||||
async authenticateWithCode() {
|
||||
// This component gets mounted twice: The first time when the actual auth request hits the frontend,
|
||||
// the second time after that auth request succeeded and the outer component "content-no-auth" isn't used
|
||||
// but instead the "content-auth" component is used. Because this component is just a route and thus
|
||||
|
@ -59,11 +59,11 @@ export default {
|
|||
|
||||
this.$store.commit(ERROR_MESSAGE, '')
|
||||
|
||||
this.$store.dispatch('auth/openIdAuth', {
|
||||
try {
|
||||
await this.$store.dispatch('auth/openIdAuth', {
|
||||
provider: this.$route.params.provider,
|
||||
code: this.$route.query.code,
|
||||
})
|
||||
.then(() => {
|
||||
const last = getLastVisited()
|
||||
if (last !== null) {
|
||||
this.$router.push({
|
||||
|
@ -74,8 +74,7 @@ export default {
|
|||
} else {
|
||||
this.$router.push({name: 'home'})
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
} catch(e) {
|
||||
const err = getErrorText(e)
|
||||
if (typeof err[1] !== 'undefined') {
|
||||
this.$store.commit(ERROR_MESSAGE, err[1])
|
||||
|
@ -83,10 +82,9 @@ export default {
|
|||
}
|
||||
|
||||
this.$store.commit(ERROR_MESSAGE, err[0])
|
||||
})
|
||||
.finally(() => {
|
||||
} finally {
|
||||
localStorage.removeItem('authenticating')
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -85,11 +85,13 @@ export default {
|
|||
successMessage: '',
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.setTitle(this.$t('user.auth.resetPassword'))
|
||||
},
|
||||
|
||||
methods: {
|
||||
submit() {
|
||||
async submit() {
|
||||
this.errorMsg = ''
|
||||
|
||||
if (this.credentials.password2 !== this.credentials.password) {
|
||||
|
@ -98,14 +100,13 @@ export default {
|
|||
}
|
||||
|
||||
let passwordReset = new PasswordResetModel({newPassword: this.credentials.password})
|
||||
this.passwordResetService.resetPassword(passwordReset)
|
||||
.then(response => {
|
||||
this.successMessage = response.message
|
||||
try {
|
||||
const { message } = this.passwordResetService.resetPassword(passwordReset)
|
||||
this.successMessage = message
|
||||
localStorage.removeItem('passwordResetToken')
|
||||
})
|
||||
.catch(e => {
|
||||
} catch(e) {
|
||||
this.errorMsg = e.response.data.message
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -147,7 +147,6 @@ export default {
|
|||
}
|
||||
|
||||
this.$store.dispatch('auth/register', credentials)
|
||||
.catch(() => {})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -69,15 +69,14 @@ export default {
|
|||
this.setTitle(this.$t('user.auth.resetPassword'))
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
async submit() {
|
||||
this.errorMsg = ''
|
||||
this.passwordResetService.requestResetPassword(this.passwordReset)
|
||||
.then(() => {
|
||||
try {
|
||||
await this.passwordResetService.requestResetPassword(this.passwordReset)
|
||||
this.isSuccess = true
|
||||
})
|
||||
.catch(e => {
|
||||
} catch(e) {
|
||||
this.errorMsg = e.response.data.message
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -385,90 +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(() => {
|
||||
await this.passwordUpdateService.update(this.passwordUpdate)
|
||||
this.$message.success({message: this.$t('user.settings.passwordUpdateSuccess')})
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
updateEmail() {
|
||||
this.emailUpdateService.update(this.emailUpdate)
|
||||
.then(() => {
|
||||
|
||||
async updateEmail() {
|
||||
await this.emailUpdateService.update(this.emailUpdate)
|
||||
this.$message.success({message: this.$t('user.settings.updateEmailSuccess')})
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
totpStatus() {
|
||||
|
||||
async totpStatus() {
|
||||
if (!this.totpEnabled) {
|
||||
return
|
||||
}
|
||||
this.totpService.get()
|
||||
.then(r => {
|
||||
this.totp = r
|
||||
try {
|
||||
this.totp = await this.totpService.get()
|
||||
this.totpSetQrCode()
|
||||
})
|
||||
.catch(e => {
|
||||
} catch(e) {
|
||||
// Error code 1016 means totp is not enabled, we don't need an error in that case.
|
||||
if (e.response && e.response.data && e.response.data.code && e.response.data.code === 1016) {
|
||||
this.totpEnrolled = false
|
||||
return
|
||||
}
|
||||
|
||||
this.$message.error(e)
|
||||
})
|
||||
throw e
|
||||
}
|
||||
},
|
||||
totpSetQrCode() {
|
||||
this.totpService.qrcode()
|
||||
.then(qr => {
|
||||
|
||||
async totpSetQrCode() {
|
||||
const qr = await this.totpService.qrcode()
|
||||
const urlCreator = window.URL || window.webkitURL
|
||||
this.totpQR = urlCreator.createObjectURL(qr)
|
||||
})
|
||||
},
|
||||
totpEnroll() {
|
||||
this.totpService.enroll()
|
||||
.then(r => {
|
||||
|
||||
async totpEnroll() {
|
||||
this.totp = await this.totpService.enroll()
|
||||
this.totpEnrolled = true
|
||||
this.totp = r
|
||||
this.totpSetQrCode()
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
totpConfirm() {
|
||||
this.totpService.enable({passcode: this.totpConfirmPasscode})
|
||||
.then(() => {
|
||||
|
||||
async totpConfirm() {
|
||||
await this.totpService.enable({passcode: this.totpConfirmPasscode})
|
||||
this.totp.enabled = true
|
||||
this.$message.success({message: this.$t('user.settings.totp.confirmSuccess')})
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
totpDisable() {
|
||||
this.totpService.disable({password: this.totpDisablePassword})
|
||||
.then(() => {
|
||||
|
||||
async totpDisable() {
|
||||
await this.totpService.disable({password: this.totpDisablePassword})
|
||||
this.totpEnrolled = false
|
||||
this.totp = new TotpModel()
|
||||
this.$message.success({message: this.$t('user.settings.totp.disableSuccess')})
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
updateSettings() {
|
||||
|
||||
async updateSettings() {
|
||||
localStorage.setItem(playSoundWhenDoneKey, this.playSoundWhenDone)
|
||||
saveLanguage(this.language)
|
||||
setQuickAddMagicMode(this.quickAddMagicMode)
|
||||
this.settings.defaultListId = this.defaultList ? this.defaultList.id : 0
|
||||
|
||||
this.userSettingsService.update(this.settings)
|
||||
.then(() => {
|
||||
await this.userSettingsService.update(this.settings)
|
||||
this.$store.commit('auth/setUserSettings', this.settings)
|
||||
this.$message.success({message: this.$t('user.settings.general.savedSuccess')})
|
||||
})
|
||||
.catch(e => this.$message.error(e))
|
||||
},
|
||||
|
||||
anchorHashCheck() {
|
||||
if (window.location.hash === this.$route.hash) {
|
||||
const el = document.getElementById(this.$route.hash.slice(1))
|
||||
|
|
Loading…
Reference in a new issue