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