forked from vikunja/frontend
Compare commits
56 Commits
9aaac69e29
...
1efe096968
Author | SHA1 | Date |
---|---|---|
Adrian Simmons | 1efe096968 | |
Adrian Simmons | 8e4095e1bd | |
renovate | e535584412 | |
renovate | c5b9e2a1ff | |
Dominik Pschenitschni | e45bc83132 | |
Dominik Pschenitschni | bc8b04fc7a | |
Dominik Pschenitschni | 84284a6211 | |
Dominik Pschenitschni | 716de2c99c | |
Dominik Pschenitschni | 769d94e879 | |
Dominik Pschenitschni | baa86530c8 | |
renovate | 7613afbf27 | |
renovate | aeb886e4c5 | |
renovate | 306bd1c179 | |
renovate | f3cf79fa65 | |
renovate | 8c945b049a | |
renovate | f69111c105 | |
renovate | 03afbfc6c8 | |
renovate | c07288dd8b | |
renovate | 03f3c52548 | |
renovate | 384037c694 | |
kolaente | 734db0795c | |
renovate | 8f6c0f3738 | |
drone | f3324c6aaf | |
konrad | f8d009a6aa | |
dpschen | 59e915cc10 | |
dpschen | 0c9dad9891 | |
dpschen | b7ad29f056 | |
renovate | a7434f24df | |
drone | f61d5bac46 | |
renovate | 2911dee3cc | |
kolaente | 58c361ee29 | |
renovate | d3fc1439b5 | |
renovate | bb544c353c | |
renovate | cc90a1cea5 | |
renovate | cffba33748 | |
renovate | 055e0a2901 | |
renovate | c8d1921bcd | |
renovate | 9e09314f75 | |
renovate | c3833d90d8 | |
renovate | e24cb55e1b | |
renovate | f897611ad1 | |
renovate | ea8fe297d9 | |
renovate | c6b604f1fa | |
renovate | 4792adfbd1 | |
dpschen | 04c94418d7 | |
renovate | 12bec04c42 | |
renovate | e8eb94d71b | |
renovate | 5137f9f6cb | |
renovate | a2f65d86c2 | |
renovate | 08dcc897cc | |
renovate | c975fb0fee | |
Adrian Simmons | 350c4260c2 | |
drone | b73cf344b6 | |
kolaente | d4b45dc255 | |
kolaente | e2beaba3b9 | |
renovate | dd9be97793 |
|
@ -99,7 +99,7 @@ steps:
|
||||||
- dependencies
|
- dependencies
|
||||||
|
|
||||||
- name: test-frontend
|
- name: test-frontend
|
||||||
image: cypress/browsers:node14.17.0-chrome91-ff89
|
image: cypress/browsers:node16.5.0-chrome94-ff93
|
||||||
pull: true
|
pull: true
|
||||||
environment:
|
environment:
|
||||||
CYPRESS_API_URL: http://api:3456/api/v1
|
CYPRESS_API_URL: http://api:3456/api/v1
|
||||||
|
|
|
@ -8,7 +8,7 @@ const testAndAssertFailed = fixture => {
|
||||||
|
|
||||||
cy.wait(5000) // It can take waaaayy too long to log the user in
|
cy.wait(5000) // It can take waaaayy too long to log the user in
|
||||||
cy.url().should('include', '/')
|
cy.url().should('include', '/')
|
||||||
cy.get('div.notification.is-danger').contains('Wrong username or password.')
|
cy.get('div.message.danger').contains('Wrong username or password.')
|
||||||
}
|
}
|
||||||
|
|
||||||
context('Login', () => {
|
context('Login', () => {
|
||||||
|
|
|
@ -32,7 +32,7 @@ context('Registration', () => {
|
||||||
cy.get('h2').should('contain', `Hi ${fixture.username}!`)
|
cy.get('h2').should('contain', `Hi ${fixture.username}!`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should fail', () => {
|
it.only('Should fail', () => {
|
||||||
const fixture = {
|
const fixture = {
|
||||||
username: 'test',
|
username: 'test',
|
||||||
password: '123456',
|
password: '123456',
|
||||||
|
@ -45,6 +45,6 @@ context('Registration', () => {
|
||||||
cy.get('#password').type(fixture.password)
|
cy.get('#password').type(fixture.password)
|
||||||
cy.get('#passwordValidation').type(fixture.password)
|
cy.get('#passwordValidation').type(fixture.password)
|
||||||
cy.get('#register-submit').click()
|
cy.get('#register-submit').click()
|
||||||
cy.get('div.notification.is-danger').contains('A user with this username already exists.')
|
cy.get('div.message.danger').contains('A user with this username already exists.')
|
||||||
})
|
})
|
||||||
})
|
})
|
53
package.json
53
package.json
|
@ -17,17 +17,17 @@
|
||||||
"browserslist:update": "npx browserslist@latest --update-db"
|
"browserslist:update": "npx browserslist@latest --update-db"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bulvar/bulma": "^0.9.3",
|
"@bulvar/bulma": "^0.9.4",
|
||||||
"@github/hotkey": "1.6.0",
|
"@github/hotkey": "1.6.0",
|
||||||
"@kyvg/vue3-notification": "2.3.4",
|
"@kyvg/vue3-notification": "2.3.4",
|
||||||
"@sentry/tracing": "6.15.0",
|
"@sentry/tracing": "6.15.0",
|
||||||
"@sentry/vue": "6.15.0",
|
"@sentry/vue": "6.15.0",
|
||||||
"@vue/compat": "3.2.22",
|
"@vue/compat": "3.2.23",
|
||||||
"@vueuse/core": "7.0.1",
|
"@vueuse/core": "7.1.2",
|
||||||
"camel-case": "4.1.2",
|
"camel-case": "4.1.2",
|
||||||
"codemirror": "5.64.0",
|
"codemirror": "5.64.0",
|
||||||
"copy-to-clipboard": "3.3.1",
|
"copy-to-clipboard": "3.3.1",
|
||||||
"date-fns": "2.26.0",
|
"date-fns": "2.27.0",
|
||||||
"dompurify": "2.3.3",
|
"dompurify": "2.3.3",
|
||||||
"easymde": "2.15.0",
|
"easymde": "2.15.0",
|
||||||
"flatpickr": "4.6.9",
|
"flatpickr": "4.6.9",
|
||||||
|
@ -36,57 +36,58 @@
|
||||||
"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",
|
"lodash.debounce": "4.0.8",
|
||||||
"marked": "4.0.4",
|
"marked": "4.0.5",
|
||||||
"register-service-worker": "1.7.2",
|
"register-service-worker": "1.7.2",
|
||||||
"snake-case": "3.0.4",
|
"snake-case": "3.0.4",
|
||||||
"ufo": "0.7.9",
|
"ufo": "0.7.9",
|
||||||
"v-tooltip": "4.0.0-beta.2",
|
"v-tooltip": "4.0.0-beta.2",
|
||||||
"vue": "3.2.22",
|
"vue": "3.2.23",
|
||||||
"vue-advanced-cropper": "2.7.0",
|
"vue-advanced-cropper": "2.7.0",
|
||||||
"vue-drag-resize": "2.0.3",
|
"vue-drag-resize": "2.0.3",
|
||||||
"vue-flatpickr-component": "9.0.5",
|
"vue-flatpickr-component": "9.0.5",
|
||||||
"vue-i18n": "9.2.0-beta.20",
|
"vue-i18n": "9.2.0-beta.22",
|
||||||
"vue-router": "4.0.12",
|
"vue-router": "4.0.12",
|
||||||
"vuedraggable": "4.1.0",
|
"vuedraggable": "4.1.0",
|
||||||
"vuex": "4.0.2",
|
"vuex": "4.0.2",
|
||||||
"workbox-precaching": "6.4.1"
|
"workbox-precaching": "6.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@4tw/cypress-drag-drop": "2.0.0",
|
"@4tw/cypress-drag-drop": "2.1.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||||
"@fortawesome/vue-fontawesome": "3.0.0-5",
|
"@fortawesome/vue-fontawesome": "3.0.0-5",
|
||||||
"@types/flexsearch": "0.7.2",
|
"@types/flexsearch": "0.7.2",
|
||||||
"@types/jest": "27.0.3",
|
"@types/jest": "27.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "5.4.0",
|
"@typescript-eslint/eslint-plugin": "5.5.0",
|
||||||
"@typescript-eslint/parser": "5.4.0",
|
"@typescript-eslint/parser": "5.5.0",
|
||||||
"@vitejs/plugin-legacy": "1.6.3",
|
"@vitejs/plugin-legacy": "1.6.3",
|
||||||
"@vitejs/plugin-vue": "1.10.0",
|
"@vitejs/plugin-vue": "1.10.1",
|
||||||
"@vue/eslint-config-typescript": "9.1.0",
|
"@vue/eslint-config-typescript": "9.1.0",
|
||||||
|
"autoprefixer": "10.4.0",
|
||||||
"axios": "0.24.0",
|
"axios": "0.24.0",
|
||||||
"browserslist": "4.18.1",
|
"browserslist": "4.18.1",
|
||||||
"cypress": "8.7.0",
|
"cypress": "8.7.0",
|
||||||
"cypress-file-upload": "5.0.8",
|
"cypress-file-upload": "5.0.8",
|
||||||
"esbuild": "0.13.15",
|
"esbuild": "0.14.1",
|
||||||
"eslint": "8.3.0",
|
"eslint": "8.3.0",
|
||||||
"eslint-plugin-vue": "8.1.1",
|
"eslint-plugin-vue": "8.1.1",
|
||||||
"express": "4.17.1",
|
"express": "4.17.1",
|
||||||
"faker": "5.5.3",
|
"faker": "5.5.3",
|
||||||
"jest": "27.3.1",
|
"jest": "27.4.2",
|
||||||
"netlify-cli": "7.1.0",
|
"netlify-cli": "8.0.6",
|
||||||
"postcss": "8.3.11",
|
"postcss": "8.4.4",
|
||||||
"postcss-preset-env": "7.0.1",
|
"postcss-preset-env": "7.0.1",
|
||||||
"rollup": "2.60.1",
|
"rollup": "2.60.2",
|
||||||
"rollup-plugin-visualizer": "5.5.2",
|
"rollup-plugin-visualizer": "5.5.2",
|
||||||
"sass": "1.43.4",
|
"sass": "1.44.0",
|
||||||
"slugify": "1.6.3",
|
"slugify": "1.6.3",
|
||||||
"ts-jest": "27.0.7",
|
"ts-jest": "27.0.7",
|
||||||
"typescript": "4.5.2",
|
"typescript": "4.5.2",
|
||||||
"vite": "2.6.14",
|
"vite": "2.6.14",
|
||||||
"vite-plugin-pwa": "0.11.7",
|
"vite-plugin-pwa": "0.11.9",
|
||||||
"vite-svg-loader": "3.1.0",
|
"vite-svg-loader": "3.1.0",
|
||||||
"vue-tsc": "0.29.6",
|
"vue-tsc": "0.29.8",
|
||||||
"wait-on": "6.0.0",
|
"wait-on": "6.0.0",
|
||||||
"workbox-cli": "6.4.1"
|
"workbox-cli": "6.4.1"
|
||||||
},
|
},
|
||||||
|
@ -95,7 +96,8 @@
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"es2021": true,
|
"es2021": true,
|
||||||
"node": true
|
"node": true,
|
||||||
|
"vue/setup-compiler-macros": true
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
|
@ -119,6 +121,7 @@
|
||||||
"error",
|
"error",
|
||||||
"never"
|
"never"
|
||||||
],
|
],
|
||||||
|
"vue/script-setup-uses-vars": "error",
|
||||||
"vue/multi-word-component-names": 0
|
"vue/multi-word-component-names": 0
|
||||||
},
|
},
|
||||||
"parser": "vue-eslint-parser",
|
"parser": "vue-eslint-parser",
|
||||||
|
@ -129,7 +132,10 @@
|
||||||
"ignorePatterns": [
|
"ignorePatterns": [
|
||||||
"*.test.*",
|
"*.test.*",
|
||||||
"cypress/*"
|
"cypress/*"
|
||||||
]
|
],
|
||||||
|
"globals": {
|
||||||
|
"defineProps": "readonly"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"postcss": {
|
"postcss": {
|
||||||
"plugins": {
|
"plugins": {
|
||||||
|
@ -154,5 +160,6 @@
|
||||||
"json"
|
"json"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"license": "AGPL-3.0-or-later"
|
"license": "AGPL-3.0-or-later",
|
||||||
}
|
"packageManager": "yarn@1.22.17"
|
||||||
|
}
|
|
@ -82,7 +82,7 @@ export default {
|
||||||
this.$route.name === 'labels.index' ||
|
this.$route.name === 'labels.index' ||
|
||||||
this.$route.name === 'migrate.start' ||
|
this.$route.name === 'migrate.start' ||
|
||||||
this.$route.name === 'migrate.wunderlist' ||
|
this.$route.name === 'migrate.wunderlist' ||
|
||||||
this.$route.name === 'user.settings' ||
|
this.$route.name.startsWith('user.settings') ||
|
||||||
this.$route.name === 'namespaces.index'
|
this.$route.name === 'namespaces.index'
|
||||||
) {
|
) {
|
||||||
return this.$store.dispatch(CURRENT_LIST, null)
|
return this.$store.dispatch(CURRENT_LIST, null)
|
||||||
|
|
|
@ -30,27 +30,25 @@
|
||||||
<a @click="() => (configureApi = true)">{{ $t('apiConfig.change') }}</a>
|
<a @click="() => (configureApi = true)">{{ $t('apiConfig.change') }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<message variant="success" v-if="successMsg !== '' && errorMsg === ''" class="mt-2">
|
||||||
class="notification is-success mt-2"
|
|
||||||
v-if="successMsg !== '' && errorMsg === ''"
|
|
||||||
>
|
|
||||||
{{ successMsg }}
|
{{ successMsg }}
|
||||||
</div>
|
</message>
|
||||||
<div
|
<message variant="danger" v-if="errorMsg !== '' && successMsg === ''" class="mt-2">
|
||||||
class="notification is-danger mt-2"
|
|
||||||
v-if="errorMsg !== '' && successMsg === ''"
|
|
||||||
>
|
|
||||||
{{ errorMsg }}
|
{{ errorMsg }}
|
||||||
</div>
|
</message>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Message from '@/components/misc/message'
|
||||||
import {parseURL} from 'ufo'
|
import {parseURL} from 'ufo'
|
||||||
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
|
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'apiConfig',
|
name: 'apiConfig',
|
||||||
|
components: {
|
||||||
|
Message,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
configureApi: false,
|
configureApi: false,
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="notification is-danger">
|
<message variant="danger">
|
||||||
<i18n-t keypath="loadingError.failed">
|
<i18n-t keypath="loadingError.failed">
|
||||||
<a @click="reload">{{ $t('loadingError.tryAgain') }}</a>
|
<a @click="reload">{{ $t('loadingError.tryAgain') }}</a>
|
||||||
<a href="https://vikunja.io/contact/" rel="noreferrer noopener nofollow" target="_blank">{{ $t('loadingError.contact') }}</a>
|
<a href="https://vikunja.io/contact/" rel="noreferrer noopener nofollow" target="_blank">{{ $t('loadingError.contact') }}</a>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
</div>
|
</message>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Message from '@/components/misc/message'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'error',
|
name: 'error',
|
||||||
|
components: {Message},
|
||||||
methods: {
|
methods: {
|
||||||
reload() {
|
reload() {
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
|
|
|
@ -4,13 +4,11 @@
|
||||||
<template v-for="(s, i) in shortcuts" :key="i">
|
<template v-for="(s, i) in shortcuts" :key="i">
|
||||||
<h3>{{ $t(s.title) }}</h3>
|
<h3>{{ $t(s.title) }}</h3>
|
||||||
|
|
||||||
<div class="message is-primary">
|
<message>
|
||||||
<div class="message-body">
|
{{
|
||||||
{{
|
s.available($route) ? $t('keyboardShortcuts.currentPageOnly') : $t('keyboardShortcuts.allPages')
|
||||||
s.available($route) ? $t('keyboardShortcuts.currentPageOnly') : $t('keyboardShortcuts.allPages')
|
}}
|
||||||
}}
|
</message>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<dl class="shortcut-list">
|
<dl class="shortcut-list">
|
||||||
<template v-for="(sc, si) in s.shortcuts" :key="si">
|
<template v-for="(sc, si) in s.shortcuts" :key="si">
|
||||||
|
@ -30,11 +28,15 @@
|
||||||
<script>
|
<script>
|
||||||
import {KEYBOARD_SHORTCUTS_ACTIVE} from '@/store/mutation-types'
|
import {KEYBOARD_SHORTCUTS_ACTIVE} from '@/store/mutation-types'
|
||||||
import Shortcut from '@/components/misc/shortcut.vue'
|
import Shortcut from '@/components/misc/shortcut.vue'
|
||||||
|
import Message from '@/components/misc/message'
|
||||||
import {KEYBOARD_SHORTCUTS} from './shortcuts'
|
import {KEYBOARD_SHORTCUTS} from './shortcuts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'keyboard-shortcuts',
|
name: 'keyboard-shortcuts',
|
||||||
components: {Shortcut},
|
components: {
|
||||||
|
Message,
|
||||||
|
Shortcut,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
shortcuts: KEYBOARD_SHORTCUTS,
|
shortcuts: KEYBOARD_SHORTCUTS,
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
<template>
|
||||||
|
<div class="message" :class="variant">
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
variant: {
|
||||||
|
type: String,
|
||||||
|
default: 'info',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.message {
|
||||||
|
padding: .75rem 1rem;
|
||||||
|
border-radius: $radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
border: 1px solid var(--primary);
|
||||||
|
background: hsla(var(--primary-hsl), .05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
border: 1px solid var(--danger);
|
||||||
|
background: hsla(var(--danger-h), var(--danger-s), var(--danger-l), .05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
border: 1px solid var(--warning);
|
||||||
|
background: hsla(var(--warning-h), var(--warning-s), var(--warning-l), .05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
border: 1px solid var(--success);
|
||||||
|
background: hsla(var(--success-h), var(--success-s), var(--success-l), .05);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,14 +2,9 @@
|
||||||
<div class="no-auth-wrapper">
|
<div class="no-auth-wrapper">
|
||||||
<div class="noauth-container">
|
<div class="noauth-container">
|
||||||
<Logo class="logo" width="400" height="117" />
|
<Logo class="logo" width="400" height="117" />
|
||||||
<div class="message is-info" v-if="motd !== ''">
|
<message v-if="motd !== ''" class="my-2">
|
||||||
<div class="message-header">
|
{{ motd }}
|
||||||
<p>{{ $t('misc.info') }}</p>
|
</message>
|
||||||
</div>
|
|
||||||
<div class="message-body">
|
|
||||||
{{ motd }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<slot/>
|
<slot/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,6 +12,7 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import Logo from '@/components/home/Logo.vue'
|
import Logo from '@/components/home/Logo.vue'
|
||||||
|
import message from '@/components/misc/message'
|
||||||
import {useStore} from 'vuex'
|
import {useStore} from 'vuex'
|
||||||
import {computed} from 'vue'
|
import {computed} from 'vue'
|
||||||
|
|
||||||
|
@ -26,7 +22,7 @@ const motd = computed(() => store.state.config.motd)
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.no-auth-wrapper {
|
.no-auth-wrapper {
|
||||||
background: url('@/assets/llama.svg') no-repeat bottom left fixed var(--site-background);
|
background: url('@/assets/llama.svg?url') no-repeat bottom left fixed var(--site-background);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,5 +35,6 @@ const motd = computed(() => store.state.config.motd)
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
color: var(--logo-text-color);
|
color: var(--logo-text-color);
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<p v-if="error === errorNoApiUrl">
|
<p v-if="error === errorNoApiUrl">
|
||||||
{{ $t('ready.noApiUrlConfigured') }}
|
{{ $t('ready.noApiUrlConfigured') }}
|
||||||
</p>
|
</p>
|
||||||
<div class="notification is-danger" v-else>
|
<message variant="danger" v-else>
|
||||||
<p>
|
<p>
|
||||||
{{ $t('ready.errorOccured') }}<br/>
|
{{ $t('ready.errorOccured') }}<br/>
|
||||||
{{ error }}
|
{{ error }}
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
<p>
|
<p>
|
||||||
{{ $t('ready.checkApiUrl') }}
|
{{ $t('ready.checkApiUrl') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</message>
|
||||||
<api-config :configure-open="true" @found-api="load"/>
|
<api-config :configure-open="true" @found-api="load"/>
|
||||||
</card>
|
</card>
|
||||||
</no-auth-wrapper>
|
</no-auth-wrapper>
|
||||||
|
@ -43,6 +43,7 @@
|
||||||
<script>
|
<script>
|
||||||
import Logo from '@/assets/logo.svg?component'
|
import Logo from '@/assets/logo.svg?component'
|
||||||
import ApiConfig from '@/components/misc/api-config'
|
import ApiConfig from '@/components/misc/api-config'
|
||||||
|
import Message from '@/components/misc/message'
|
||||||
import NoAuthWrapper from '@/components/misc/no-auth-wrapper'
|
import NoAuthWrapper from '@/components/misc/no-auth-wrapper'
|
||||||
import {mapState} from 'vuex'
|
import {mapState} from 'vuex'
|
||||||
import {ERROR_NO_API_URL} from '@/helpers/checkAndSetApiUrl'
|
import {ERROR_NO_API_URL} from '@/helpers/checkAndSetApiUrl'
|
||||||
|
@ -50,6 +51,7 @@ import {ERROR_NO_API_URL} from '@/helpers/checkAndSetApiUrl'
|
||||||
export default {
|
export default {
|
||||||
name: 'ready',
|
name: 'ready',
|
||||||
components: {
|
components: {
|
||||||
|
Message,
|
||||||
Logo,
|
Logo,
|
||||||
NoAuthWrapper,
|
NoAuthWrapper,
|
||||||
ApiConfig,
|
ApiConfig,
|
||||||
|
|
|
@ -193,10 +193,6 @@ export default {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-body {
|
|
||||||
padding: .5rem .75rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,13 @@
|
||||||
<div class="field is-grouped">
|
<div class="field is-grouped">
|
||||||
<p class="control has-icons-left is-expanded">
|
<p class="control has-icons-left is-expanded">
|
||||||
<textarea
|
<textarea
|
||||||
:disabled="taskService.loading || null"
|
:disabled="taskService.loading || undefined"
|
||||||
class="input"
|
class="add-task-textarea input"
|
||||||
:placeholder="$t('list.list.addPlaceholder')"
|
:placeholder="$t('list.list.addPlaceholder')"
|
||||||
cols="1"
|
rows="1"
|
||||||
v-focus
|
v-focus
|
||||||
v-model="newTaskTitle"
|
v-model="newTaskTitle"
|
||||||
ref="newTaskInput"
|
ref="newTaskInput"
|
||||||
:style="{
|
|
||||||
'minHeight': `${initialTextAreaHeight}px`,
|
|
||||||
'height': `calc(${textAreaHeight}px - 2px + 1rem)`
|
|
||||||
}"
|
|
||||||
@keyup="errorMessage = ''"
|
@keyup="errorMessage = ''"
|
||||||
@keydown.enter="handleEnter"
|
@keydown.enter="handleEnter"
|
||||||
/>
|
/>
|
||||||
|
@ -23,7 +19,8 @@
|
||||||
</p>
|
</p>
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<x-button
|
<x-button
|
||||||
:disabled="newTaskTitle === '' || taskService.loading || null"
|
class="add-task-button"
|
||||||
|
:disabled="newTaskTitle === '' || taskService.loading || undefined"
|
||||||
@click="addTask()"
|
@click="addTask()"
|
||||||
icon="plus"
|
icon="plus"
|
||||||
:loading="taskService.loading"
|
:loading="taskService.loading"
|
||||||
|
@ -35,121 +32,172 @@
|
||||||
<p class="help is-danger" v-if="errorMessage !== ''">
|
<p class="help is-danger" v-if="errorMessage !== ''">
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</p>
|
</p>
|
||||||
<quick-add-magic v-if="errorMessage === ''"/>
|
<quick-add-magic v-else />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import {ref, watch, unref, shallowReactive} from 'vue'
|
||||||
|
import {useI18n} from 'vue-i18n'
|
||||||
|
import {useStore} from 'vuex'
|
||||||
|
import { tryOnMounted, debouncedWatch, useWindowSize, MaybeRef } from '@vueuse/core'
|
||||||
|
|
||||||
import TaskService from '../../services/task'
|
import TaskService from '../../services/task'
|
||||||
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
|
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
|
||||||
|
|
||||||
const INPUT_BORDER_PX = 2
|
function cleanupTitle(title: string) {
|
||||||
const LINE_HEIGHT = 1.5 // using getComputedStyles().lineHeight returns an (wrong) absolute pixel value, we need the factor to do calculations with it.
|
|
||||||
|
|
||||||
const cleanupTitle = title => {
|
|
||||||
return title.replace(/^((\* |\+ |- )(\[ \] )?)/g, '')
|
return title.replace(/^((\* |\+ |- )(\[ \] )?)/g, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
function useAutoHeightTextarea(value: MaybeRef<string>) {
|
||||||
name: 'add-task',
|
const textarea = ref<HTMLInputElement>()
|
||||||
emits: ['taskAdded'],
|
const minHeight = ref(0)
|
||||||
data() {
|
|
||||||
return {
|
// adapted from https://github.com/LeaVerou/stretchy/blob/47f5f065c733029acccb755cae793009645809e2/src/stretchy.js#L34
|
||||||
newTaskTitle: '',
|
function resize(textareaEl: HTMLInputElement|undefined) {
|
||||||
taskService: new TaskService(),
|
if (!textareaEl) return
|
||||||
errorMessage: '',
|
|
||||||
textAreaHeight: null,
|
let empty
|
||||||
initialTextAreaHeight: null,
|
|
||||||
|
// the value here is the the attribute value
|
||||||
|
if (!textareaEl.value && textareaEl.placeholder) {
|
||||||
|
empty = true
|
||||||
|
textareaEl.value = textareaEl.placeholder
|
||||||
}
|
}
|
||||||
},
|
|
||||||
components: {
|
const cs = getComputedStyle(textareaEl)
|
||||||
QuickAddMagic,
|
|
||||||
},
|
textareaEl.style.minHeight = ''
|
||||||
props: {
|
textareaEl.style.height = '0'
|
||||||
defaultPosition: {
|
const offset = textareaEl.offsetHeight - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom)
|
||||||
type: Number,
|
const height = textareaEl.scrollHeight + offset + 'px'
|
||||||
required: false,
|
|
||||||
|
textareaEl.style.height = height
|
||||||
|
|
||||||
|
// calculate min-height for the first time
|
||||||
|
if (!minHeight.value) {
|
||||||
|
minHeight.value = parseFloat(height)
|
||||||
|
}
|
||||||
|
|
||||||
|
textareaEl.style.minHeight = minHeight.value.toString()
|
||||||
|
|
||||||
|
|
||||||
|
if (empty) {
|
||||||
|
textareaEl.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
tryOnMounted(() => {
|
||||||
|
if (textarea.value) {
|
||||||
|
// we don't want scrollbars
|
||||||
|
textarea.value.style.overflowY = 'hidden'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { width: windowWidth } = useWindowSize()
|
||||||
|
|
||||||
|
debouncedWatch(
|
||||||
|
windowWidth,
|
||||||
|
() => resize(textarea.value),
|
||||||
|
{ debounce: 200 },
|
||||||
|
)
|
||||||
|
|
||||||
|
// It is not possible to get notified of a change of the value attribute of a textarea without workarounds (setTimeout)
|
||||||
|
// So instead we watch the value that we bound to it.
|
||||||
|
watch(
|
||||||
|
() => [textarea.value, unref(value)],
|
||||||
|
() => resize(textarea.value),
|
||||||
|
{
|
||||||
|
immediate: true, // calculate initial size
|
||||||
|
flush: 'post', // resize after value change is rendered to DOM
|
||||||
},
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return textarea
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits(['taskAdded'])
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
defaultPosition: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
watch: {
|
})
|
||||||
newTaskTitle(newVal) {
|
|
||||||
// Calculating the textarea height based on lines of input in it.
|
|
||||||
// That is more reliable when removing a line from the input.
|
|
||||||
const numberOfLines = newVal.split(/\r\n|\r|\n/).length
|
|
||||||
const fontSize = parseFloat(window.getComputedStyle(this.$refs.newTaskInput, null).getPropertyValue('font-size'))
|
|
||||||
|
|
||||||
this.textAreaHeight = numberOfLines * fontSize * LINE_HEIGHT + INPUT_BORDER_PX
|
const taskService = shallowReactive(new TaskService())
|
||||||
},
|
const errorMessage = ref('')
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.initialTextAreaHeight = this.$refs.newTaskInput.scrollHeight + INPUT_BORDER_PX
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async addTask() {
|
|
||||||
if (this.newTaskTitle === '') {
|
|
||||||
this.errorMessage = this.$t('list.create.addTitleRequired')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.errorMessage = ''
|
|
||||||
|
|
||||||
if (this.taskService.loading) {
|
const newTaskTitle = ref('')
|
||||||
return
|
const newTaskInput = useAutoHeightTextarea(newTaskTitle)
|
||||||
}
|
|
||||||
|
|
||||||
const newTasks = this.newTaskTitle.split(/[\r\n]+/).map(async t => {
|
const { t } = useI18n()
|
||||||
const title = cleanupTitle(t)
|
const store = useStore()
|
||||||
if (title === '') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const task = await this.$store.dispatch('tasks/createNewTask', {
|
async function addTask() {
|
||||||
title,
|
if (newTaskTitle.value === '') {
|
||||||
listId: this.$store.state.auth.settings.defaultListId,
|
errorMessage.value = t('list.create.addTitleRequired')
|
||||||
position: this.defaultPosition,
|
return
|
||||||
})
|
}
|
||||||
this.$emit('taskAdded', task)
|
errorMessage.value = ''
|
||||||
return task
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
if (taskService.loading) {
|
||||||
await Promise.all(newTasks)
|
return
|
||||||
this.newTaskTitle = ''
|
}
|
||||||
} catch (e) {
|
|
||||||
if (e.message === 'NO_LIST') {
|
|
||||||
this.errorMessage = this.$t('list.create.addListRequired')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleEnter(e) {
|
|
||||||
// when pressing shift + enter we want to continue as we normally would. Otherwise, we want to create
|
|
||||||
// the new task(s). The vue event modifier don't allow this, hence this method.
|
|
||||||
if (e.shiftKey) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault()
|
const taskTitleBackup = newTaskTitle.value
|
||||||
this.addTask()
|
const newTasks = newTaskTitle.value.split(/[\r\n]+/).map(async uncleanedTitle => {
|
||||||
},
|
const title = cleanupTitle(uncleanedTitle)
|
||||||
},
|
if (title === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const task = await store.dispatch('tasks/createNewTask', {
|
||||||
|
title,
|
||||||
|
listId: store.state.auth.settings.defaultListId,
|
||||||
|
position: props.defaultPosition,
|
||||||
|
})
|
||||||
|
emit('taskAdded', task)
|
||||||
|
return task
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
newTaskTitle.value = ''
|
||||||
|
await Promise.all(newTasks)
|
||||||
|
} catch (e: any) {
|
||||||
|
newTaskTitle.value = taskTitleBackup
|
||||||
|
if (e?.message === 'NO_LIST') {
|
||||||
|
errorMessage.value = t('list.create.addListRequired')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEnter(e: KeyboardEvent) {
|
||||||
|
// when pressing shift + enter we want to continue as we normally would. Otherwise, we want to create
|
||||||
|
// the new task(s). The vue event modifier don't allow this, hence this method.
|
||||||
|
if (e.shiftKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
addTask()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.task-add {
|
.task-add {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
||||||
.button {
|
|
||||||
height: 2.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input, .textarea {
|
.add-task-button {
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
.add-task-textarea {
|
||||||
transition: border-color $transition;
|
transition: border-color $transition;
|
||||||
}
|
resize: none;
|
||||||
|
|
||||||
.input {
|
|
||||||
resize: vertical;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,13 +2,6 @@
|
||||||
<div class="gantt-chart">
|
<div class="gantt-chart">
|
||||||
<div class="filter-container">
|
<div class="filter-container">
|
||||||
<div class="items">
|
<div class="items">
|
||||||
<x-button
|
|
||||||
@click.prevent.stop="showTaskFilter = !showTaskFilter"
|
|
||||||
type="secondary"
|
|
||||||
icon="filter"
|
|
||||||
>
|
|
||||||
{{ $t('filters.title') }}
|
|
||||||
</x-button>
|
|
||||||
<filter-popup
|
<filter-popup
|
||||||
:visible="showTaskFilter"
|
:visible="showTaskFilter"
|
||||||
v-model="params"
|
v-model="params"
|
||||||
|
@ -237,7 +230,6 @@ export default {
|
||||||
newTaskFieldActive: false,
|
newTaskFieldActive: false,
|
||||||
priorities: priorities,
|
priorities: priorities,
|
||||||
taskCollectionService: new TaskCollectionService(),
|
taskCollectionService: new TaskCollectionService(),
|
||||||
showTaskFilter: false,
|
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
sort_by: ['done', 'id'],
|
sort_by: ['done', 'id'],
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import {computed, watch, readonly} from 'vue'
|
import {computed, watch, readonly} from 'vue'
|
||||||
import {useStorage, createSharedComposable, ColorSchemes, usePreferredColorScheme, tryOnMounted} from '@vueuse/core'
|
import {useStorage, createSharedComposable, ColorSchema, usePreferredColorScheme, tryOnMounted} from '@vueuse/core'
|
||||||
|
|
||||||
const STORAGE_KEY = 'color-scheme'
|
const STORAGE_KEY = 'color-scheme'
|
||||||
|
|
||||||
const DEFAULT_COLOR_SCHEME_SETTING: ColorSchemes = 'light'
|
const DEFAULT_COLOR_SCHEME_SETTING: ColorSchema = 'light'
|
||||||
|
|
||||||
const CLASS_DARK = 'dark'
|
const CLASS_DARK = 'dark'
|
||||||
const CLASS_LIGHT = 'light'
|
const CLASS_LIGHT = 'light'
|
||||||
|
@ -16,7 +16,7 @@ const CLASS_LIGHT = 'light'
|
||||||
// - value is synced via `createSharedComposable`
|
// - value is synced via `createSharedComposable`
|
||||||
// https://github.com/vueuse/vueuse/blob/main/packages/core/useDark/index.ts
|
// https://github.com/vueuse/vueuse/blob/main/packages/core/useDark/index.ts
|
||||||
export const useColorScheme = createSharedComposable(() => {
|
export const useColorScheme = createSharedComposable(() => {
|
||||||
const store = useStorage<ColorSchemes>(STORAGE_KEY, DEFAULT_COLOR_SCHEME_SETTING)
|
const store = useStorage<ColorSchema>(STORAGE_KEY, DEFAULT_COLOR_SCHEME_SETTING)
|
||||||
|
|
||||||
const preferredColorScheme = usePreferredColorScheme()
|
const preferredColorScheme = usePreferredColorScheme()
|
||||||
|
|
||||||
|
|
|
@ -901,7 +901,7 @@
|
||||||
"5010": "Tento tým nemá k tomuto prostoru přístup.",
|
"5010": "Tento tým nemá k tomuto prostoru přístup.",
|
||||||
"5011": "Tento uživatel již má přístup k tomuto prostoru.",
|
"5011": "Tento uživatel již má přístup k tomuto prostoru.",
|
||||||
"5012": "Prostor je archivován, a proto je přístupný pouze pro čtení.",
|
"5012": "Prostor je archivován, a proto je přístupný pouze pro čtení.",
|
||||||
"6001": "Název týmu nemůže být prázdný.",
|
"6001": "The team name cannot be empty.",
|
||||||
"6002": "Tým neexistuje.",
|
"6002": "Tým neexistuje.",
|
||||||
"6004": "Tým již má přístup k tomuto prostoru nebo seznamu.",
|
"6004": "Tým již má přístup k tomuto prostoru nebo seznamu.",
|
||||||
"6005": "Uživatel je již členem tohoto týmu.",
|
"6005": "Uživatel je již členem tohoto týmu.",
|
||||||
|
|
|
@ -114,12 +114,12 @@
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
},
|
},
|
||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Color Scheme",
|
"title": "Farbschema",
|
||||||
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
"setSuccess": "Änderung des Farbschemas auf {colorScheme} gespeichert",
|
||||||
"colorScheme": {
|
"colorScheme": {
|
||||||
"light": "Light",
|
"light": "Hell",
|
||||||
"system": "System",
|
"system": "System",
|
||||||
"dark": "Dark"
|
"dark": "Dunkel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -556,7 +556,7 @@
|
||||||
"text2": "Dies wird auch alle Anhänge, Erinnerungen und Verknüpfungen, die zu dieser Aufgabe gehören löschen und kann nicht rückgängig gemacht werden!"
|
"text2": "Dies wird auch alle Anhänge, Erinnerungen und Verknüpfungen, die zu dieser Aufgabe gehören löschen und kann nicht rückgängig gemacht werden!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Assign to a user",
|
"assign": "Benutzer:in zuweisen",
|
||||||
"label": "Label hinzufügen",
|
"label": "Label hinzufügen",
|
||||||
"priority": "Priorität setzen",
|
"priority": "Priorität setzen",
|
||||||
"dueDate": "Fälligkeitsdatum setzen",
|
"dueDate": "Fälligkeitsdatum setzen",
|
||||||
|
@ -775,7 +775,7 @@
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Aufgabenseite",
|
"title": "Aufgabenseite",
|
||||||
"done": "Eine Aufgabe als erledigt markieren",
|
"done": "Eine Aufgabe als erledigt markieren",
|
||||||
"assign": "Assign to a user",
|
"assign": "Benutzer:in zuweisen",
|
||||||
"labels": "Dieser Aufgabe ein Label hinzufügen",
|
"labels": "Dieser Aufgabe ein Label hinzufügen",
|
||||||
"dueDate": "Ändere das Fälligkeitsdatum dieser Aufgabe",
|
"dueDate": "Ändere das Fälligkeitsdatum dieser Aufgabe",
|
||||||
"attachment": "Einen Anhang dieser Aufgabe hinzufügen",
|
"attachment": "Einen Anhang dieser Aufgabe hinzufügen",
|
||||||
|
|
|
@ -114,12 +114,12 @@
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
},
|
},
|
||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Color Scheme",
|
"title": "Farbschema",
|
||||||
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
"setSuccess": "Änderung des Farbschemas auf {colorScheme} gespeichert",
|
||||||
"colorScheme": {
|
"colorScheme": {
|
||||||
"light": "Light",
|
"light": "Hell",
|
||||||
"system": "System",
|
"system": "System",
|
||||||
"dark": "Dark"
|
"dark": "Dunkel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -556,7 +556,7 @@
|
||||||
"text2": "Das wird au alli Ahhäng, Errinnerige und Beziehige wo mit dere Uufgab verchnüpft sind chüble und cha nid rückgängig gmacht werde!"
|
"text2": "Das wird au alli Ahhäng, Errinnerige und Beziehige wo mit dere Uufgab verchnüpft sind chüble und cha nid rückgängig gmacht werde!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Assign to a user",
|
"assign": "Benutzer:in zuweisen",
|
||||||
"label": "Label hinzuefüege",
|
"label": "Label hinzuefüege",
|
||||||
"priority": "Priorität setzä",
|
"priority": "Priorität setzä",
|
||||||
"dueDate": "Fälligkeitsdatum setze",
|
"dueDate": "Fälligkeitsdatum setze",
|
||||||
|
@ -775,7 +775,7 @@
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Uufgabesiite",
|
"title": "Uufgabesiite",
|
||||||
"done": "Uufgab als erledigt markiere",
|
"done": "Uufgab als erledigt markiere",
|
||||||
"assign": "Assign to a user",
|
"assign": "Benutzer:in zuweisen",
|
||||||
"labels": "Labels ennere Uufgab hinzuefüege",
|
"labels": "Labels ennere Uufgab hinzuefüege",
|
||||||
"dueDate": "S'Fälligkeitsdatum für die Uufgab ändere",
|
"dueDate": "S'Fälligkeitsdatum für die Uufgab ändere",
|
||||||
"attachment": "En Aahang dere Uufgab hinzuefüege",
|
"attachment": "En Aahang dere Uufgab hinzuefüege",
|
||||||
|
@ -901,7 +901,7 @@
|
||||||
"5010": "Da Team hett kei zuegriff uf de Namensruum.",
|
"5010": "Da Team hett kei zuegriff uf de Namensruum.",
|
||||||
"5011": "De Benutzer hett bereits zuegriff uf de Namensruum.",
|
"5011": "De Benutzer hett bereits zuegriff uf de Namensruum.",
|
||||||
"5012": "De Namensruum isch momentan schriibgschützt weil er archiviert isch.",
|
"5012": "De Namensruum isch momentan schriibgschützt weil er archiviert isch.",
|
||||||
"6001": "Der TeamName kann nicht leer sein.",
|
"6001": "Der Teamname kann nicht leer sein.",
|
||||||
"6002": "Da Team giz nid.",
|
"6002": "Da Team giz nid.",
|
||||||
"6004": "Da Team het scho Zuegang zu dem Namensruum oder Liste.",
|
"6004": "Da Team het scho Zuegang zu dem Namensruum oder Liste.",
|
||||||
"6005": "De Benutzer isch scho bi dem Team.",
|
"6005": "De Benutzer isch scho bi dem Team.",
|
||||||
|
|
|
@ -901,7 +901,7 @@
|
||||||
"5010": "This team does not have access to that namespace.",
|
"5010": "This team does not have access to that namespace.",
|
||||||
"5011": "This user has already access to that namespace.",
|
"5011": "This user has already access to that namespace.",
|
||||||
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
||||||
"6001": "The team name cannot be emtpy.",
|
"6001": "The team name cannot be empty.",
|
||||||
"6002": "The team does not exist.",
|
"6002": "The team does not exist.",
|
||||||
"6004": "The team already has access to that namespace or list.",
|
"6004": "The team already has access to that namespace or list.",
|
||||||
"6005": "The user is already a member of that team.",
|
"6005": "The user is already a member of that team.",
|
||||||
|
|
|
@ -901,7 +901,7 @@
|
||||||
"5010": "This team does not have access to that namespace.",
|
"5010": "This team does not have access to that namespace.",
|
||||||
"5011": "This user has already access to that namespace.",
|
"5011": "This user has already access to that namespace.",
|
||||||
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
||||||
"6001": "The team name cannot be emtpy.",
|
"6001": "The team name cannot be empty.",
|
||||||
"6002": "The team does not exist.",
|
"6002": "The team does not exist.",
|
||||||
"6004": "The team already has access to that namespace or list.",
|
"6004": "The team already has access to that namespace or list.",
|
||||||
"6005": "The user is already a member of that team.",
|
"6005": "The user is already a member of that team.",
|
||||||
|
|
|
@ -901,7 +901,7 @@
|
||||||
"5010": "Cette équipe n’a pas accès à cet espace de noms.",
|
"5010": "Cette équipe n’a pas accès à cet espace de noms.",
|
||||||
"5011": "Cet·e utilisateur·rice a déjà accès à cet espace de noms.",
|
"5011": "Cet·e utilisateur·rice a déjà accès à cet espace de noms.",
|
||||||
"5012": "L’espace de noms est archivé et ne peut donc être consulté qu’en lecture seule.",
|
"5012": "L’espace de noms est archivé et ne peut donc être consulté qu’en lecture seule.",
|
||||||
"6001": "Le nom de l’équipe ne peut pas être vide.",
|
"6001": "The team name cannot be empty.",
|
||||||
"6002": "L’équipe n’existe pas.",
|
"6002": "L’équipe n’existe pas.",
|
||||||
"6004": "L’équipe a déjà accès à cet espace de noms ou à cette liste.",
|
"6004": "L’équipe a déjà accès à cet espace de noms ou à cette liste.",
|
||||||
"6005": "L’utilisateur·rice est déjà membre de cette équipe.",
|
"6005": "L’utilisateur·rice est déjà membre de cette équipe.",
|
||||||
|
|
|
@ -901,7 +901,7 @@
|
||||||
"5010": "This team does not have access to that namespace.",
|
"5010": "This team does not have access to that namespace.",
|
||||||
"5011": "This user has already access to that namespace.",
|
"5011": "This user has already access to that namespace.",
|
||||||
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
||||||
"6001": "The team name cannot be emtpy.",
|
"6001": "The team name cannot be empty.",
|
||||||
"6002": "The team does not exist.",
|
"6002": "The team does not exist.",
|
||||||
"6004": "The team already has access to that namespace or list.",
|
"6004": "The team already has access to that namespace or list.",
|
||||||
"6005": "The user is already a member of that team.",
|
"6005": "The user is already a member of that team.",
|
||||||
|
|
|
@ -901,7 +901,7 @@
|
||||||
"5010": "This team does not have access to that namespace.",
|
"5010": "This team does not have access to that namespace.",
|
||||||
"5011": "This user has already access to that namespace.",
|
"5011": "This user has already access to that namespace.",
|
||||||
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
||||||
"6001": "The team name cannot be emtpy.",
|
"6001": "The team name cannot be empty.",
|
||||||
"6002": "The team does not exist.",
|
"6002": "The team does not exist.",
|
||||||
"6004": "The team already has access to that namespace or list.",
|
"6004": "The team already has access to that namespace or list.",
|
||||||
"6005": "The user is already a member of that team.",
|
"6005": "The user is already a member of that team.",
|
||||||
|
|
|
@ -901,7 +901,7 @@
|
||||||
"5010": "This team does not have access to that namespace.",
|
"5010": "This team does not have access to that namespace.",
|
||||||
"5011": "This user has already access to that namespace.",
|
"5011": "This user has already access to that namespace.",
|
||||||
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
||||||
"6001": "The team name cannot be emtpy.",
|
"6001": "The team name cannot be empty.",
|
||||||
"6002": "The team does not exist.",
|
"6002": "The team does not exist.",
|
||||||
"6004": "The team already has access to that namespace or list.",
|
"6004": "The team already has access to that namespace or list.",
|
||||||
"6005": "The user is already a member of that team.",
|
"6005": "The user is already a member of that team.",
|
||||||
|
|
|
@ -901,7 +901,7 @@
|
||||||
"5010": "This team does not have access to that namespace.",
|
"5010": "This team does not have access to that namespace.",
|
||||||
"5011": "This user has already access to that namespace.",
|
"5011": "This user has already access to that namespace.",
|
||||||
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
||||||
"6001": "The team name cannot be emtpy.",
|
"6001": "The team name cannot be empty.",
|
||||||
"6002": "The team does not exist.",
|
"6002": "The team does not exist.",
|
||||||
"6004": "The team already has access to that namespace or list.",
|
"6004": "The team already has access to that namespace or list.",
|
||||||
"6005": "The user is already a member of that team.",
|
"6005": "The user is already a member of that team.",
|
||||||
|
|
|
@ -901,7 +901,7 @@
|
||||||
"5010": "This team does not have access to that namespace.",
|
"5010": "This team does not have access to that namespace.",
|
||||||
"5011": "This user has already access to that namespace.",
|
"5011": "This user has already access to that namespace.",
|
||||||
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
||||||
"6001": "The team name cannot be emtpy.",
|
"6001": "The team name cannot be empty.",
|
||||||
"6002": "The team does not exist.",
|
"6002": "The team does not exist.",
|
||||||
"6004": "The team already has access to that namespace or list.",
|
"6004": "The team already has access to that namespace or list.",
|
||||||
"6005": "The user is already a member of that team.",
|
"6005": "The user is already a member of that team.",
|
||||||
|
|
|
@ -901,7 +901,7 @@
|
||||||
"5010": "У этой команды нет доступа к этому пространству имён.",
|
"5010": "У этой команды нет доступа к этому пространству имён.",
|
||||||
"5011": "Этот пользователь уже имеет доступ к этому пространству имён.",
|
"5011": "Этот пользователь уже имеет доступ к этому пространству имён.",
|
||||||
"5012": "Это пространство имён архивировано и поэтому доступно только для чтения.",
|
"5012": "Это пространство имён архивировано и поэтому доступно только для чтения.",
|
||||||
"6001": "Имя команды не может быть пустым.",
|
"6001": "The team name cannot be empty.",
|
||||||
"6002": "Команда не существует.",
|
"6002": "Команда не существует.",
|
||||||
"6004": "Эта команда уже имеет доступ к этому пространству имён или списку.",
|
"6004": "Эта команда уже имеет доступ к этому пространству имён или списку.",
|
||||||
"6005": "Пользователь уже является участником этой команды.",
|
"6005": "Пользователь уже является участником этой команды.",
|
||||||
|
|
|
@ -0,0 +1,933 @@
|
||||||
|
{
|
||||||
|
"home": {
|
||||||
|
"welcomeNight": "Good Night {username}",
|
||||||
|
"welcomeMorning": "Good Morning {username}",
|
||||||
|
"welcomeDay": "Hi {username}",
|
||||||
|
"welcomeEvening": "Good Evening {username}",
|
||||||
|
"lastViewed": "Last viewed",
|
||||||
|
"list": {
|
||||||
|
"newText": "You can create a new list for your new tasks:",
|
||||||
|
"new": "Create a new list",
|
||||||
|
"importText": "Or import your lists and tasks from other services into Vikunja:",
|
||||||
|
"import": "Import your data into Vikunja"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"title": "Not found",
|
||||||
|
"text": "The page you requested does not exist."
|
||||||
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Vikunja is loading…",
|
||||||
|
"errorOccured": "An error occured:",
|
||||||
|
"checkApiUrl": "Please check if the api url is correct.",
|
||||||
|
"noApiUrlConfigured": "No API url was configured. Please set one below:"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "You are offline.",
|
||||||
|
"text": "Please check your network connection and try again."
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"auth": {
|
||||||
|
"username": "Username",
|
||||||
|
"usernameEmail": "Username Or Email Address",
|
||||||
|
"usernamePlaceholder": "e.g. frederick",
|
||||||
|
"email": "E-mail address",
|
||||||
|
"emailPlaceholder": "e.g. frederic{'@'}vikunja.io",
|
||||||
|
"password": "Password",
|
||||||
|
"passwordRepeat": "Retype your password",
|
||||||
|
"passwordPlaceholder": "e.g. •••••••••••",
|
||||||
|
"resetPassword": "Reset your password",
|
||||||
|
"resetPasswordAction": "Send me a password reset link",
|
||||||
|
"resetPasswordSuccess": "Check your inbox! You should have an e-mail with instructions on how to reset your password.",
|
||||||
|
"passwordsDontMatch": "Passwords don't match",
|
||||||
|
"confirmEmailSuccess": "You successfully confirmed your email! You can log in now.",
|
||||||
|
"totpTitle": "Two Factor Authentication Code",
|
||||||
|
"totpPlaceholder": "e.g. 123456",
|
||||||
|
"login": "Login",
|
||||||
|
"register": "Register",
|
||||||
|
"loginWith": "Log in with {provider}",
|
||||||
|
"authenticating": "Authenticating…",
|
||||||
|
"openIdStateError": "State does not match, refusing to continue!",
|
||||||
|
"openIdGeneralError": "An error occured while authenticating against the third party.",
|
||||||
|
"logout": "Logout"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "Settings",
|
||||||
|
"newPasswordTitle": "Update Your Password",
|
||||||
|
"newPassword": "New Password",
|
||||||
|
"newPasswordConfirm": "New Password Confirmation",
|
||||||
|
"currentPassword": "Current Password",
|
||||||
|
"currentPasswordPlaceholder": "Your current password",
|
||||||
|
"passwordsDontMatch": "The new password and its confirmation don't match.",
|
||||||
|
"passwordUpdateSuccess": "The password was successfully updated.",
|
||||||
|
"updateEmailTitle": "Update Your E-Mail Address",
|
||||||
|
"updateEmailNew": "New Email Address",
|
||||||
|
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
|
||||||
|
"general": {
|
||||||
|
"title": "General Settings",
|
||||||
|
"name": "Name",
|
||||||
|
"newName": "The new Name",
|
||||||
|
"savedSuccess": "The settings were successfully updated.",
|
||||||
|
"emailReminders": "Send me reminders for tasks via Email",
|
||||||
|
"overdueReminders": "Send me reminders for overdue undone tasks via email each morning",
|
||||||
|
"discoverableByName": "Let other users find me when they search for my name",
|
||||||
|
"discoverableByEmail": "Let other users find me when they search for my full email",
|
||||||
|
"playSoundWhenDone": "Play a sound when marking tasks as done",
|
||||||
|
"weekStart": "Week starts on",
|
||||||
|
"weekStartSunday": "Sunday",
|
||||||
|
"weekStartMonday": "Monday",
|
||||||
|
"language": "Language",
|
||||||
|
"defaultList": "Default List"
|
||||||
|
},
|
||||||
|
"totp": {
|
||||||
|
"title": "Two Factor Authentication",
|
||||||
|
"enroll": "Enroll",
|
||||||
|
"finishSetupPart1": "To finish your setup, use this secret in your totp app (Google Authenticator or similar):",
|
||||||
|
"finishSetupPart2": "After that, enter a code from your app below.",
|
||||||
|
"scanQR": "Alternatively you can scan this QR code:",
|
||||||
|
"passcode": "Passcode",
|
||||||
|
"passcodePlaceholder": "A code generated by your totp application",
|
||||||
|
"setupSuccess": "You've sucessfully set up two factor authentication!",
|
||||||
|
"enterPassword": "Please Enter Your Password",
|
||||||
|
"disable": "Disable two factor authentication",
|
||||||
|
"confirmSuccess": "You've successfully confirmed your totp setup and can use it from now on!",
|
||||||
|
"disableSuccess": "Two factor authentication was sucessfully disabled."
|
||||||
|
},
|
||||||
|
"caldav": {
|
||||||
|
"title": "Caldav",
|
||||||
|
"howTo": "You can connect Vikunja to caldav clients to view and manage all tasks from different clients. Enter this url into your client:",
|
||||||
|
"more": "More information about caldav in Vikunja"
|
||||||
|
},
|
||||||
|
"avatar": {
|
||||||
|
"title": "Avatar",
|
||||||
|
"initials": "Initials",
|
||||||
|
"gravatar": "Gravatar",
|
||||||
|
"upload": "Upload",
|
||||||
|
"uploadAvatar": "Upload Avatar",
|
||||||
|
"statusUpdateSuccess": "Avatar status was updated successfully!",
|
||||||
|
"setSuccess": "The avatar has been set successfully!"
|
||||||
|
},
|
||||||
|
"quickAddMagic": {
|
||||||
|
"title": "Quick Add Magic Mode",
|
||||||
|
"disabled": "Disabled",
|
||||||
|
"todoist": "Todoist",
|
||||||
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deletion": {
|
||||||
|
"title": "Delete your Vikunja Account",
|
||||||
|
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, lists, tasks and everything associated with it.",
|
||||||
|
"text2": "To proceed, please enter your password. You will receive an email with further instructions.",
|
||||||
|
"confirm": "Delete my account",
|
||||||
|
"requestSuccess": "The request was successful. You'll receive an email with further instructions.",
|
||||||
|
"passwordRequired": "Please enter your password.",
|
||||||
|
"confirmSuccess": "You've successfully confirmed the deletion of your account. We will delete your account in three days.",
|
||||||
|
"scheduled": "We will delete your Vikunja account at {date} ({dateSince}).",
|
||||||
|
"scheduledCancel": "To cancel the deletion of your account, click here.",
|
||||||
|
"scheduledCancelText": "To cancel the deletion of your account, please enter your password below:",
|
||||||
|
"scheduledCancelConfirm": "Cancel the deletion of my account",
|
||||||
|
"scheduledCancelSuccess": "We will not delete your account."
|
||||||
|
},
|
||||||
|
"export": {
|
||||||
|
"title": "Export your Vikunja data",
|
||||||
|
"description": "You can request a copy of all your Vikunja data. This include Namespaces, Lists, Tasks and everything associated to them. You can import this data in any Vikunja instance through the migration function.",
|
||||||
|
"descriptionPasswordRequired": "Please enter your password to proceed:",
|
||||||
|
"request": "Request a copy of my Vikunja Data",
|
||||||
|
"success": "You've successfully requested your Vikunja Data! We will send you an email once it's ready to download.",
|
||||||
|
"downloadTitle": "Download your exported Vikunja data"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"archived": "This list is archived. It is not possible to create new or edit tasks for it.",
|
||||||
|
"title": "List Title",
|
||||||
|
"color": "Color",
|
||||||
|
"lists": "Lists",
|
||||||
|
"search": "Type to search for a list…",
|
||||||
|
"searchSelect": "Click or press enter to select this list",
|
||||||
|
"shared": "Shared Lists",
|
||||||
|
"create": {
|
||||||
|
"header": "Create a new list",
|
||||||
|
"titlePlaceholder": "The list's title goes here…",
|
||||||
|
"addTitleRequired": "Please specify a title.",
|
||||||
|
"createdSuccess": "The list was successfully created.",
|
||||||
|
"addListRequired": "Please specify a list or set a default list in the settings."
|
||||||
|
},
|
||||||
|
"archive": {
|
||||||
|
"title": "Archive \"{list}\"",
|
||||||
|
"archive": "Archive this list",
|
||||||
|
"unarchive": "Un-Archive this list",
|
||||||
|
"unarchiveText": "You will be able to create new tasks or edit it.",
|
||||||
|
"archiveText": "You won't be able to edit this list or create new tasks until you un-archive it.",
|
||||||
|
"success": "The list was successfully archived."
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"title": "Set list background",
|
||||||
|
"remove": "Remove Background",
|
||||||
|
"upload": "Choose a background from your pc",
|
||||||
|
"searchPlaceholder": "Search for a background…",
|
||||||
|
"poweredByUnsplash": "Powered by Unsplash",
|
||||||
|
"loadMore": "Load more photos",
|
||||||
|
"success": "The background has been set successfully!",
|
||||||
|
"removeSuccess": "The background has been removed successfully!"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"title": "Delete \"{list}\"",
|
||||||
|
"header": "Delete this list",
|
||||||
|
"text1": "Are you sure you want to delete this list and all of its contents?",
|
||||||
|
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||||
|
"success": "The list was successfully deleted."
|
||||||
|
},
|
||||||
|
"duplicate": {
|
||||||
|
"title": "Duplicate this list",
|
||||||
|
"label": "Duplicate",
|
||||||
|
"text": "Select a namespace which should hold the duplicated list:",
|
||||||
|
"success": "The list was successfully duplicated."
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"header": "Edit This List",
|
||||||
|
"title": "Edit \"{list}\"",
|
||||||
|
"titlePlaceholder": "The list title goes here…",
|
||||||
|
"identifierTooltip": "The list identifier can be used to uniquely identify a task across lists. You can set it to empty to disable it.",
|
||||||
|
"identifier": "List Identifier",
|
||||||
|
"identifierPlaceholder": "The list identifier goes here…",
|
||||||
|
"description": "Description",
|
||||||
|
"descriptionPlaceholder": "The lists description goes here…",
|
||||||
|
"color": "Color",
|
||||||
|
"success": "The list was successfully updated."
|
||||||
|
},
|
||||||
|
"share": {
|
||||||
|
"header": "Share this list",
|
||||||
|
"title": "Share \"{list}\"",
|
||||||
|
"share": "Share",
|
||||||
|
"links": {
|
||||||
|
"title": "Share Links",
|
||||||
|
"what": "What is a share link?",
|
||||||
|
"explanation": "Share Links allow you to easily share a list with other users who don't have an account on Vikunja.",
|
||||||
|
"create": "Create a new link share",
|
||||||
|
"name": "Name (optional)",
|
||||||
|
"namePlaceholder": "e.g. Lorem Ipsum",
|
||||||
|
"nameExplanation": "All actions done by this link share will show up with the name.",
|
||||||
|
"password": "Password (optional)",
|
||||||
|
"passwordExplanation": "When authenticating, the user will be required to enter this password.",
|
||||||
|
"noName": "No name set",
|
||||||
|
"remove": "Remove a link share",
|
||||||
|
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this list with this link share. This cannot be undone!",
|
||||||
|
"createSuccess": "The link share was successfully created.",
|
||||||
|
"deleteSuccess": "The link share was successfully deleted"
|
||||||
|
},
|
||||||
|
"userTeam": {
|
||||||
|
"typeUser": "user | users",
|
||||||
|
"typeTeam": "team | teams",
|
||||||
|
"shared": "Shared with these {type}",
|
||||||
|
"you": "You",
|
||||||
|
"notShared": "Not shared with any {type} yet.",
|
||||||
|
"removeHeader": "Remove a {type} from the {sharable}",
|
||||||
|
"removeText": "Are you sure you want to remove this {sharable} from the {type}? This cannot be undone!",
|
||||||
|
"removeSuccess": "The {sharable} was successfully removed from the {type}.",
|
||||||
|
"addedSuccess": "The {type} was successfully added.",
|
||||||
|
"updatedSuccess": "The {type} was successfully added."
|
||||||
|
},
|
||||||
|
"right": {
|
||||||
|
"title": "Right",
|
||||||
|
"read": "Read only",
|
||||||
|
"readWrite": "Read & write",
|
||||||
|
"admin": "Admin"
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"link": "Link",
|
||||||
|
"name": "Name",
|
||||||
|
"sharedBy": "Shared by",
|
||||||
|
"right": "Right",
|
||||||
|
"delete": "Delete"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "List",
|
||||||
|
"add": "Add",
|
||||||
|
"addPlaceholder": "Add a new task…",
|
||||||
|
"empty": "This list is currently empty.",
|
||||||
|
"newTaskCta": "Create a new task.",
|
||||||
|
"editTask": "Edit Task"
|
||||||
|
},
|
||||||
|
"gantt": {
|
||||||
|
"title": "Gantt",
|
||||||
|
"showTasksWithoutDates": "Show tasks which don't have dates set",
|
||||||
|
"size": "Size",
|
||||||
|
"default": "Default",
|
||||||
|
"month": "Month",
|
||||||
|
"day": "Day",
|
||||||
|
"from": "From",
|
||||||
|
"to": "To",
|
||||||
|
"noDates": "This task has no dates set."
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"title": "Table",
|
||||||
|
"columns": "Columns"
|
||||||
|
},
|
||||||
|
"kanban": {
|
||||||
|
"title": "Kanban",
|
||||||
|
"limit": "Limit: {limit}",
|
||||||
|
"noLimit": "Not Set",
|
||||||
|
"doneBucket": "Done bucket",
|
||||||
|
"doneBucketHint": "All tasks moved into this bucket will automatically marked as done.",
|
||||||
|
"doneBucketHintExtended": "All tasks moved into the done bucket will be marked as done automatically. All tasks marked as done from elsewhere will be moved as well.",
|
||||||
|
"doneBucketSavedSuccess": "The done bucket has been saved successfully.",
|
||||||
|
"deleteLast": "You cannot remove the last bucket.",
|
||||||
|
"addTaskPlaceholder": "Enter the new task title…",
|
||||||
|
"addTask": "Add a task",
|
||||||
|
"addAnotherTask": "Add another task",
|
||||||
|
"addBucket": "Create a new bucket",
|
||||||
|
"addBucketPlaceholder": "Enter the new bucket title…",
|
||||||
|
"deleteHeaderBucket": "Delete the bucket",
|
||||||
|
"deleteBucketText1": "Are you sure you want to delete this bucket?",
|
||||||
|
"deleteBucketText2": "This will not delete any tasks but move them into the default bucket.",
|
||||||
|
"deleteBucketSuccess": "The bucket has been deleted successfully.",
|
||||||
|
"bucketTitleSavedSuccess": "The bucket title has been saved successfully.",
|
||||||
|
"bucketLimitSavedSuccess": "The bucket limit been saved successfully.",
|
||||||
|
"collapse": "Collapse this bucket"
|
||||||
|
},
|
||||||
|
"pseudo": {
|
||||||
|
"favorites": {
|
||||||
|
"title": "Favorites"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"namespace": {
|
||||||
|
"title": "Namespaces & Lists",
|
||||||
|
"namespace": "Namespace",
|
||||||
|
"showArchived": "Show Archived",
|
||||||
|
"noneAvailable": "You don't have any namespaces right now.",
|
||||||
|
"unarchive": "Un-Archive",
|
||||||
|
"archived": "Archived",
|
||||||
|
"noLists": "This namespace does not contain any lists.",
|
||||||
|
"createList": "Create a new list in this namespace.",
|
||||||
|
"namespaces": "Namespaces",
|
||||||
|
"search": "Type to search for a namespace…",
|
||||||
|
"create": {
|
||||||
|
"title": "Create a new namespace",
|
||||||
|
"titleRequired": "Please specify a title.",
|
||||||
|
"explanation": "A namespace is a collection of lists you can share and use to organize your lists with. In fact, every list belongs to a namepace.",
|
||||||
|
"tooltip": "What's a namespace?",
|
||||||
|
"success": "The namespace was successfully created."
|
||||||
|
},
|
||||||
|
"archive": {
|
||||||
|
"titleArchive": "Archive \"{namespace}\"",
|
||||||
|
"titleUnarchive": "Un-Archive \"{namespace}\"",
|
||||||
|
"archiveText": "You won't be able to edit this namespace or create new lists until you un-archive it. This will also archive all lists in this namespace.",
|
||||||
|
"unarchiveText": "You will be able to create new lists or edit it.",
|
||||||
|
"success": "The namespace was successfully archived.",
|
||||||
|
"description": "If a namespace is archived, you cannot create new lists or edit it."
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"title": "Delete \"{namespace}\"",
|
||||||
|
"text1": "Are you sure you want to delete this namespace and all of its contents?",
|
||||||
|
"text2": "This includes all lists and tasks and CANNOT BE UNDONE!",
|
||||||
|
"success": "The namespace was successfully deleted."
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"title": "Edit \"{namespace}\"",
|
||||||
|
"success": "The namespace was successfully updated."
|
||||||
|
},
|
||||||
|
"share": {
|
||||||
|
"title": "Share \"{namespace}\""
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"title": "Namespace Title",
|
||||||
|
"titlePlaceholder": "The namespace title goes here…",
|
||||||
|
"description": "Description",
|
||||||
|
"descriptionPlaceholder": "The namespaces description goes here…",
|
||||||
|
"color": "Color",
|
||||||
|
"archived": "Is Archived",
|
||||||
|
"isArchived": "This namespace is archived"
|
||||||
|
},
|
||||||
|
"pseudo": {
|
||||||
|
"sharedLists": {
|
||||||
|
"title": "Shared Lists"
|
||||||
|
},
|
||||||
|
"favorites": {
|
||||||
|
"title": "Favorites"
|
||||||
|
},
|
||||||
|
"savedFilters": {
|
||||||
|
"title": "Filters"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"title": "Filters",
|
||||||
|
"clear": "Clear Filters",
|
||||||
|
"attributes": {
|
||||||
|
"title": "Title",
|
||||||
|
"titlePlaceholder": "The saved filter title goes here…",
|
||||||
|
"description": "Description",
|
||||||
|
"descriptionPlaceholder": "The description goes here…",
|
||||||
|
"includeNulls": "Include Tasks which don't have a value set",
|
||||||
|
"requireAll": "Require all filters to be true for a task to show up",
|
||||||
|
"showDoneTasks": "Show Done Tasks",
|
||||||
|
"enablePriority": "Enable Filter By Priority",
|
||||||
|
"enablePercentDone": "Enable Filter By Percent Done",
|
||||||
|
"dueDateRange": "Due Date Range",
|
||||||
|
"startDateRange": "Start Date Range",
|
||||||
|
"endDateRange": "End Date Range",
|
||||||
|
"reminderRange": "Reminder Date Range"
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"title": "Create A Saved Filter",
|
||||||
|
"description": "A saved filter is a virtual list which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
|
||||||
|
"action": "Create new saved filter"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"header": "Delete this saved filter",
|
||||||
|
"text": "Are you sure you want to delete this saved filter?",
|
||||||
|
"success": "The filter was deleted successfully."
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"title": "Edit This Saved Filter",
|
||||||
|
"success": "The filter was saved successfully."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"migrate": {
|
||||||
|
"title": "Migrate from other services to Vikunja",
|
||||||
|
"titleService": "Import your data from {name} into Vikunja",
|
||||||
|
"import": "Import your data into Vikunja",
|
||||||
|
"description": "Click on the logo of one of the third-party services below to get started.",
|
||||||
|
"descriptionDo": "Vikunja will import all lists, tasks, notes, reminders and files you have access to.",
|
||||||
|
"authorize": "To authorize Vikunja to access your {name} Account, click the button below.",
|
||||||
|
"getStarted": "Get Started",
|
||||||
|
"inProgress": "Importing in progress…",
|
||||||
|
"alreadyMigrated1": "It looks like you've already imported your stuff from {name} at {date}.",
|
||||||
|
"alreadyMigrated2": "Importing again is possible, but might create duplicates. Are you sure?",
|
||||||
|
"confirm": "I am sure, please start migrating now!",
|
||||||
|
"importUpload": "To import data from {name} into Vikunja, click the button below to select a file.",
|
||||||
|
"upload": "Upload file"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"title": "Labels",
|
||||||
|
"manage": "Manage labels",
|
||||||
|
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose list you have access.",
|
||||||
|
"newCTA": "You currently do not have any labels.",
|
||||||
|
"search": "Type to search for a label…",
|
||||||
|
"create": {
|
||||||
|
"header": "New label",
|
||||||
|
"title": "Create a new label",
|
||||||
|
"titleRequired": "Please specify a title.",
|
||||||
|
"success": "The label was successfully created."
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"header": "Edit Label",
|
||||||
|
"forbidden": "You are not allowed to edit this label because you dont own it.",
|
||||||
|
"success": "The label was successfully updated."
|
||||||
|
},
|
||||||
|
"deleteSuccess": "The label was successfully deleted.",
|
||||||
|
"attributes": {
|
||||||
|
"title": "Title",
|
||||||
|
"titlePlaceholder": "The label title goes here…",
|
||||||
|
"description": "Description",
|
||||||
|
"descriptionPlaceholder": "Label description",
|
||||||
|
"color": "Color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sharing": {
|
||||||
|
"authenticating": "Authenticating…",
|
||||||
|
"passwordRequired": "This shared list requires a password. Please enter it below:",
|
||||||
|
"error": "An error occured.",
|
||||||
|
"invalidPassword": "The password is invalid."
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"overview": "Overview",
|
||||||
|
"upcoming": "Upcoming",
|
||||||
|
"settings": "Settings",
|
||||||
|
"imprint": "Imprint",
|
||||||
|
"privacy": "Privacy Policy"
|
||||||
|
},
|
||||||
|
"misc": {
|
||||||
|
"loading": "Loading…",
|
||||||
|
"save": "Save",
|
||||||
|
"delete": "Delete",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"disable": "Disable",
|
||||||
|
"copy": "Copy to clipboard",
|
||||||
|
"search": "Search",
|
||||||
|
"searchPlaceholder": "Type to search…",
|
||||||
|
"previous": "Previous",
|
||||||
|
"next": "Next",
|
||||||
|
"poweredBy": "Powered by Vikunja",
|
||||||
|
"info": "Info",
|
||||||
|
"create": "Create",
|
||||||
|
"doit": "Do it!",
|
||||||
|
"saving": "Saving…",
|
||||||
|
"saved": "Saved!",
|
||||||
|
"default": "Default",
|
||||||
|
"close": "Close",
|
||||||
|
"download": "Download",
|
||||||
|
"showMenu": "Show the menu",
|
||||||
|
"hideMenu": "Hide the menu"
|
||||||
|
},
|
||||||
|
"input": {
|
||||||
|
"resetColor": "Reset Color",
|
||||||
|
"datepicker": {
|
||||||
|
"today": "Today",
|
||||||
|
"tomorrow": "Tomorrow",
|
||||||
|
"nextMonday": "Next Monday",
|
||||||
|
"thisWeekend": "This Weekend",
|
||||||
|
"laterThisWeek": "Later This Week",
|
||||||
|
"nextWeek": "Next Week",
|
||||||
|
"chooseDate": "Choose a date"
|
||||||
|
},
|
||||||
|
"editor": {
|
||||||
|
"edit": "Edit",
|
||||||
|
"done": "Done",
|
||||||
|
"heading1": "Heading 1",
|
||||||
|
"heading2": "Heading 2",
|
||||||
|
"heading3": "Heading 3",
|
||||||
|
"headingSmaller": "Heading Smaller",
|
||||||
|
"headingBigger": "Heading Bigger",
|
||||||
|
"bold": "Bold",
|
||||||
|
"italic": "Italic",
|
||||||
|
"strikethrough": "Strikethrough",
|
||||||
|
"code": "Code",
|
||||||
|
"quote": "Quote",
|
||||||
|
"unorderedList": "Unordered List",
|
||||||
|
"orderedList": "Ordered List",
|
||||||
|
"cleanBlock": "Clean Block",
|
||||||
|
"link": "Link",
|
||||||
|
"image": "Image",
|
||||||
|
"table": "Table",
|
||||||
|
"horizontalRule": "Horizontal Rule",
|
||||||
|
"sideBySide": "Side By Side",
|
||||||
|
"guide": "Guide"
|
||||||
|
},
|
||||||
|
"multiselect": {
|
||||||
|
"createPlaceholder": "Create new",
|
||||||
|
"selectPlaceholder": "Click or press enter to select"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"task": {
|
||||||
|
"task": "Task",
|
||||||
|
"new": "Create a new task",
|
||||||
|
"delete": "Delete this task",
|
||||||
|
"createSuccess": "The task was successfully created.",
|
||||||
|
"addReminder": "Add a new reminder…",
|
||||||
|
"doneSuccess": "The task was successfully marked as done.",
|
||||||
|
"undoneSuccess": "The task was successfully un-marked as done.",
|
||||||
|
"openDetail": "Open task detail view",
|
||||||
|
"checklistTotal": "{checked} of {total} tasks",
|
||||||
|
"checklistAllDone": "{total} tasks",
|
||||||
|
"show": {
|
||||||
|
"titleCurrent": "Current Tasks",
|
||||||
|
"titleDates": "Tasks from {from} until {to}",
|
||||||
|
"noDates": "Show tasks without dates",
|
||||||
|
"current": "Current tasks",
|
||||||
|
"from": "Tasks from",
|
||||||
|
"until": "until",
|
||||||
|
"today": "Today",
|
||||||
|
"nextWeek": "Next Week",
|
||||||
|
"nextMonth": "Next Month",
|
||||||
|
"noTasks": "Nothing to do — Have a nice day!"
|
||||||
|
},
|
||||||
|
"detail": {
|
||||||
|
"chooseDueDate": "Click here to set a due date",
|
||||||
|
"chooseStartDate": "Click here to set a start date",
|
||||||
|
"chooseEndDate": "Click here to set an end date",
|
||||||
|
"move": "Move task to a different list",
|
||||||
|
"done": "Done!",
|
||||||
|
"undone": "Mark as undone",
|
||||||
|
"created": "Created {0} by {1}",
|
||||||
|
"updated": "Updated {0}",
|
||||||
|
"doneAt": "Done {0}",
|
||||||
|
"updateSuccess": "The task was saved successfully.",
|
||||||
|
"deleteSuccess": "The task has been deleted successfully.",
|
||||||
|
"belongsToList": "This task belongs to list '{list}'",
|
||||||
|
"due": "Due {at}",
|
||||||
|
"closePopup": "Close popup",
|
||||||
|
"delete": {
|
||||||
|
"header": "Delete this task",
|
||||||
|
"text1": "Are you sure you want to remove this task?",
|
||||||
|
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"assign": "Assign to a user",
|
||||||
|
"label": "Add labels",
|
||||||
|
"priority": "Set Priority",
|
||||||
|
"dueDate": "Set Due Date",
|
||||||
|
"startDate": "Set a Start Date",
|
||||||
|
"endDate": "Set an End Date",
|
||||||
|
"reminders": "Set Reminders",
|
||||||
|
"repeatAfter": "Set a repeating interval",
|
||||||
|
"percentDone": "Set Percent Done",
|
||||||
|
"attachments": "Add attachments",
|
||||||
|
"relatedTasks": "Add task relations",
|
||||||
|
"moveList": "Move task",
|
||||||
|
"color": "Set task color",
|
||||||
|
"delete": "Delete task",
|
||||||
|
"favorite": "Save as favorite",
|
||||||
|
"unfavorite": "Remove from favorites"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"assignees": "Assignees",
|
||||||
|
"color": "Color",
|
||||||
|
"created": "Created",
|
||||||
|
"createdBy": "Created By",
|
||||||
|
"description": "Description",
|
||||||
|
"done": "Done",
|
||||||
|
"dueDate": "Due Date",
|
||||||
|
"endDate": "End Date",
|
||||||
|
"labels": "Labels",
|
||||||
|
"percentDone": "% Done",
|
||||||
|
"priority": "Priority",
|
||||||
|
"relatedTasks": "Related Tasks",
|
||||||
|
"reminders": "Reminders",
|
||||||
|
"repeat": "Repeat",
|
||||||
|
"startDate": "Start Date",
|
||||||
|
"title": "Title",
|
||||||
|
"updated": "Updated"
|
||||||
|
},
|
||||||
|
"subscription": {
|
||||||
|
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
|
||||||
|
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
|
||||||
|
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
|
||||||
|
"subscribe": "Subscribe",
|
||||||
|
"unsubscribe": "Unsubscribe",
|
||||||
|
"subscribeSuccess": "You are now subscribed to this {entity}",
|
||||||
|
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
|
||||||
|
},
|
||||||
|
"attachment": {
|
||||||
|
"title": "Attachments",
|
||||||
|
"createdBy": "created {0} by {1}",
|
||||||
|
"downloadTooltip": "Download this attachment",
|
||||||
|
"upload": "Upload attachment",
|
||||||
|
"drop": "Drop files here to upload",
|
||||||
|
"delete": "Delete attachment",
|
||||||
|
"deleteTooltip": "Delete this attachment",
|
||||||
|
"deleteText1": "Are you sure you want to delete the attachment {filename}?",
|
||||||
|
"deleteText2": "This cannot be undone!",
|
||||||
|
"copyUrl": "Copy URL",
|
||||||
|
"copyUrlTooltip": "Copy the url of this attachment for usage in text"
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"title": "Comments",
|
||||||
|
"loading": "Loading comments…",
|
||||||
|
"edited": "edited {date}",
|
||||||
|
"creating": "Creating comment…",
|
||||||
|
"placeholder": "Add your comment…",
|
||||||
|
"comment": "Comment",
|
||||||
|
"delete": "Delete this comment",
|
||||||
|
"deleteText1": "Are you sure you want to delete this comment?",
|
||||||
|
"deleteText2": "This cannot be undone!",
|
||||||
|
"addedSuccess": "The comment was added successfully."
|
||||||
|
},
|
||||||
|
"deferDueDate": {
|
||||||
|
"title": "Defer due date",
|
||||||
|
"1day": "1 day",
|
||||||
|
"3days": "3 days",
|
||||||
|
"1week": "1 week"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"placeholder": "Click here to enter a description…",
|
||||||
|
"empty": "No description available yet."
|
||||||
|
},
|
||||||
|
"assignee": {
|
||||||
|
"placeholder": "Type to assign a user…",
|
||||||
|
"selectPlaceholder": "Assign this user",
|
||||||
|
"assignSuccess": "The user has been assigned successfully.",
|
||||||
|
"unassignSuccess": "The user has been unassigned successfully."
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"placeholder": "Type to add a new label…",
|
||||||
|
"createPlaceholder": "Add this as new label",
|
||||||
|
"addSuccess": "The label has been added successfully.",
|
||||||
|
"createSuccess": "The label has been created successfully.",
|
||||||
|
"removeSuccess": "The label has been removed successfully.",
|
||||||
|
"addCreateSuccess": "The label has been created and added successfully."
|
||||||
|
},
|
||||||
|
"priority": {
|
||||||
|
"unset": "Unset",
|
||||||
|
"low": "Low",
|
||||||
|
"medium": "Medium",
|
||||||
|
"high": "high",
|
||||||
|
"urgent": "Urgent",
|
||||||
|
"doNow": "DO NOW"
|
||||||
|
},
|
||||||
|
"relation": {
|
||||||
|
"add": "Add a New Task Relation",
|
||||||
|
"new": "New Task Relation",
|
||||||
|
"searchPlaceholder": "Type search for a new task to add as related…",
|
||||||
|
"createPlaceholder": "Add this as new related task",
|
||||||
|
"differentList": "This task belongs to a different list.",
|
||||||
|
"differentNamespace": "This task belongs to a different namespace.",
|
||||||
|
"noneYet": "No task relations yet.",
|
||||||
|
"delete": "Delete Task Relation",
|
||||||
|
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||||
|
"deleteText2": "This cannot be undone!",
|
||||||
|
"select": "Select a relation kind",
|
||||||
|
"kinds": {
|
||||||
|
"subtask": "Subtask | Subtasks",
|
||||||
|
"parenttask": "Parent Task | Parent Tasks",
|
||||||
|
"related": "Related Task | Related Tasks",
|
||||||
|
"duplicateof": "Duplicate Of | Duplicates Of",
|
||||||
|
"duplicates": "Duplicates | Duplicates",
|
||||||
|
"blocking": "Blocking | Blocking",
|
||||||
|
"blocked": "Blocked By | Blocked By",
|
||||||
|
"precedes": "Precedes | Precedes",
|
||||||
|
"follows": "Follows | Follows",
|
||||||
|
"copiedfrom": "Copied From | Copied From",
|
||||||
|
"copiedto": "Copied To | Copied To"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeat": {
|
||||||
|
"everyDay": "Every Day",
|
||||||
|
"everyWeek": "Every Week",
|
||||||
|
"everyMonth": "Every Month",
|
||||||
|
"mode": "Repeat mode",
|
||||||
|
"monthly": "Monthly",
|
||||||
|
"fromCurrentDate": "From Current Date",
|
||||||
|
"each": "Each",
|
||||||
|
"specifyAmount": "Specify an amount…",
|
||||||
|
"hours": "Hours",
|
||||||
|
"days": "Days",
|
||||||
|
"weeks": "Weeks",
|
||||||
|
"months": "Months",
|
||||||
|
"years": "Years"
|
||||||
|
},
|
||||||
|
"quickAddMagic": {
|
||||||
|
"hint": "You can use Quick Add Magic",
|
||||||
|
"what": "What?",
|
||||||
|
"title": "Quick Add Magic",
|
||||||
|
"intro": "When creating a task, you can use special keywords to directly add attributes to the newly created task. This allows to add commonly used attributes to tasks much faster.",
|
||||||
|
"multiple": "You can use this multiple times.",
|
||||||
|
"label1": "To add a label, simply prefix the name of the label with {prefix}.",
|
||||||
|
"label2": "Vikunja will first check if the label already exist and create it if not.",
|
||||||
|
"label3": "To use spaces, simply add a \" around the label name.",
|
||||||
|
"label4": "For example: {prefix}\"Label with spaces\".",
|
||||||
|
"priority1": "To set a task's priority, add a number 1-5, prefixed with a {prefix}.",
|
||||||
|
"priority2": "The higher the number, the higher the priority.",
|
||||||
|
"assignees": "To directly assign the task to a user, add their username prefixed with {prefix} to the task.",
|
||||||
|
"list1": "To set a list for the task to appear in, enter its name prefixed with {prefix}.",
|
||||||
|
"list2": "This will return an error if the list does not exist.",
|
||||||
|
"dateAndTime": "Date and time",
|
||||||
|
"date": "Any date will be used as the due date of the new task. You can use dates in any of these formats:",
|
||||||
|
"dateWeekday": "any weekday, will use the next date with that date",
|
||||||
|
"dateCurrentYear": "will use the current year",
|
||||||
|
"dateNth": "will use the {day}th of the current month",
|
||||||
|
"dateTime": "Combine any of the date formats with \"{time}\" (or {timePM}) to set a time."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"team": {
|
||||||
|
"title": "Teams",
|
||||||
|
"noTeams": "You are currently not part of any teams.",
|
||||||
|
"create": {
|
||||||
|
"title": "Create a new team",
|
||||||
|
"success": "The team was successfully created."
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"title": "Edit Team \"{team}\"",
|
||||||
|
"members": "Team Members",
|
||||||
|
"search": "Type to search a user…",
|
||||||
|
"addUser": "Add to team",
|
||||||
|
"makeMember": "Make Member",
|
||||||
|
"makeAdmin": "Make Admin",
|
||||||
|
"success": "The team was successfully updated.",
|
||||||
|
"userAddedSuccess": "The team member was successfully added.",
|
||||||
|
"madeMember": "The team member was successfully made member.",
|
||||||
|
"madeAdmin": "The team member was successfully made admin.",
|
||||||
|
"delete": {
|
||||||
|
"header": "Delete the team",
|
||||||
|
"text1": "Are you sure you want to delete this team and all of its members?",
|
||||||
|
"text2": "All team members will lose access to lists and namespaces shared with this team. This CANNOT BE UNDONE!",
|
||||||
|
"success": "The team was successfully deleted."
|
||||||
|
},
|
||||||
|
"deleteUser": {
|
||||||
|
"header": "Remove a user from the team",
|
||||||
|
"text1": "Are you sure you want to remove this user from the team?",
|
||||||
|
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
|
||||||
|
"success": "The user was successfully deleted from the team."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"name": "Team Name",
|
||||||
|
"namePlaceholder": "The team's name goes here…",
|
||||||
|
"nameRequired": "Please specify a name.",
|
||||||
|
"description": "Description",
|
||||||
|
"descriptionPlaceholder": "The teams description goes here…",
|
||||||
|
"admin": "Admin",
|
||||||
|
"member": "Member"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keyboardShortcuts": {
|
||||||
|
"title": "Keyboard Shortcuts",
|
||||||
|
"general": "General",
|
||||||
|
"allPages": "These shortcuts work on all pages.",
|
||||||
|
"currentPageOnly": "These shortcuts work only on the current page.",
|
||||||
|
"toggleMenu": "Toggle The Menu",
|
||||||
|
"quickSearch": "Open the search/quick action bar",
|
||||||
|
"then": "then",
|
||||||
|
"task": {
|
||||||
|
"title": "Task Page",
|
||||||
|
"done": "Mark a task as done",
|
||||||
|
"assign": "Assign to a user",
|
||||||
|
"labels": "Add labels to this task",
|
||||||
|
"dueDate": "Change the due date of this task",
|
||||||
|
"attachment": "Add an attachment to this task",
|
||||||
|
"related": "Modify related tasks of this task"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "List Views",
|
||||||
|
"switchToListView": "Switch to list view",
|
||||||
|
"switchToGanttView": "Switch to gantt view",
|
||||||
|
"switchToKanbanView": "Switch to kanban view",
|
||||||
|
"switchToTableView": "Switch to table view"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"available": "There is an update for Vikunja available!",
|
||||||
|
"do": "Update Now"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"edit": "Edit",
|
||||||
|
"archive": "Archive",
|
||||||
|
"duplicate": "Duplicate",
|
||||||
|
"delete": "Delete",
|
||||||
|
"unarchive": "Un-Archive",
|
||||||
|
"setBackground": "Set background",
|
||||||
|
"share": "Share",
|
||||||
|
"newList": "New list"
|
||||||
|
},
|
||||||
|
"apiConfig": {
|
||||||
|
"url": "Vikunja URL",
|
||||||
|
"urlPlaceholder": "eg. https://localhost:3456",
|
||||||
|
"change": "change",
|
||||||
|
"signInOn": "Sign in to your Vikunja account on {0}",
|
||||||
|
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.",
|
||||||
|
"success": "Using Vikunja installation at \"{domain}\".",
|
||||||
|
"urlRequired": "A url is required."
|
||||||
|
},
|
||||||
|
"loadingError": {
|
||||||
|
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||||
|
"tryAgain": "try again",
|
||||||
|
"contact": "contact us"
|
||||||
|
},
|
||||||
|
"notification": {
|
||||||
|
"title": "Notifications",
|
||||||
|
"none": "You don't have any notifications. Have a nice day!",
|
||||||
|
"explainer": "Notifications will appear here when actions on namespaces, lists or tasks you subscribed to happen."
|
||||||
|
},
|
||||||
|
"quickActions": {
|
||||||
|
"commands": "Commands",
|
||||||
|
"placeholder": "Type a command or search…",
|
||||||
|
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
|
||||||
|
"tasks": "Tasks",
|
||||||
|
"lists": "Lists",
|
||||||
|
"teams": "Teams",
|
||||||
|
"newList": "Enter the title of the new list…",
|
||||||
|
"newTask": "Enter the title of the new task…",
|
||||||
|
"newNamespace": "Enter the title of the new namespace…",
|
||||||
|
"newTeam": "Enter the name of the new team…",
|
||||||
|
"createTask": "Create a task in the current list ({title})",
|
||||||
|
"createList": "Create a list in the current namespace ({title})",
|
||||||
|
"cmds": {
|
||||||
|
"newTask": "New task",
|
||||||
|
"newList": "New list",
|
||||||
|
"newNamespace": "New namespace",
|
||||||
|
"newTeam": "New team"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"locale": "en",
|
||||||
|
"altFormatLong": "j M Y H:i",
|
||||||
|
"altFormatShort": "j M Y"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"error": "Error",
|
||||||
|
"success": "Success",
|
||||||
|
"0001": "You're not allowed to do that.",
|
||||||
|
"1001": "A user with this username already exists.",
|
||||||
|
"1002": "A user with this email address already exists.",
|
||||||
|
"1004": "No username and password specified.",
|
||||||
|
"1005": "The user does not exist.",
|
||||||
|
"1006": "Could not get the user id.",
|
||||||
|
"1008": "No password reset token provided.",
|
||||||
|
"1009": "Invalid password reset token.",
|
||||||
|
"1010": "Invalid email confirm token.",
|
||||||
|
"1011": "Wrong username or password.",
|
||||||
|
"1012": "Email address of the user not confirmed.",
|
||||||
|
"1013": "New password is empty.",
|
||||||
|
"1014": "Old password is empty.",
|
||||||
|
"1015": "Totp is already enabled for this user.",
|
||||||
|
"1016": "Totp is not enabled for this user.",
|
||||||
|
"1017": "The totp passcode is invalid.",
|
||||||
|
"1018": "The user avatar type setting is invalid.",
|
||||||
|
"2001": "ID cannot be empty or 0.",
|
||||||
|
"2002": "Some of the request data was invalid.",
|
||||||
|
"3001": "The list does not exist.",
|
||||||
|
"3004": "You need to have read permissions on that list to perform that action.",
|
||||||
|
"3005": "The list title cannot be empty.",
|
||||||
|
"3006": "The list share does not exist.",
|
||||||
|
"3007": "A list with this identifier already exists.",
|
||||||
|
"3008": "The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list.",
|
||||||
|
"4001": "The list task text cannot be empty.",
|
||||||
|
"4002": "The list task does not exist.",
|
||||||
|
"4003": "All bulk editing tasks must belong to the same list.",
|
||||||
|
"4004": "Need at least one task when bulk editing tasks.",
|
||||||
|
"4005": "You do not have the right to see the task.",
|
||||||
|
"4006": "You can't set a parent task as the task itself.",
|
||||||
|
"4007": "You can't create a task relation with an invalid kind of relation.",
|
||||||
|
"4008": "You can't create a task relation which already exists.",
|
||||||
|
"4009": "The task relation does not exist.",
|
||||||
|
"4010": "Cannot relate a task with itself.",
|
||||||
|
"4011": "The task attachment does not exist.",
|
||||||
|
"4012": "The task attachment is too large.",
|
||||||
|
"4013": "The task sort param is invalid.",
|
||||||
|
"4014": "The task sort order is invalid.",
|
||||||
|
"4015": "The task comment does not exist.",
|
||||||
|
"4016": "Invalid task field.",
|
||||||
|
"4017": "Invalid task filter comparator.",
|
||||||
|
"4018": "Invalid task filter concatinator.",
|
||||||
|
"4019": "Invalid task filter value.",
|
||||||
|
"5001": "The namespace does not exist.",
|
||||||
|
"5003": "You do not have access to the specified namespace.",
|
||||||
|
"5006": "The namespace name cannot be empty.",
|
||||||
|
"5009": "You need to have namespace read access to perform that action.",
|
||||||
|
"5010": "This team does not have access to that namespace.",
|
||||||
|
"5011": "This user has already access to that namespace.",
|
||||||
|
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
||||||
|
"6001": "The team name cannot be empty.",
|
||||||
|
"6002": "The team does not exist.",
|
||||||
|
"6004": "The team already has access to that namespace or list.",
|
||||||
|
"6005": "The user is already a member of that team.",
|
||||||
|
"6006": "Cannot delete the last team member.",
|
||||||
|
"6007": "The team does not have access to the list to perform that action.",
|
||||||
|
"7002": "The user already has access to that list.",
|
||||||
|
"7003": "You do not have access to that list.",
|
||||||
|
"8001": "This label already exists on that task.",
|
||||||
|
"8002": "The label does not exist.",
|
||||||
|
"8003": "You do not have access to this label.",
|
||||||
|
"9001": "The right is invalid.",
|
||||||
|
"10001": "The bucket does not exist.",
|
||||||
|
"10002": "The bucket does not belong to that list.",
|
||||||
|
"10003": "You cannot remove the last bucket on a list.",
|
||||||
|
"10004": "You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold.",
|
||||||
|
"10005": "There can be only one done bucket per list.",
|
||||||
|
"11001": "The saved filter does not exist.",
|
||||||
|
"11002": "Saved filters are not available for link shares.",
|
||||||
|
"12001": "The subscription entity type is invalid.",
|
||||||
|
"12002": "You are already subscribed to the entity itself or a parent entity.",
|
||||||
|
"13001": "This link share requires a password for authentication, but none was provided.",
|
||||||
|
"13002": "The provided link share password was invalid."
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"title": "About",
|
||||||
|
"frontendVersion": "Frontend Version: {version}",
|
||||||
|
"apiVersion": "API Version: {version}"
|
||||||
|
}
|
||||||
|
}
|
|
@ -901,7 +901,7 @@
|
||||||
"5010": "This team does not have access to that namespace.",
|
"5010": "This team does not have access to that namespace.",
|
||||||
"5011": "This user has already access to that namespace.",
|
"5011": "This user has already access to that namespace.",
|
||||||
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
||||||
"6001": "The team name cannot be emtpy.",
|
"6001": "The team name cannot be empty.",
|
||||||
"6002": "The team does not exist.",
|
"6002": "The team does not exist.",
|
||||||
"6004": "The team already has access to that namespace or list.",
|
"6004": "The team already has access to that namespace or list.",
|
||||||
"6005": "The user is already a member of that team.",
|
"6005": "The user is already a member of that team.",
|
||||||
|
|
|
@ -114,12 +114,12 @@
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
},
|
},
|
||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Color Scheme",
|
"title": "Phối màu",
|
||||||
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
"setSuccess": "Đã lưu phối màu vào {colorScheme}",
|
||||||
"colorScheme": {
|
"colorScheme": {
|
||||||
"light": "Light",
|
"light": "Sáng",
|
||||||
"system": "System",
|
"system": "Hệ thống",
|
||||||
"dark": "Dark"
|
"dark": "Tối"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -556,7 +556,7 @@
|
||||||
"text2": "Thao tác này cũng sẽ xóa tất cả tệp đính kèm, lời nhắc và liên kết đến công việc này. Nó không thể hoàn tác!"
|
"text2": "Thao tác này cũng sẽ xóa tất cả tệp đính kèm, lời nhắc và liên kết đến công việc này. Nó không thể hoàn tác!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Assign to a user",
|
"assign": "Chỉ định một người",
|
||||||
"label": "Thêm nhãn",
|
"label": "Thêm nhãn",
|
||||||
"priority": "Mức độ ưu tiên",
|
"priority": "Mức độ ưu tiên",
|
||||||
"dueDate": "Đặt ngày đến hạn",
|
"dueDate": "Đặt ngày đến hạn",
|
||||||
|
@ -775,7 +775,7 @@
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Trang công việc",
|
"title": "Trang công việc",
|
||||||
"done": "Đánh dấu hoàn thành",
|
"done": "Đánh dấu hoàn thành",
|
||||||
"assign": "Assign to a user",
|
"assign": "Chỉ định một người",
|
||||||
"labels": "Thêm nhãn cho công việc này",
|
"labels": "Thêm nhãn cho công việc này",
|
||||||
"dueDate": "Thay đổi ngày hết hạn của công việc này",
|
"dueDate": "Thay đổi ngày hết hạn của công việc này",
|
||||||
"attachment": "Thêm tệp đính kèm cho công việc này",
|
"attachment": "Thêm tệp đính kèm cho công việc này",
|
||||||
|
@ -901,7 +901,7 @@
|
||||||
"5010": "Team này không có quyền bước vào góc làm việc đó.",
|
"5010": "Team này không có quyền bước vào góc làm việc đó.",
|
||||||
"5011": "Người này đã có quyền bước vào góc làm việc đó.",
|
"5011": "Người này đã có quyền bước vào góc làm việc đó.",
|
||||||
"5012": "Góc làm việc đã được lưu trữ nên chỉ có thể vào đó để đọc.",
|
"5012": "Góc làm việc đã được lưu trữ nên chỉ có thể vào đó để đọc.",
|
||||||
"6001": "Team cũng cần có tên gọi mà.",
|
"6001": "The team name cannot be empty.",
|
||||||
"6002": "Team không tồn tại.",
|
"6002": "Team không tồn tại.",
|
||||||
"6004": "Team đã có quyền bước vào góc làm việc và xem danh sách.",
|
"6004": "Team đã có quyền bước vào góc làm việc và xem danh sách.",
|
||||||
"6005": "Người này đã là thành viên của Team đó rồi.",
|
"6005": "Người này đã là thành viên của Team đó rồi.",
|
||||||
|
|
|
@ -19,14 +19,12 @@ $mobile: math.div($tablet, 2);
|
||||||
$family-sans-serif: 'Open Sans', Helvetica, Arial, sans-serif;
|
$family-sans-serif: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||||
$vikunja-font: 'Quicksand', sans-serif;
|
$vikunja-font: 'Quicksand', sans-serif;
|
||||||
|
|
||||||
$thickness: 1px;
|
|
||||||
$pagination-current-border: var(--primary);
|
$pagination-current-border: var(--primary);
|
||||||
$navbar-item-active-color: var(--primary);
|
$navbar-item-active-color: var(--primary);
|
||||||
|
|
||||||
$dropdown-content-shadow: none;
|
$dropdown-content-shadow: none;
|
||||||
$dropdown-item-hover-background-color: var(--grey-100);
|
$dropdown-item-hover-background-color: var(--grey-100);
|
||||||
|
|
||||||
$bulmaswatch-import-font: false !default;
|
|
||||||
$site-background: var(--grey-100);
|
$site-background: var(--grey-100);
|
||||||
|
|
||||||
$transition-duration: 150ms;
|
$transition-duration: 150ms;
|
||||||
|
|
|
@ -79,6 +79,8 @@
|
||||||
--link: var(--primary);
|
--link: var(--primary);
|
||||||
--link-hover: hsla(var(--primary-h), var(--primary-s), var(--primary-l), .75);
|
--link-hover: hsla(var(--primary-h), var(--primary-s), var(--primary-l), .75);
|
||||||
--border: var(--grey-200);
|
--border: var(--grey-200);
|
||||||
|
--input-disabled-background-color: var(--grey-100);
|
||||||
|
--input-disabled-border-color: var(--grey-300);
|
||||||
|
|
||||||
&.dark {
|
&.dark {
|
||||||
// Light mode colours reversed for dark mode
|
// Light mode colours reversed for dark mode
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
@import "@bulvar/bulma/sass/elements/content";
|
@import "@bulvar/bulma/sass/elements/content";
|
||||||
@import "@bulvar/bulma/sass/elements/icon";
|
@import "@bulvar/bulma/sass/elements/icon";
|
||||||
@import "@bulvar/bulma/sass/elements/image";
|
@import "@bulvar/bulma/sass/elements/image";
|
||||||
@import "@bulvar/bulma/sass/elements/notification";
|
// @import "@bulvar/bulma/sass/elements/notification"; // not used
|
||||||
@import "@bulvar/bulma/sass/elements/progress";
|
@import "@bulvar/bulma/sass/elements/progress";
|
||||||
@import "@bulvar/bulma/sass/elements/table";
|
@import "@bulvar/bulma/sass/elements/table";
|
||||||
@import "@bulvar/bulma/sass/elements/tag";
|
@import "@bulvar/bulma/sass/elements/tag";
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
// @import "@bulvar/bulma/sass/components/level"; // not used
|
// @import "@bulvar/bulma/sass/components/level"; // not used
|
||||||
@import "@bulvar/bulma/sass/components/media";
|
@import "@bulvar/bulma/sass/components/media";
|
||||||
@import "@bulvar/bulma/sass/components/menu";
|
@import "@bulvar/bulma/sass/components/menu";
|
||||||
@import "@bulvar/bulma/sass/components/message";
|
// @import "@bulvar/bulma/sass/components/message"; //not used
|
||||||
@import "@bulvar/bulma/sass/components/modal";
|
@import "@bulvar/bulma/sass/components/modal";
|
||||||
@import "@bulvar/bulma/sass/components/navbar";
|
@import "@bulvar/bulma/sass/components/navbar";
|
||||||
@import "@bulvar/bulma/sass/components/pagination";
|
@import "@bulvar/bulma/sass/components/pagination";
|
||||||
|
|
|
@ -7,4 +7,4 @@
|
||||||
@import "form";
|
@import "form";
|
||||||
@import "link-share";
|
@import "link-share";
|
||||||
@import "loading";
|
@import "loading";
|
||||||
@import "notification";
|
@import "flatpickr";
|
|
@ -0,0 +1,221 @@
|
||||||
|
// Flatpickr overrides to use css custom properties and enable styling it
|
||||||
|
|
||||||
|
.flatpickr-calendar {
|
||||||
|
background: var(--white);
|
||||||
|
box-shadow: 1px 0 0 var(--grey-200), -1px 0 0 var(--grey-200), 0 1px 0 var(--grey-200), 0 -1px 0 var(--grey-200), 0 3px 13px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1) {
|
||||||
|
box-shadow: -2px 0 0 var(--grey-200), 5px 0 0 var(--grey-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-calendar.hasTime .flatpickr-time {
|
||||||
|
border-top: 1px solid var(--grey-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-calendar.arrowTop:before {
|
||||||
|
border-bottom-color: var(--grey-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-calendar.arrowTop:after {
|
||||||
|
border-bottom-color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-calendar.arrowBottom:before {
|
||||||
|
border-top-color: var(--grey-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-calendar.arrowBottom:after {
|
||||||
|
border-top-color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-months .flatpickr-month,
|
||||||
|
.flatpickr-months .flatpickr-prev-month,
|
||||||
|
.flatpickr-months .flatpickr-next-month {
|
||||||
|
color: var(--text);
|
||||||
|
fill: var(--grey-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-months .flatpickr-prev-month:hover,
|
||||||
|
.flatpickr-months .flatpickr-next-month:hover {
|
||||||
|
color: var(--grey-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-months .flatpickr-prev-month:hover svg,
|
||||||
|
.flatpickr-months .flatpickr-next-month:hover svg {
|
||||||
|
fill: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.numInputWrapper span {
|
||||||
|
border: 1px solid var(--grey-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.numInputWrapper span:hover {
|
||||||
|
background: var(--grey-800);
|
||||||
|
}
|
||||||
|
|
||||||
|
.numInputWrapper span:active {
|
||||||
|
background: var(--grey-800);
|
||||||
|
}
|
||||||
|
|
||||||
|
.numInputWrapper span.arrowUp:after {
|
||||||
|
border-bottom: 4px solid var(--grey-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.numInputWrapper span.arrowDown:after {
|
||||||
|
border-top: 4px solid var(--grey-200);
|
||||||
|
top: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.numInputWrapper span svg path {
|
||||||
|
fill: var(--grey-800);
|
||||||
|
}
|
||||||
|
|
||||||
|
.numInputWrapper:hover {
|
||||||
|
background: var(--grey-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-current-month span.cur-month:hover {
|
||||||
|
background: var(--grey-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-current-month .numInputWrapper span.arrowUp:after {
|
||||||
|
border-bottom-color: var(--grey-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-current-month .numInputWrapper span.arrowDown:after {
|
||||||
|
border-top-color: var(--grey-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-current-month input.cur-year[disabled],
|
||||||
|
.flatpickr-current-month input.cur-year[disabled]:hover {
|
||||||
|
color: var(--grey-800);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-current-month .flatpickr-monthDropdown-months:hover {
|
||||||
|
background: var(--grey-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
span.flatpickr-weekday {
|
||||||
|
color: var(--grey-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dayContainer + .dayContainer {
|
||||||
|
box-shadow: -1px 0 0 var(--grey-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-day {
|
||||||
|
color: var(--grey-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-day.inRange,
|
||||||
|
.flatpickr-day.prevMonthDay.inRange,
|
||||||
|
.flatpickr-day.nextMonthDay.inRange,
|
||||||
|
.flatpickr-day.today.inRange,
|
||||||
|
.flatpickr-day.prevMonthDay.today.inRange,
|
||||||
|
.flatpickr-day.nextMonthDay.today.inRange,
|
||||||
|
.flatpickr-day:hover,
|
||||||
|
.flatpickr-day.prevMonthDay:hover,
|
||||||
|
.flatpickr-day.nextMonthDay:hover,
|
||||||
|
.flatpickr-day:focus,
|
||||||
|
.flatpickr-day.prevMonthDay:focus,
|
||||||
|
.flatpickr-day.nextMonthDay:focus {
|
||||||
|
background: var(--grey-200);
|
||||||
|
border-color: var(--grey-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-day.today {
|
||||||
|
border-color: var(--grey-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-day.today:hover,
|
||||||
|
.flatpickr-day.today:focus {
|
||||||
|
border-color: var(--grey-400);
|
||||||
|
background: var(--grey-400);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-day.selected,
|
||||||
|
.flatpickr-day.startRange,
|
||||||
|
.flatpickr-day.endRange,
|
||||||
|
.flatpickr-day.selected.inRange,
|
||||||
|
.flatpickr-day.startRange.inRange,
|
||||||
|
.flatpickr-day.endRange.inRange,
|
||||||
|
.flatpickr-day.selected:focus,
|
||||||
|
.flatpickr-day.startRange:focus,
|
||||||
|
.flatpickr-day.endRange:focus,
|
||||||
|
.flatpickr-day.selected:hover,
|
||||||
|
.flatpickr-day.startRange:hover,
|
||||||
|
.flatpickr-day.endRange:hover,
|
||||||
|
.flatpickr-day.selected.prevMonthDay,
|
||||||
|
.flatpickr-day.startRange.prevMonthDay,
|
||||||
|
.flatpickr-day.endRange.prevMonthDay,
|
||||||
|
.flatpickr-day.selected.nextMonthDay,
|
||||||
|
.flatpickr-day.startRange.nextMonthDay,
|
||||||
|
.flatpickr-day.endRange.nextMonthDay {
|
||||||
|
background: var(--primary);
|
||||||
|
color: var(--white);
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)),
|
||||||
|
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)),
|
||||||
|
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) {
|
||||||
|
box-shadow: -10px 0 0 var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-day.inRange {
|
||||||
|
box-shadow: -5px 0 0 var(--grey-200), 5px 0 0 var(--grey-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-day.flatpickr-disabled,
|
||||||
|
.flatpickr-day.flatpickr-disabled:hover,
|
||||||
|
.flatpickr-day.prevMonthDay,
|
||||||
|
.flatpickr-day.nextMonthDay,
|
||||||
|
.flatpickr-day.notAllowed,
|
||||||
|
.flatpickr-day.notAllowed.prevMonthDay,
|
||||||
|
.flatpickr-day.notAllowed.nextMonthDay {
|
||||||
|
color: var(--grey-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-day.flatpickr-disabled,
|
||||||
|
.flatpickr-day.flatpickr-disabled:hover {
|
||||||
|
color: var(--grey-800);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-day.week.selected {
|
||||||
|
box-shadow: -5px 0 0 var(--primary), 5px 0 0 var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-weekwrapper .flatpickr-weeks {
|
||||||
|
box-shadow: 1px 0 0 var(--grey-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-weekwrapper span.flatpickr-day,
|
||||||
|
.flatpickr-weekwrapper span.flatpickr-day:hover {
|
||||||
|
color: var(--grey-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-time .numInputWrapper span.arrowUp:after {
|
||||||
|
border-bottom-color: var(--grey-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-time .numInputWrapper span.arrowDown:after {
|
||||||
|
border-top-color: var(--grey-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-time input {
|
||||||
|
color: var(--grey-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-time .flatpickr-time-separator,
|
||||||
|
.flatpickr-time .flatpickr-am-pm {
|
||||||
|
color: var(--grey-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-time input:hover,
|
||||||
|
.flatpickr-time .flatpickr-am-pm:hover,
|
||||||
|
.flatpickr-time input:focus,
|
||||||
|
.flatpickr-time .flatpickr-am-pm:focus {
|
||||||
|
background: var(--grey-100);
|
||||||
|
}
|
|
@ -40,6 +40,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.select select {
|
.select select {
|
||||||
|
$thickness: 1px;
|
||||||
border-width: $thickness;
|
border-width: $thickness;
|
||||||
|
|
||||||
&:not([multiple]) {
|
&:not([multiple]) {
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
.notification {
|
|
||||||
border: $thickness solid $border;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications {
|
|
||||||
left: 0.5rem !important;
|
|
||||||
bottom: 1rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message .message-body {
|
|
||||||
border: $thickness solid;
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@
|
||||||
<h2 v-if="userInfo">
|
<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">
|
<message variant="danger" v-if="deletionScheduledAt !== null">
|
||||||
{{
|
{{
|
||||||
$t('user.deletion.scheduled', {
|
$t('user.deletion.scheduled', {
|
||||||
date: formatDateShort(deletionScheduledAt),
|
date: formatDateShort(deletionScheduledAt),
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
<router-link :to="{name: 'user.settings', hash: '#deletion'}">
|
<router-link :to="{name: 'user.settings', hash: '#deletion'}">
|
||||||
{{ $t('user.deletion.scheduledCancel') }}
|
{{ $t('user.deletion.scheduledCancel') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</message>
|
||||||
<add-task
|
<add-task
|
||||||
:listId="defaultListId"
|
:listId="defaultListId"
|
||||||
@taskAdded="updateTaskList"
|
@taskAdded="updateTaskList"
|
||||||
|
@ -55,90 +55,70 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts" setup>
|
||||||
import {mapState} from 'vuex'
|
import {ref, computed} from 'vue'
|
||||||
import ShowTasks from './tasks/ShowTasks.vue'
|
import {useStore} from 'vuex'
|
||||||
import {getHistory} from '../modules/listHistory'
|
import {useNow} from '@vueuse/core'
|
||||||
|
|
||||||
|
import Message from '@/components/misc/message.vue'
|
||||||
|
import ShowTasks from '@/views/tasks/ShowTasks.vue'
|
||||||
import ListCard from '@/components/list/partials/list-card.vue'
|
import ListCard from '@/components/list/partials/list-card.vue'
|
||||||
import AddTask from '../components/tasks/add-task.vue'
|
import AddTask from '@/components/tasks/add-task.vue'
|
||||||
import {LOADING, LOADING_MODULE} from '../store/mutation-types'
|
|
||||||
import {parseDateOrNull} from '../helpers/parseDateOrNull'
|
|
||||||
|
|
||||||
export default {
|
import {getHistory} from '@/modules/listHistory'
|
||||||
name: 'Home',
|
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
||||||
components: {
|
import {formatDateShort, formatDateSince} from '@/helpers/time/formatDate'
|
||||||
ListCard,
|
|
||||||
ShowTasks,
|
|
||||||
AddTask,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
currentDate: new Date(),
|
|
||||||
tasks: [],
|
|
||||||
showTasksKey: 0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
welcome() {
|
|
||||||
const now = new Date()
|
|
||||||
|
|
||||||
if (now.getHours() < 5) {
|
const now = useNow()
|
||||||
return 'Night'
|
const welcome = computed(() => {
|
||||||
}
|
const hours = new Date(now.value).getHours()
|
||||||
|
|
||||||
if (now.getHours() < 11) {
|
if (hours < 5) {
|
||||||
return 'Morning'
|
return 'Night'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (now.getHours() < 18) {
|
if (hours < 11) {
|
||||||
return 'Day'
|
return 'Morning'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (now.getHours() < 23) {
|
if (hours < 18) {
|
||||||
return 'Evening'
|
return 'Day'
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Night'
|
if (hours < 23) {
|
||||||
},
|
return 'Evening'
|
||||||
listHistory() {
|
}
|
||||||
const history = getHistory()
|
|
||||||
return history.map(l => {
|
|
||||||
return this.$store.getters['lists/getListById'](l.id)
|
|
||||||
}).filter(l => l !== null)
|
|
||||||
},
|
|
||||||
...mapState({
|
|
||||||
migratorsEnabled: state =>
|
|
||||||
state.config.availableMigrators !== null &&
|
|
||||||
state.config.availableMigrators.length > 0,
|
|
||||||
authenticated: state => state.auth.authenticated,
|
|
||||||
userInfo: state => state.auth.info,
|
|
||||||
hasTasks: state => state.hasTasks,
|
|
||||||
defaultListId: state => state.auth.defaultListId,
|
|
||||||
defaultNamespaceId: state => {
|
|
||||||
if (state.namespaces.namespaces.length === 0) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return state.namespaces.namespaces[0].id
|
return 'Night'
|
||||||
},
|
})
|
||||||
hasLists: state => {
|
|
||||||
if (state.namespaces.namespaces.length === 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return state.namespaces.namespaces[0].lists.length > 0
|
const store = useStore()
|
||||||
},
|
const listHistory = computed(() => {
|
||||||
loading: state => state[LOADING] && state[LOADING_MODULE] === 'tasks',
|
const history = getHistory()
|
||||||
deletionScheduledAt: state => parseDateOrNull(state.auth.info?.deletionScheduledAt),
|
return history.map(l => {
|
||||||
}),
|
return store.getters['lists/getListById'](l.id)
|
||||||
},
|
}).filter(l => l !== null)
|
||||||
methods: {
|
})
|
||||||
// This is to reload the tasks list after adding a new task through the global task add.
|
|
||||||
// FIXME: Should use vuex (somehow?)
|
|
||||||
updateTaskList() {
|
const migratorsEnabled = computed(() => store.state.config.availableMigrators?.length > 0)
|
||||||
this.showTasksKey++
|
const userInfo = computed(() => store.state.auth.info)
|
||||||
},
|
const hasTasks = computed(() => store.state.hasTasks)
|
||||||
},
|
const defaultListId = computed(() => store.state.auth.defaultListId)
|
||||||
|
const defaultNamespaceId = computed(() => store.state.namespaces.namespaces?.[0].id || 0)
|
||||||
|
const hasLists = computed (() => {
|
||||||
|
return store.state.namespaces.namespaces.length === 0
|
||||||
|
? false
|
||||||
|
: store.state.namespaces.namespaces[0].lists.length > 0
|
||||||
|
})
|
||||||
|
const loading = computed(() => store.state.loading && store.state.loadingModule === 'tasks')
|
||||||
|
const deletionScheduledAt = computed(() => parseDateOrNull(store.state.auth.info?.deletionScheduledAt))
|
||||||
|
|
||||||
|
// This is to reload the tasks list after adding a new task through the global task add.
|
||||||
|
// FIXME: Should use vuex (somehow?)
|
||||||
|
const showTasksKey = ref(0)
|
||||||
|
function updateTaskList() {
|
||||||
|
showTasksKey.value++
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -147,7 +127,7 @@ export default {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
max-height: calc(#{$list-height * 2} + #{$list-spacing * 2} - 4px);
|
max-height: calc(#{$list-height * 2} + #{$list-spacing * 2} - 4px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@media screen and (max-width: $mobile) {
|
@media screen and (max-width: $mobile) {
|
||||||
max-height: calc(#{$list-height * 4} + #{$list-spacing * 4} - 4px);
|
max-height: calc(#{$list-height * 4} + #{$list-spacing * 4} - 4px);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,9 +36,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div class="notification is-warning" v-if="currentList.isArchived">
|
<message variant="warning" v-if="currentList.isArchived" class="mb-4">
|
||||||
{{ $t('list.archived') }}
|
{{ $t('list.archived') }}
|
||||||
</div>
|
</message>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<router-view/>
|
<router-view/>
|
||||||
|
@ -46,6 +46,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Message from '@/components/misc/message'
|
||||||
import ListModel from '../../models/list'
|
import ListModel from '../../models/list'
|
||||||
import ListService from '../../services/list'
|
import ListService from '../../services/list'
|
||||||
import {CURRENT_LIST} from '../../store/mutation-types'
|
import {CURRENT_LIST} from '../../store/mutation-types'
|
||||||
|
@ -53,6 +54,7 @@ import {getListView} from '../../helpers/saveListView'
|
||||||
import {saveListToHistory} from '../../modules/listHistory'
|
import {saveListToHistory} from '../../modules/listHistory'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {Message},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
listService: new ListService(),
|
listService: new ListService(),
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
<div class="migration-in-progress">
|
<div class="migration-in-progress">
|
||||||
<img :alt="migrator.name" :src="migrator.icon" class="logo"/>
|
<img :alt="migrator.name" :src="migrator.icon" class="logo"/>
|
||||||
<div class="progress-dots">
|
<div class="progress-dots">
|
||||||
<span v-for="i in progressDotsCount" :key="i" />
|
<span v-for="i in progressDotsCount" :key="i"/>
|
||||||
</div>
|
</div>
|
||||||
<Logo class="logo"/>
|
<Logo class="logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,11 +57,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="message is-primary">
|
<message class="mb-4">
|
||||||
<div class="message-body">
|
{{ message }}
|
||||||
{{ message }}
|
</message>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<x-button :to="{name: 'home'}">{{ $t('misc.refresh') }}</x-button>
|
<x-button :to="{name: 'home'}">{{ $t('misc.refresh') }}</x-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,6 +69,7 @@
|
||||||
import AbstractMigrationService from '@/services/migrator/abstractMigration'
|
import AbstractMigrationService from '@/services/migrator/abstractMigration'
|
||||||
import AbstractMigrationFileService from '@/services/migrator/abstractMigrationFile'
|
import AbstractMigrationFileService from '@/services/migrator/abstractMigrationFile'
|
||||||
import Logo from '@/assets/logo.svg?component'
|
import Logo from '@/assets/logo.svg?component'
|
||||||
|
import Message from '@/components/misc/message'
|
||||||
|
|
||||||
import {MIGRATORS} from './migrators'
|
import {MIGRATORS} from './migrators'
|
||||||
|
|
||||||
|
@ -79,7 +78,10 @@ const PROGRESS_DOTS_COUNT = 8
|
||||||
export default {
|
export default {
|
||||||
name: 'MigrateService',
|
name: 'MigrateService',
|
||||||
|
|
||||||
components: { Logo },
|
components: {
|
||||||
|
Logo,
|
||||||
|
Message,
|
||||||
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -101,7 +103,7 @@ export default {
|
||||||
|
|
||||||
beforeRouteEnter(to) {
|
beforeRouteEnter(to) {
|
||||||
if (MIGRATORS[to.params.service] === undefined) {
|
if (MIGRATORS[to.params.service] === undefined) {
|
||||||
return { name: 'not-found' }
|
return {name: 'not-found'}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -122,7 +124,7 @@ export default {
|
||||||
if (this.migrator.isFileMigrator) {
|
if (this.migrator.isFileMigrator) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.authUrl = await this.migrationService.getAuthUrl().then(({url}) => url)
|
this.authUrl = await this.migrationService.getAuthUrl().then(({url}) => url)
|
||||||
|
|
||||||
this.migratorAuthCode = location.hash.startsWith('#token=')
|
this.migratorAuthCode = location.hash.startsWith('#token=')
|
||||||
|
@ -150,7 +152,7 @@ export default {
|
||||||
this.lastMigrationDate = null
|
this.lastMigrationDate = null
|
||||||
this.message = ''
|
this.message = ''
|
||||||
|
|
||||||
let migrationConfig = { code: this.migratorAuthCode }
|
let migrationConfig = {code: this.migratorAuthCode}
|
||||||
|
|
||||||
if (this.migrator.isFileMigrator) {
|
if (this.migrator.isFileMigrator) {
|
||||||
if (this.$refs.uploadInput.files.length === 0) {
|
if (this.$refs.uploadInput.files.length === 0) {
|
||||||
|
@ -160,7 +162,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { message } = await this.migrationService.migrate(migrationConfig)
|
const {message} = await this.migrationService.migrate(migrationConfig)
|
||||||
this.message = message
|
this.message = message
|
||||||
return this.$store.dispatch('namespaces/loadNamespaces')
|
return this.$store.dispatch('namespaces/loadNamespaces')
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -173,24 +175,24 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.migration-in-progress-container {
|
.migration-in-progress-container {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
margin: 4rem auto 0;
|
margin: 4rem auto 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.migration-in-progress {
|
.migration-in-progress {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
display: block;
|
display: block;
|
||||||
max-height: 100px;
|
max-height: 100px;
|
||||||
max-width: 100px;
|
max-width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-dots {
|
.progress-dots {
|
||||||
|
|
|
@ -28,19 +28,20 @@
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ $t('namespace.attributes.color') }}</label>
|
<label class="label">{{ $t('namespace.attributes.color') }}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<color-picker v-model="namespace.hexColor" />
|
<color-picker v-model="namespace.hexColor"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="notification is-info mt-4">
|
<message class="mt-4">
|
||||||
<h4 class="title">{{ $t('namespace.create.tooltip') }}</h4>
|
<h4 class="title">{{ $t('namespace.create.tooltip') }}</h4>
|
||||||
|
|
||||||
{{ $t('namespace.create.explanation') }}
|
{{ $t('namespace.create.explanation') }}
|
||||||
</div>
|
</message>
|
||||||
</create-edit>
|
</create-edit>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Message from '@/components/misc/message'
|
||||||
import NamespaceModel from '../../models/namespace'
|
import NamespaceModel from '../../models/namespace'
|
||||||
import NamespaceService from '../../services/namespace'
|
import NamespaceService from '../../services/namespace'
|
||||||
import CreateEdit from '@/components/misc/create-edit.vue'
|
import CreateEdit from '@/components/misc/create-edit.vue'
|
||||||
|
@ -56,6 +57,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
Message,
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
CreateEdit,
|
CreateEdit,
|
||||||
},
|
},
|
||||||
|
@ -72,7 +74,7 @@ export default {
|
||||||
|
|
||||||
const namespace = await this.namespaceService.create(this.namespace)
|
const namespace = await this.namespaceService.create(this.namespace)
|
||||||
this.$store.commit('namespaces/addNamespace', namespace)
|
this.$store.commit('namespaces/addNamespace', namespace)
|
||||||
this.$message.success({message: this.$t('namespace.create.success') })
|
this.$message.success({message: this.$t('namespace.create.success')})
|
||||||
this.$router.back()
|
this.$router.back()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="notification is-info is-light has-text-centered" v-if="loading">
|
<message v-if="loading">
|
||||||
{{ $t('sharing.authenticating') }}
|
{{ $t('sharing.authenticating') }}
|
||||||
</div>
|
</message>
|
||||||
<div v-if="authenticateWithPassword" class="box">
|
<div v-if="authenticateWithPassword" class="box">
|
||||||
<p class="pb-2">
|
<p class="pb-2">
|
||||||
{{ $t('sharing.passwordRequired') }}
|
{{ $t('sharing.passwordRequired') }}
|
||||||
|
@ -25,18 +25,20 @@
|
||||||
{{ $t('user.auth.login') }}
|
{{ $t('user.auth.login') }}
|
||||||
</x-button>
|
</x-button>
|
||||||
|
|
||||||
<div class="notification is-danger mt-4" v-if="errorMessage !== ''">
|
<message variant="danger" class="mt-4" v-if="errorMessage !== ''">
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</div>
|
</message>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapGetters} from 'vuex'
|
import {mapGetters} from 'vuex'
|
||||||
|
import Message from '@/components/misc/message'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'LinkSharingAuth',
|
name: 'LinkSharingAuth',
|
||||||
|
components: {Message},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: true,
|
loading: true,
|
||||||
|
@ -72,7 +74,7 @@ export default {
|
||||||
password: this.password,
|
password: this.password,
|
||||||
})
|
})
|
||||||
this.$router.push({name: 'list.list', params: {listId: r.list_id}})
|
this.$router.push({name: 'list.list', params: {listId: r.list_id}})
|
||||||
} catch(e) {
|
} 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
|
||||||
|
|
|
@ -486,7 +486,6 @@ export default {
|
||||||
taskColor: '',
|
taskColor: '',
|
||||||
|
|
||||||
showDeleteModal: false,
|
showDeleteModal: false,
|
||||||
descriptionChanged: false,
|
|
||||||
// Used to avoid flashing of empty elements if the task content is not yet loaded.
|
// Used to avoid flashing of empty elements if the task content is not yet loaded.
|
||||||
visible: false,
|
visible: false,
|
||||||
|
|
||||||
|
@ -679,13 +678,6 @@ export default {
|
||||||
this.saveTask(true, this.toggleTaskDone)
|
this.saveTask(true, this.toggleTaskDone)
|
||||||
},
|
},
|
||||||
|
|
||||||
setDescriptionChanged(e) {
|
|
||||||
if (e.key === 'Enter' || e.key === 'Control') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.descriptionChanged = true
|
|
||||||
},
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
<div>
|
<div>
|
||||||
<h2 class="title has-text-centered">Login</h2>
|
<h2 class="title has-text-centered">Login</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="notification is-success has-text-centered" v-if="confirmedEmailSuccess">
|
<message variant="success" class="has-text-centered" v-if="confirmedEmailSuccess">
|
||||||
{{ $t('user.auth.confirmEmailSuccess') }}
|
{{ $t('user.auth.confirmEmailSuccess') }}
|
||||||
</div>
|
</message>
|
||||||
<api-config @foundApi="hasApiUrl = true"/>
|
<api-config @foundApi="hasApiUrl = true"/>
|
||||||
<form @submit.prevent="submit" id="loginform" v-if="hasApiUrl && localAuthEnabled">
|
<form @submit.prevent="submit" id="loginform" v-if="hasApiUrl && localAuthEnabled">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -78,9 +78,9 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="notification is-danger" v-if="errorMessage">
|
<message variant="danger" v-if="errorMessage">
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</div>
|
</message>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -110,11 +110,13 @@ import {LOADING} from '@/store/mutation-types'
|
||||||
import legal from '../../components/misc/legal'
|
import legal from '../../components/misc/legal'
|
||||||
import ApiConfig from '@/components/misc/api-config.vue'
|
import ApiConfig from '@/components/misc/api-config.vue'
|
||||||
import {getErrorText} from '@/message'
|
import {getErrorText} from '@/message'
|
||||||
|
import Message from '@/components/misc/message'
|
||||||
import {redirectToProvider} from '../../helpers/redirectToProvider'
|
import {redirectToProvider} from '../../helpers/redirectToProvider'
|
||||||
import {getLastVisited, clearLastVisited} from '../../helpers/saveLastVisited'
|
import {getLastVisited, clearLastVisited} from '../../helpers/saveLastVisited'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
Message,
|
||||||
ApiConfig,
|
ApiConfig,
|
||||||
legal,
|
legal,
|
||||||
},
|
},
|
||||||
|
@ -149,7 +151,7 @@ export default {
|
||||||
const last = getLastVisited()
|
const last = getLastVisited()
|
||||||
if (last !== null) {
|
if (last !== null) {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: last.name,
|
name: last.name,
|
||||||
params: last.params,
|
params: last.params,
|
||||||
})
|
})
|
||||||
clearLastVisited()
|
clearLastVisited()
|
||||||
|
@ -206,7 +208,7 @@ export default {
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('auth/login', credentials)
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="notification is-danger" v-if="errorMessage">
|
<message variant="danger" v-if="errorMessage">
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</div>
|
</message>
|
||||||
<div class="notification is-info" v-if="loading">
|
<message v-if="loading">
|
||||||
{{ $t('user.auth.authenticating') }}
|
{{ $t('user.auth.authenticating') }}
|
||||||
</div>
|
</message>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -14,10 +14,12 @@ import {mapState} from 'vuex'
|
||||||
|
|
||||||
import {LOADING} from '@/store/mutation-types'
|
import {LOADING} from '@/store/mutation-types'
|
||||||
import {getErrorText} from '@/message'
|
import {getErrorText} from '@/message'
|
||||||
|
import Message from '@/components/misc/message'
|
||||||
import {clearLastVisited, getLastVisited} from '../../helpers/saveLastVisited'
|
import {clearLastVisited, getLastVisited} from '../../helpers/saveLastVisited'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Auth',
|
name: 'Auth',
|
||||||
|
components: {Message},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
|
|
|
@ -45,37 +45,37 @@
|
||||||
</x-button>
|
</x-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="notification is-info" v-if="this.passwordResetService.loading">
|
<message v-if="this.passwordResetService.loading">
|
||||||
{{ $t('misc.loading') }}
|
{{ $t('misc.loading') }}
|
||||||
</div>
|
</message>
|
||||||
<div class="notification is-danger" v-if="errorMsg">
|
<message v-if="errorMsg">
|
||||||
{{ errorMsg }}
|
{{ errorMsg }}
|
||||||
</div>
|
</message>
|
||||||
</form>
|
</form>
|
||||||
<div class="has-text-centered" v-if="successMessage">
|
<div class="has-text-centered" v-if="successMessage">
|
||||||
<div class="notification is-success">
|
<message variant="success">
|
||||||
{{ successMessage }}
|
{{ successMessage }}
|
||||||
</div>
|
</message>
|
||||||
<x-button :to="{ name: 'user.login' }">
|
<x-button :to="{ name: 'user.login' }">
|
||||||
{{ $t('user.auth.login') }}
|
{{ $t('user.auth.login') }}
|
||||||
</x-button>
|
</x-button>
|
||||||
</div>
|
</div>
|
||||||
<Legal />
|
<Legal/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {ref, reactive} from 'vue'
|
import {ref, reactive} from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
import Legal from '@/components/misc/legal'
|
import Legal from '@/components/misc/legal'
|
||||||
|
|
||||||
import PasswordResetModel from '@/models/passwordReset'
|
import PasswordResetModel from '@/models/passwordReset'
|
||||||
import PasswordResetService from '@/services/passwordReset'
|
import PasswordResetService from '@/services/passwordReset'
|
||||||
import { useTitle } from '@/composables/useTitle'
|
import {useTitle} from '@/composables/useTitle'
|
||||||
|
import Message from '@/components/misc/message'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const {t} = useI18n()
|
||||||
useTitle(() => t('user.auth.resetPassword'))
|
useTitle(() => t('user.auth.resetPassword'))
|
||||||
|
|
||||||
const credentials = reactive({
|
const credentials = reactive({
|
||||||
|
@ -97,10 +97,10 @@ async function submit() {
|
||||||
|
|
||||||
const passwordReset = new PasswordResetModel({newPassword: credentials.password})
|
const passwordReset = new PasswordResetModel({newPassword: credentials.password})
|
||||||
try {
|
try {
|
||||||
const { message } = passwordResetService.resetPassword(passwordReset)
|
const {message} = passwordResetService.resetPassword(passwordReset)
|
||||||
successMessage.value = message
|
successMessage.value = message
|
||||||
localStorage.removeItem('passwordResetToken')
|
localStorage.removeItem('passwordResetToken')
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
errorMsg.value = e.response.data.message
|
errorMsg.value = e.response.data.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,12 +83,12 @@
|
||||||
</x-button>
|
</x-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="notification is-info" v-if="loading">
|
<message v-if="loading">
|
||||||
{{ $t('misc.loading') }}
|
{{ $t('misc.loading') }}
|
||||||
</div>
|
</message>
|
||||||
<div class="notification is-danger" v-if="errorMessage !== ''">
|
<message variant="danger" v-if="errorMessage !== ''">
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</div>
|
</message>
|
||||||
</form>
|
</form>
|
||||||
<legal/>
|
<legal/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -97,13 +97,13 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {ref, reactive, toRaw, computed, onBeforeMount} from 'vue'
|
import {ref, reactive, toRaw, computed, onBeforeMount} from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { store } from '@/store'
|
import {store} from '@/store'
|
||||||
import { useTitle } from '@/composables/useTitle'
|
import {useTitle} from '@/composables/useTitle'
|
||||||
|
|
||||||
import Legal from '@/components/misc/legal'
|
import Legal from '@/components/misc/legal'
|
||||||
|
import Message from '@/components/misc/message'
|
||||||
|
|
||||||
// FIXME: use the `beforeEnter` hook of vue-router
|
// FIXME: use the `beforeEnter` hook of vue-router
|
||||||
// Check if the user is already logged in, if so, redirect them to the homepage
|
// Check if the user is already logged in, if so, redirect them to the homepage
|
||||||
|
@ -113,7 +113,7 @@ onBeforeMount(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const { t } = useI18n()
|
const {t} = useI18n()
|
||||||
useTitle(() => t('user.auth.register'))
|
useTitle(() => t('user.auth.register'))
|
||||||
|
|
||||||
const credentials = reactive({
|
const credentials = reactive({
|
||||||
|
@ -137,7 +137,7 @@ async function submit() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await store.dispatch('auth/register', toRaw(credentials))
|
await store.dispatch('auth/register', toRaw(credentials))
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
errorMessage.value = e.message
|
errorMessage.value = e.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,14 +31,14 @@
|
||||||
</x-button>
|
</x-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="notification is-danger" v-if="errorMsg">
|
<message variant="danger" v-if="errorMsg">
|
||||||
{{ errorMsg }}
|
{{ errorMsg }}
|
||||||
</div>
|
</message>
|
||||||
</form>
|
</form>
|
||||||
<div class="has-text-centered" v-if="isSuccess">
|
<div class="has-text-centered" v-if="isSuccess">
|
||||||
<div class="notification is-success">
|
<message variant="success">
|
||||||
{{ $t('user.auth.resetPasswordSuccess') }}
|
{{ $t('user.auth.resetPasswordSuccess') }}
|
||||||
</div>
|
</message>
|
||||||
<x-button :to="{ name: 'user.login' }">
|
<x-button :to="{ name: 'user.login' }">
|
||||||
{{ $t('user.auth.login') }}
|
{{ $t('user.auth.login') }}
|
||||||
</x-button>
|
</x-button>
|
||||||
|
@ -57,6 +57,7 @@ import Legal from '@/components/misc/legal'
|
||||||
import PasswordResetModel from '@/models/passwordReset'
|
import PasswordResetModel from '@/models/passwordReset'
|
||||||
import PasswordResetService from '@/services/passwordReset'
|
import PasswordResetService from '@/services/passwordReset'
|
||||||
import { useTitle } from '@/composables/useTitle'
|
import { useTitle } from '@/composables/useTitle'
|
||||||
|
import Message from '@/components/misc/message'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
useTitle(() => t('user.auth.resetPassword'))
|
useTitle(() => t('user.auth.resetPassword'))
|
||||||
|
|
Loading…
Reference in New Issue