Merge branch 'main' into feature/date-math
This commit is contained in:
commit
0b6a74d11e
@ -120,7 +120,7 @@ steps:
|
||||
from_secret: cypress_project_key
|
||||
commands:
|
||||
- sed -i 's/localhost/api/g' dist/index.html
|
||||
- yarn serve:dist & npx wait-on http://localhost:5000
|
||||
- yarn serve:dist & npx wait-on http://localhost:4173
|
||||
- yarn test:frontend --browser chrome --record
|
||||
depends_on:
|
||||
- dependencies
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:5000",
|
||||
"baseUrl": "http://localhost:4173",
|
||||
"env": {
|
||||
"API_URL": "http://localhost:3456/api/v1",
|
||||
"TEST_SECRET": "averyLongSecretToSe33dtheDB"
|
||||
|
@ -132,7 +132,7 @@ describe('List View Kanban', () => {
|
||||
cy.getSettled('.kanban .bucket .tasks .task')
|
||||
.contains(tasks[0].title)
|
||||
.first()
|
||||
.drag('.kanban .bucket:nth-child(2) .tasks .dropper')
|
||||
.drag('.kanban .bucket:nth-child(2) .tasks')
|
||||
|
||||
cy.get('.kanban .bucket:nth-child(2) .tasks')
|
||||
.should('contain', tasks[0].title)
|
||||
@ -176,7 +176,7 @@ describe('List View Kanban', () => {
|
||||
.click()
|
||||
|
||||
cy.get('.task-view .action-buttons .button', { timeout: 3000 })
|
||||
.contains('Move task')
|
||||
.contains('Move')
|
||||
.click()
|
||||
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
|
||||
.type(`${lists[1].title}{enter}`)
|
||||
|
@ -210,7 +210,7 @@ describe('Task', () => {
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Move task')
|
||||
.contains('Move')
|
||||
.click()
|
||||
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
|
||||
.type(`${lists[1].title}{enter}`)
|
||||
@ -237,7 +237,7 @@ describe('Task', () => {
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.should('be.visible')
|
||||
.contains('Delete task')
|
||||
.contains('Delete')
|
||||
.click()
|
||||
cy.get('.modal-mask .modal-container .modal-content .header')
|
||||
.should('contain', 'Delete this task')
|
||||
@ -317,7 +317,7 @@ describe('Task', () => {
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Add labels')
|
||||
.contains('Add Labels')
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.get('.task-view .details.labels-list .multiselect input')
|
||||
@ -344,7 +344,7 @@ describe('Task', () => {
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Add labels')
|
||||
.contains('Add Labels')
|
||||
.click()
|
||||
cy.get('.task-view .details.labels-list .multiselect input')
|
||||
.type(labels[0].title)
|
||||
|
@ -6,7 +6,7 @@ describe('Log out', () => {
|
||||
|
||||
cy.get('.navbar .user .username')
|
||||
.click()
|
||||
cy.get('.navbar .user .dropdown-menu a.dropdown-item')
|
||||
cy.get('.navbar .user .dropdown-menu .dropdown-item')
|
||||
.contains('Logout')
|
||||
.click()
|
||||
|
||||
|
63
package.json
63
package.json
@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"serve": "vite",
|
||||
"serve:dist-dev": "node scripts/serve-dist.js",
|
||||
"serve:dist": "vite preview",
|
||||
"serve:dist": "vite preview --port 4173",
|
||||
"build": "vite build && workbox copyLibraries dist/",
|
||||
"build:modern-only": "BUILD_MODERN_ONLY=true vite build && workbox copyLibraries dist/",
|
||||
"build:dev": "vite build -m development --outDir dist-dev/",
|
||||
@ -20,18 +20,18 @@
|
||||
"dependencies": {
|
||||
"@github/hotkey": "2.0.0",
|
||||
"@kyvg/vue3-notification": "2.3.4",
|
||||
"@sentry/tracing": "6.17.4",
|
||||
"@sentry/vue": "6.17.4",
|
||||
"@sentry/tracing": "6.17.9",
|
||||
"@sentry/vue": "6.17.9",
|
||||
"@types/is-touch-device": "1.0.0",
|
||||
"@vue/compat": "3.2.29",
|
||||
"@vueuse/core": "7.5.5",
|
||||
"@vueuse/router": "7.5.5",
|
||||
"@vue/compat": "3.2.31",
|
||||
"@vueuse/core": "7.6.2",
|
||||
"@vueuse/router": "7.6.2",
|
||||
"bulma-css-variables": "0.9.33",
|
||||
"camel-case": "4.1.2",
|
||||
"codemirror": "5.65.1",
|
||||
"copy-to-clipboard": "3.3.1",
|
||||
"date-fns": "2.28.0",
|
||||
"dompurify": "2.3.5",
|
||||
"dompurify": "2.3.6",
|
||||
"easymde": "2.16.1",
|
||||
"flatpickr": "4.6.9",
|
||||
"flexsearch": "0.7.21",
|
||||
@ -44,8 +44,8 @@
|
||||
"snake-case": "3.0.4",
|
||||
"ufo": "0.7.10",
|
||||
"v-tooltip": "4.0.0-beta.17",
|
||||
"vue": "3.2.29",
|
||||
"vue-advanced-cropper": "2.8.0",
|
||||
"vue": "3.2.31",
|
||||
"vue-advanced-cropper": "2.8.1",
|
||||
"vue-drag-resize": "2.0.3",
|
||||
"vue-flatpickr-component": "9.0.5",
|
||||
"vue-i18n": "9.2.0-beta.30",
|
||||
@ -56,41 +56,40 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@4tw/cypress-drag-drop": "2.1.0",
|
||||
"@faker-js/faker": "6.0.0-alpha.5",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||
"@faker-js/faker": "6.0.0-alpha.7",
|
||||
"@fortawesome/fontawesome-svg-core": "1.3.0",
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||
"@fortawesome/vue-fontawesome": "3.0.0-5",
|
||||
"@types/flexsearch": "0.7.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.10.2",
|
||||
"@typescript-eslint/parser": "5.10.2",
|
||||
"@vitejs/plugin-legacy": "1.6.4",
|
||||
"@vitejs/plugin-vue": "2.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.12.0",
|
||||
"@typescript-eslint/parser": "5.12.0",
|
||||
"@vitejs/plugin-legacy": "1.7.1",
|
||||
"@vitejs/plugin-vue": "2.2.2",
|
||||
"@vue/eslint-config-typescript": "10.0.0",
|
||||
"autoprefixer": "10.4.2",
|
||||
"axios": "0.25.0",
|
||||
"browserslist": "4.19.1",
|
||||
"caniuse-lite": "1.0.30001307",
|
||||
"cypress": "9.4.1",
|
||||
"esbuild": "0.14.18",
|
||||
"eslint": "8.8.0",
|
||||
"axios": "0.26.0",
|
||||
"browserslist": "4.19.3",
|
||||
"caniuse-lite": "1.0.30001312",
|
||||
"cypress": "9.5.0",
|
||||
"esbuild": "0.14.23",
|
||||
"eslint": "8.9.0",
|
||||
"eslint-plugin-vue": "8.4.1",
|
||||
"express": "4.17.2",
|
||||
"happy-dom": "2.31.1",
|
||||
"netlify-cli": "8.15.0",
|
||||
"express": "4.17.3",
|
||||
"happy-dom": "2.39.1",
|
||||
"netlify-cli": "8.16.1",
|
||||
"postcss": "8.4.6",
|
||||
"postcss-preset-env": "7.3.1",
|
||||
"rollup": "2.67.0",
|
||||
"postcss-preset-env": "7.4.1",
|
||||
"rollup": "2.67.3",
|
||||
"rollup-plugin-visualizer": "5.5.4",
|
||||
"sass": "1.49.7",
|
||||
"slugify": "1.6.5",
|
||||
"sass": "1.49.8",
|
||||
"typescript": "4.5.5",
|
||||
"vite": "2.7.13",
|
||||
"vite": "2.8.4",
|
||||
"vite-plugin-pwa": "0.11.13",
|
||||
"vite-svg-loader": "3.1.2",
|
||||
"vitest": "0.2.7",
|
||||
"vue-tsc": "0.31.1",
|
||||
"wait-on": "6.0.0",
|
||||
"vitest": "0.4.2",
|
||||
"vue-tsc": "0.31.4",
|
||||
"wait-on": "6.0.1",
|
||||
"workbox-cli": "6.4.2"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
@ -1,20 +1,24 @@
|
||||
const slugify = require('slugify')
|
||||
const {exec} = require('child_process')
|
||||
const axios = require('axios')
|
||||
|
||||
const BOT_USER_ID = 513
|
||||
const giteaToken = process.env.GITEA_TOKEN
|
||||
const siteId = process.env.NETLIFY_SITE_ID
|
||||
const branchSlug = slugify(process.env.DRONE_SOURCE_BRANCH)
|
||||
const branchSlug = String(process.env.DRONE_SOURCE_BRANCH)
|
||||
.trim()
|
||||
.normalize('NFKD')
|
||||
.toLowerCase()
|
||||
.replace(/[.\s/]/g, '-')
|
||||
.replace(/[^A-Za-z\d-]/g, '')
|
||||
const prNumber = process.env.DRONE_PULL_REQUEST
|
||||
|
||||
const prIssueCommentsUrl = `https://kolaente.dev/api/v1/repos/vikunja/frontend/issues/${prNumber}/comments`
|
||||
const alias = `${prNumber}-${branchSlug}`
|
||||
const alias = `${prNumber}-${branchSlug}`.substring(0,37)
|
||||
const fullPreviewUrl = `https://${alias}--vikunja-frontend-preview.netlify.app`
|
||||
|
||||
const promiseExec = cmd => {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(cmd, (error, stdout, stderr) => {
|
||||
exec(cmd, (error, stdout) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
|
@ -1 +1 @@
|
||||
55ce0faaa2c1919341617ccfaeccbb6029ac12107964ff488985cff13dd952f1a991df3ab0d4b0705deb761e508e6434 ./scripts/deploy-preview-netlify.js
|
||||
bb46342a0a08105b340ba7976cff9d80ef89901120ec0639669caa70bb7d2dbc43e78b1f635a7654ab2456e8358c98a4 ./scripts/deploy-preview-netlify.js
|
||||
|
@ -3,7 +3,7 @@ const express = require('express')
|
||||
const app = express()
|
||||
|
||||
const p = path.join(__dirname, '..', 'dist-dev')
|
||||
const port = 5000
|
||||
const port = 4173
|
||||
|
||||
app.use(express.static(p))
|
||||
// Handle urls set by the frontend
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ready>
|
||||
<template v-if="authUser">
|
||||
<top-navigation/>
|
||||
<TheNavigation/>
|
||||
<content-auth/>
|
||||
</template>
|
||||
<content-link-share v-else-if="authLinkShare"/>
|
||||
@ -27,7 +27,7 @@ import {success} from '@/message'
|
||||
|
||||
import Notification from '@/components/misc/notification.vue'
|
||||
import KeyboardShortcuts from './components/misc/keyboard-shortcuts/index.vue'
|
||||
import TopNavigation from './components/home/topNavigation.vue'
|
||||
import TheNavigation from '@/components/home/TheNavigation.vue'
|
||||
import ContentAuth from './components/home/contentAuth.vue'
|
||||
import ContentLinkShare from './components/home/contentLinkShare.vue'
|
||||
import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
|
||||
|
@ -69,10 +69,10 @@ watchEffect(() => {
|
||||
}
|
||||
|
||||
// if there is a href we assume the user wants an external link via a link element
|
||||
// we also set the attribute rel to "noopener" but make it possible to overwrite this by the user.
|
||||
// we also set a predefined value for the attribute rel, but make it possible to overwrite this by the user.
|
||||
if ('href' in attrs) {
|
||||
nodeName = 'a'
|
||||
bindings = {rel: 'noopener'}
|
||||
bindings = {rel: 'noreferrer noopener nofollow'}
|
||||
}
|
||||
|
||||
componentNodeName.value = nodeName
|
||||
|
@ -1,9 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useNow } from '@vueuse/core'
|
||||
|
||||
import LogoFull from '@/assets/logo-full.svg?component'
|
||||
import LogoFullPride from '@/assets/logo-full-pride.svg?component'
|
||||
|
||||
const Logo = computed(() => new Date().getMonth() === 5 ? LogoFullPride : LogoFull)
|
||||
const now = useNow()
|
||||
const Logo = computed(() => now.value.getMonth() === 5 ? LogoFullPride : LogoFull)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
@click="$store.commit('toggleMenu')"
|
||||
<BaseButton
|
||||
class="menu-show-button"
|
||||
@click="$store.commit('toggleMenu')"
|
||||
@shortkey="() => $store.commit('toggleMenu')"
|
||||
v-shortcut="'Control+e'"
|
||||
:title="$t('keyboardShortcuts.toggleMenu')"
|
||||
@ -10,11 +9,14 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import {computed} from 'vue'
|
||||
import {store} from '@/store'
|
||||
import {useStore} from 'vuex'
|
||||
|
||||
const menuActive = computed(() => store.menuActive)
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
||||
const store = useStore()
|
||||
const menuActive = computed(() => store.state.menuActive)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -22,11 +24,6 @@ $lineWidth: 2rem;
|
||||
$size: $lineWidth + 1rem;
|
||||
|
||||
.menu-show-button {
|
||||
// FIXME: create general button component
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
|
||||
min-height: $size;
|
||||
width: $size;
|
||||
|
||||
|
@ -32,12 +32,13 @@
|
||||
</a>
|
||||
<notifications/>
|
||||
<div class="user">
|
||||
<img :src="userAvatar" alt="" class="avatar" width="40" height="40"/>
|
||||
<dropdown class="is-right" ref="usernameDropdown">
|
||||
<template #trigger>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
:shadow="false">
|
||||
:shadow="false"
|
||||
>
|
||||
<img :src="userAvatar" alt="" class="avatar" width="40" height="40"/>
|
||||
<span class="username">{{ userInfo.name !== '' ? userInfo.name : userInfo.username }}</span>
|
||||
<span class="icon is-small">
|
||||
<icon icon="chevron-down"/>
|
||||
@ -45,92 +46,96 @@
|
||||
</x-button>
|
||||
</template>
|
||||
|
||||
<router-link :to="{name: 'user.settings'}" class="dropdown-item">
|
||||
<BaseButton
|
||||
:to="{name: 'user.settings'}"
|
||||
class="dropdown-item"
|
||||
>
|
||||
{{ $t('user.settings.title') }}
|
||||
</router-link>
|
||||
<a
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="imprintUrl"
|
||||
:href="imprintUrl"
|
||||
class="dropdown-item"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener nofollow"
|
||||
v-if="imprintUrl">
|
||||
>
|
||||
{{ $t('navigation.imprint') }}
|
||||
</a>
|
||||
<a
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="privacyPolicyUrl"
|
||||
:href="privacyPolicyUrl"
|
||||
class="dropdown-item"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener nofollow"
|
||||
v-if="privacyPolicyUrl">
|
||||
>
|
||||
{{ $t('navigation.privacy') }}
|
||||
</a>
|
||||
<a @click="$store.commit('keyboardShortcutsActive', true)" class="dropdown-item">
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
@click="$store.commit('keyboardShortcutsActive', true)"
|
||||
class="dropdown-item"
|
||||
>
|
||||
{{ $t('keyboardShortcuts.title') }}
|
||||
</a>
|
||||
<router-link :to="{name: 'about'}" class="dropdown-item">
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
:to="{name: 'about'}"
|
||||
class="dropdown-item"
|
||||
>
|
||||
{{ $t('about.title') }}
|
||||
</router-link>
|
||||
<a @click="logout()" class="dropdown-item">
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
@click="logout()"
|
||||
class="dropdown-item"
|
||||
>
|
||||
{{ $t('user.auth.logout') }}
|
||||
</a>
|
||||
</BaseButton>
|
||||
</dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import {CURRENT_LIST, QUICK_ACTIONS_ACTIVE} from '@/store/mutation-types'
|
||||
<script setup langs="ts">
|
||||
import {ref, computed, onMounted, nextTick} from 'vue'
|
||||
import {useStore} from 'vuex'
|
||||
import {useRouter} from 'vue-router'
|
||||
|
||||
import {QUICK_ACTIONS_ACTIVE} from '@/store/mutation-types'
|
||||
import Rights from '@/models/constants/rights.json'
|
||||
|
||||
import Update from '@/components/home/update.vue'
|
||||
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
|
||||
import Dropdown from '@/components/misc/dropdown.vue'
|
||||
import Notifications from '@/components/notifications/notifications.vue'
|
||||
import Logo from '@/components/home/Logo.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import MenuButton from '@/components/home/MenuButton.vue'
|
||||
|
||||
export default {
|
||||
name: 'topNavigation',
|
||||
components: {
|
||||
Notifications,
|
||||
Dropdown,
|
||||
ListSettingsDropdown,
|
||||
Update,
|
||||
Logo,
|
||||
MenuButton,
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
userInfo: state => state.auth.info,
|
||||
userAvatar: state => state.auth.avatarUrl,
|
||||
userAuthenticated: state => state.auth.authenticated,
|
||||
currentList: CURRENT_LIST,
|
||||
background: 'background',
|
||||
imprintUrl: state => state.config.legal.imprintUrl,
|
||||
privacyPolicyUrl: state => state.config.legal.privacyPolicyUrl,
|
||||
canWriteCurrentList: state => state.currentList.maxRight > Rights.READ,
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (typeof this.$refs.usernameDropdown === 'undefined' || typeof this.$refs.listTitle === 'undefined') {
|
||||
return
|
||||
}
|
||||
const store = useStore()
|
||||
|
||||
const usernameWidth = this.$refs.usernameDropdown.$el.clientWidth
|
||||
this.$refs.listTitle.style.setProperty('--nav-username-width', `${usernameWidth}px`)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
logout() {
|
||||
this.$store.dispatch('auth/logout')
|
||||
this.$router.push({name: 'user.login'})
|
||||
},
|
||||
openQuickActions() {
|
||||
this.$store.commit(QUICK_ACTIONS_ACTIVE, true)
|
||||
},
|
||||
},
|
||||
const userInfo = computed(() => store.state.auth.info)
|
||||
const userAvatar = computed(() => store.state.auth.avatarUrl)
|
||||
const currentList = computed(() => store.state.currentList)
|
||||
const background = computed(() => store.state.background)
|
||||
const imprintUrl = computed(() => store.state.config.legal.imprintUrl)
|
||||
const privacyPolicyUrl = computed(() => store.state.config.legal.privacyPolicyUrl)
|
||||
const canWriteCurrentList = computed(() => store.state.currentList.maxRight > Rights.READ)
|
||||
|
||||
const usernameDropdown = ref()
|
||||
const listTitle = ref()
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
if (typeof usernameDropdown.value === 'undefined' || typeof listTitle.value === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
const usernameWidth = usernameDropdown.value.$el.clientWidth
|
||||
listTitle.value.style.setProperty('--nav-username-width', `${usernameWidth}px`)
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
function logout() {
|
||||
store.dispatch('auth/logout')
|
||||
router.push({name: 'user.login'})
|
||||
}
|
||||
|
||||
function openQuickActions() {
|
||||
store.commit(QUICK_ACTIONS_ACTIVE, true)
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -246,6 +251,7 @@ $hamburger-menu-icon-width: 28px;
|
||||
border-radius: 100%;
|
||||
vertical-align: middle;
|
||||
height: 40px;
|
||||
margin-right: var(--button-padding-horizontal);
|
||||
}
|
||||
|
||||
:deep(.dropdown-trigger .button) {
|
@ -66,7 +66,7 @@ const showIconOnly = computed(() => props.icon !== '' && typeof slots.default ==
|
||||
text-transform: uppercase;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
height: $button-height;
|
||||
min-height: $button-height;
|
||||
box-shadow: var(--shadow-sm);
|
||||
display: inline-flex;
|
||||
|
||||
|
@ -39,79 +39,66 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Message from '@/components/misc/message'
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, watch} from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {parseURL} from 'ufo'
|
||||
|
||||
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
|
||||
import {success} from '@/message'
|
||||
|
||||
export default {
|
||||
name: 'apiConfig',
|
||||
components: {
|
||||
Message,
|
||||
import Message from '@/components/misc/message.vue'
|
||||
|
||||
const props = defineProps({
|
||||
configureOpen: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
configureApi: false,
|
||||
apiUrl: window.API_URL,
|
||||
errorMsg: '',
|
||||
successMsg: '',
|
||||
})
|
||||
const emit = defineEmits(['foundApi'])
|
||||
|
||||
const apiUrl = ref(window.API_URL)
|
||||
const configureApi = ref(apiUrl.value === '')
|
||||
|
||||
const apiDomain = computed(() => parseURL(apiUrl.value).host || parseURL(window.location.href).host)
|
||||
|
||||
|
||||
watch(() => props.configureOpen, (value) => {
|
||||
configureApi.value = value
|
||||
}, { immediate: true })
|
||||
|
||||
|
||||
const {t} = useI18n()
|
||||
|
||||
const errorMsg = ref('')
|
||||
const successMsg = ref('')
|
||||
async function setApiUrl() {
|
||||
if (apiUrl.value === '') {
|
||||
// Don't try to check and set an empty url
|
||||
errorMsg.value = t('apiConfig.urlRequired')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const url = await checkAndSetApiUrl(apiUrl.value)
|
||||
|
||||
if (url === '') {
|
||||
// If the config setter function could not figure out a url
|
||||
throw new Error('URL cannot be empty.')
|
||||
}
|
||||
},
|
||||
emits: ['foundApi'],
|
||||
created() {
|
||||
if (this.apiUrl === '') {
|
||||
this.configureApi = true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
apiDomain() {
|
||||
return parseURL(this.apiUrl).host || parseURL(window.location.href).host
|
||||
},
|
||||
},
|
||||
props: {
|
||||
configureOpen: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
configureOpen: {
|
||||
handler(value) {
|
||||
this.configureApi = value
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async setApiUrl() {
|
||||
if (this.apiUrl === '') {
|
||||
// Don't try to check and set an empty url
|
||||
this.errorMsg = this.$t('apiConfig.urlRequired')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const url = await checkAndSetApiUrl(this.apiUrl)
|
||||
|
||||
if (url === '') {
|
||||
// If the config setter function could not figure out a url
|
||||
throw new Error('URL cannot be empty.')
|
||||
}
|
||||
|
||||
// Set it + save it to local storage to save us the hoops
|
||||
this.errorMsg = ''
|
||||
this.$message.success({message: this.$t('apiConfig.success', {domain: this.apiDomain})})
|
||||
this.configureApi = false
|
||||
this.apiUrl = url
|
||||
this.$emit('foundApi', this.apiUrl)
|
||||
} catch (e) {
|
||||
// Still not found, url is still invalid
|
||||
this.successMsg = ''
|
||||
this.errorMsg = this.$t('apiConfig.error', {domain: this.apiDomain})
|
||||
}
|
||||
},
|
||||
},
|
||||
// Set it + save it to local storage to save us the hoops
|
||||
errorMsg.value = ''
|
||||
apiUrl.value = url
|
||||
success({message: t('apiConfig.success', {domain: apiDomain.value})})
|
||||
configureApi.value = false
|
||||
emit('foundApi', apiUrl.value)
|
||||
} catch (e) {
|
||||
// Still not found, url is still invalid
|
||||
successMsg.value = ''
|
||||
errorMsg.value = t('apiConfig.error', {domain: apiDomain.value})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
||||
}"
|
||||
>
|
||||
<BaseButton
|
||||
@click="emit('close')"
|
||||
@click="$emit('close')"
|
||||
class="close"
|
||||
>
|
||||
<icon icon="times"/>
|
||||
|
47
src/components/tasks/partials/createdUpdated.vue
Normal file
47
src/components/tasks/partials/createdUpdated.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<p class="created">
|
||||
<time :datetime="formatISO(task.created)" v-tooltip="formatDate(task.created)">
|
||||
<i18n-t keypath="task.detail.created">
|
||||
<span>{{ formatDateSince(task.created) }}</span>
|
||||
{{ task.createdBy.getDisplayName() }}
|
||||
</i18n-t>
|
||||
</time>
|
||||
<template v-if="+new Date(task.created) !== +new Date(task.updated)">
|
||||
<br/>
|
||||
<!-- Computed properties to show the actual date every time it gets updated -->
|
||||
<time :datetime="formatISO(task.updated)" v-tooltip="updatedFormatted">
|
||||
<i18n-t keypath="task.detail.updated">
|
||||
<span>{{ updatedSince }}</span>
|
||||
</i18n-t>
|
||||
</time>
|
||||
</template>
|
||||
<template v-if="task.done">
|
||||
<br/>
|
||||
<time :datetime="formatISO(task.doneAt)" v-tooltip="doneFormatted">
|
||||
<i18n-t keypath="task.detail.doneAt">
|
||||
<span>{{ doneSince }}</span>
|
||||
</i18n-t>
|
||||
</time>
|
||||
</template>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, toRefs} from 'vue'
|
||||
import TaskModel from '@/models/task'
|
||||
import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
||||
|
||||
const props = defineProps({
|
||||
task: {
|
||||
type: TaskModel,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const {task} = toRefs(props)
|
||||
|
||||
const updatedSince = computed(() => formatDateSince(task.value.updated))
|
||||
const updatedFormatted = computed(() => formatDateLong(task.value.updated))
|
||||
const doneSince = computed(() => formatDateSince(task.value.doneAt))
|
||||
const doneFormatted = computed(() => formatDateLong(task.value.doneAt))
|
||||
</script>
|
@ -138,7 +138,6 @@ $task-background: var(--white);
|
||||
border: 3px solid transparent;
|
||||
|
||||
font-size: .9rem;
|
||||
margin: .5rem;
|
||||
padding: .4rem;
|
||||
border-radius: $radius;
|
||||
background: $task-background;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="task-relations">
|
||||
<x-button
|
||||
v-if="Object.keys(relatedTasks).length > 0"
|
||||
v-if="editEnabled && Object.keys(relatedTasks).length > 0"
|
||||
@click="showNewRelationForm = !showNewRelationForm"
|
||||
class="is-pulled-right add-task-relation-button"
|
||||
:class="{'is-active': showNewRelationForm}"
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {HTTPFactory} from '@/http-common'
|
||||
import {AuthenticatedHTTPFactory} from '@/http-common'
|
||||
import {AxiosResponse} from 'axios'
|
||||
|
||||
let savedToken: string | null = null
|
||||
@ -6,8 +6,6 @@ let savedToken: string | null = null
|
||||
/**
|
||||
* Saves a token while optionally saving it to lacal storage. This is used when viewing a link share:
|
||||
* It enables viewing multiple link shares indipendently from each in multiple tabs other without overriding any other open ones.
|
||||
* @param token
|
||||
* @param persist
|
||||
*/
|
||||
export const saveToken = (token: string, persist: boolean) => {
|
||||
savedToken = token
|
||||
@ -18,7 +16,6 @@ export const saveToken = (token: string, persist: boolean) => {
|
||||
|
||||
/**
|
||||
* Returns a saved token. If there is one saved in memory it will use that before anything else.
|
||||
* @returns {string|null}
|
||||
*/
|
||||
export const getToken = (): string | null => {
|
||||
if (savedToken !== null) {
|
||||
@ -39,16 +36,11 @@ export const removeToken = () => {
|
||||
|
||||
/**
|
||||
* Refreshes an auth token while ensuring it is updated everywhere.
|
||||
* @returns {Promise<AxiosResponse<any>>}
|
||||
*/
|
||||
export async function refreshToken(persist: boolean): Promise<AxiosResponse> {
|
||||
const HTTP = HTTPFactory()
|
||||
const HTTP = AuthenticatedHTTPFactory()
|
||||
try {
|
||||
const response = await HTTP.post('user/token', null, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${getToken()}`,
|
||||
},
|
||||
})
|
||||
const response = await HTTP.post('user/token')
|
||||
saveToken(response.data.token, persist)
|
||||
return response
|
||||
|
||||
|
@ -1,7 +1,18 @@
|
||||
import axios from 'axios'
|
||||
import {getToken} from '@/helpers/auth'
|
||||
|
||||
export const HTTPFactory = () => {
|
||||
export function HTTPFactory() {
|
||||
return axios.create({
|
||||
baseURL: window.API_URL,
|
||||
})
|
||||
}
|
||||
|
||||
export function AuthenticatedHTTPFactory(token = getToken()) {
|
||||
return axios.create({
|
||||
baseURL: window.API_URL,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import {createI18n} from 'vue-i18n'
|
||||
import langEN from './lang/en.json'
|
||||
|
||||
export const i18n = createI18n({
|
||||
@ -19,6 +19,9 @@ export const availableLanguages = {
|
||||
'vi-VN': 'Tiếng Việt',
|
||||
'it-IT': 'Italiano',
|
||||
'cs-CZ': 'Čeština',
|
||||
'pl-PL': 'Polski',
|
||||
'nl-NL': 'Nederlands',
|
||||
'pt-PT': 'Português',
|
||||
}
|
||||
|
||||
const loadedLanguages = ['en'] // our default language that is preloaded
|
||||
@ -30,10 +33,10 @@ const setI18nLanguage = lang => {
|
||||
}
|
||||
|
||||
export const loadLanguageAsync = lang => {
|
||||
if(!lang) {
|
||||
return
|
||||
if (!lang) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
// If the same language
|
||||
i18n.global.locale === lang ||
|
||||
|
@ -31,10 +31,9 @@
|
||||
"username": "Uživatelské jméno",
|
||||
"usernameEmail": "Uživatelské jméno nebo e-mail",
|
||||
"usernamePlaceholder": "např. Jarmil",
|
||||
"email": "E-mailová adresa",
|
||||
"email": "Email address",
|
||||
"emailPlaceholder": "např. jarmil{'@'}vikunja.io",
|
||||
"password": "Heslo",
|
||||
"passwordRepeat": "Zopakovat heslo",
|
||||
"passwordPlaceholder": "např. • • • • • • • •",
|
||||
"forgotPassword": "Zapomenuté heslo?",
|
||||
"resetPassword": "Obnovit heslo",
|
||||
@ -45,12 +44,20 @@
|
||||
"totpTitle": "Kód dvoufaktorového ověření",
|
||||
"totpPlaceholder": "např. 123456",
|
||||
"login": "Přihlásit se",
|
||||
"register": "Registrovat",
|
||||
"createAccount": "Create account",
|
||||
"loginWith": "Přihlásit se pomocí {provider}",
|
||||
"authenticating": "Ověřování…",
|
||||
"openIdStateError": "Stav neodpovídá, odmítám pokračovat!",
|
||||
"openIdGeneralError": "Došlo k chybě při ověřování proti třetí straně.",
|
||||
"logout": "Odhlásit se"
|
||||
"logout": "Odhlásit se",
|
||||
"emailInvalid": "Please enter a valid email address.",
|
||||
"usernameRequired": "Please provide a username.",
|
||||
"passwordRequired": "Please provide a password.",
|
||||
"showPassword": "Show the password",
|
||||
"hidePassword": "Hide the password",
|
||||
"noAccountYet": "Don't have an account yet?",
|
||||
"alreadyHaveAnAccount": "Already have an account?",
|
||||
"remember": "Stay logged in"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Nastavení",
|
||||
@ -61,7 +68,7 @@
|
||||
"currentPasswordPlaceholder": "Vaše současné heslo",
|
||||
"passwordsDontMatch": "Nové heslo se neshoduje s potvrzením hesla.",
|
||||
"passwordUpdateSuccess": "Heslo bylo úspěšně změněno.",
|
||||
"updateEmailTitle": "Aktualizovat Vaši e-mailovou adresu",
|
||||
"updateEmailTitle": "Update Your Email Address",
|
||||
"updateEmailNew": "Nová e-mailová adresa",
|
||||
"updateEmailSuccess": "Vaše e-mailová adresa byla úspěšně aktualizována. Poslali jsme vám odkaz pro její potvrzení.",
|
||||
"general": {
|
||||
@ -78,7 +85,8 @@
|
||||
"weekStartSunday": "Neděle",
|
||||
"weekStartMonday": "Pondělí",
|
||||
"language": "Jazyk",
|
||||
"defaultList": "Výchozí seznam"
|
||||
"defaultList": "Výchozí seznam",
|
||||
"timezone": "Time Zone"
|
||||
},
|
||||
"totp": {
|
||||
"title": "Dvoufaktorové ověření",
|
||||
@ -327,6 +335,7 @@
|
||||
"archiveText": "Nebudete moci upravovat tento jmenný prostor ani vytvářet nové seznamy, dokud jej neodarchivujete. Všechny seznamy v tomto prostoru budou také archivovány.",
|
||||
"unarchiveText": "Budete moci vytvářet nové úkoly nebo je upravovat.",
|
||||
"success": "Prostor byl úspěšně archivován.",
|
||||
"unarchiveSuccess": "The namespace was successfully un-archived.",
|
||||
"description": "Pokud je prostor archivován, nelze vytvořit nové seznamy nebo je upravit."
|
||||
},
|
||||
"delete": {
|
||||
@ -376,7 +385,7 @@
|
||||
"showDoneTasks": "Zobrazit dokončené úkoly",
|
||||
"sortAlphabetically": "Řadit podle abecedy",
|
||||
"enablePriority": "Povolit filtrování podle priority",
|
||||
"enablePercentDone": "Povolit filtrování dle dokončenosti",
|
||||
"enablePercentDone": "Enable Filter By Progress",
|
||||
"dueDateRange": "Rozsah termínu",
|
||||
"startDateRange": "Začátek období",
|
||||
"endDateRange": "Konec období",
|
||||
@ -569,7 +578,7 @@
|
||||
"endDate": "Nastavit koncové datum",
|
||||
"reminders": "Nastavit připomenutí",
|
||||
"repeatAfter": "Nastavit interval opakování",
|
||||
"percentDone": "Nastavit procenta dokončeno",
|
||||
"percentDone": "Set Progress",
|
||||
"attachments": "Přidat přílohy",
|
||||
"relatedTasks": "Přidat vztahy úkolu",
|
||||
"moveList": "Přesunout úkol",
|
||||
@ -589,7 +598,7 @@
|
||||
"dueDate": "Termín",
|
||||
"endDate": "Datum ukončení",
|
||||
"labels": "Štítky",
|
||||
"percentDone": "% Hotovo",
|
||||
"percentDone": "Progress",
|
||||
"priority": "Priorita",
|
||||
"relatedTasks": "Související úkoly",
|
||||
"reminders": "Připomínky",
|
||||
|
@ -31,10 +31,9 @@
|
||||
"username": "Anmeldename",
|
||||
"usernameEmail": "Anmeldename oder E-Mail-Adresse",
|
||||
"usernamePlaceholder": "z.B. frederick",
|
||||
"email": "Email address",
|
||||
"email": "E-Mail-Adresse",
|
||||
"emailPlaceholder": "z.B. frederic{'@'}vikunja.io",
|
||||
"password": "Passwort",
|
||||
"passwordRepeat": "Gib dein Passwort erneut ein",
|
||||
"passwordPlaceholder": "z.B. •••••••••••",
|
||||
"forgotPassword": "Passwort vergessen?",
|
||||
"resetPassword": "Setze dein Passwort zurück",
|
||||
@ -45,12 +44,20 @@
|
||||
"totpTitle": "Zwei-Faktor-Authentifizierungscode",
|
||||
"totpPlaceholder": "z.B. 123456",
|
||||
"login": "Anmelden",
|
||||
"register": "Registrieren",
|
||||
"createAccount": "Account erstellen",
|
||||
"loginWith": "Mit {provider} anmelden",
|
||||
"authenticating": "Authentifizierung…",
|
||||
"openIdStateError": "Zustand stimmt nicht überein, fahre nicht fort!",
|
||||
"openIdGeneralError": "Es ist ein Fehler bei der externen Authentisierung aufgetreten.",
|
||||
"logout": "Abmelden"
|
||||
"logout": "Abmelden",
|
||||
"emailInvalid": "Bitte gib eine gültige E-Mail-Adresse ein.",
|
||||
"usernameRequired": "Bitte gib einen Anmeldenamen ein.",
|
||||
"passwordRequired": "Bitte gib ein Passwort ein.",
|
||||
"showPassword": "Passwort anzeigen",
|
||||
"hidePassword": "Passwort verbergen",
|
||||
"noAccountYet": "Noch kein Account?",
|
||||
"alreadyHaveAnAccount": "Hast du bereits einen Account?",
|
||||
"remember": "Angemeldet bleiben"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Einstellungen",
|
||||
@ -78,7 +85,8 @@
|
||||
"weekStartSunday": "Sonntag",
|
||||
"weekStartMonday": "Montag",
|
||||
"language": "Sprache",
|
||||
"defaultList": "Standard-Liste"
|
||||
"defaultList": "Standard-Liste",
|
||||
"timezone": "Zeitzone"
|
||||
},
|
||||
"totp": {
|
||||
"title": "Zwei-Faktor-Authentifizierung",
|
||||
@ -327,6 +335,7 @@
|
||||
"archiveText": "Du kannst diesen Namespace nicht mehr bearbeiten oder neue Listen erstellen, bis du die Archivierung rückgängig machst. Das gilt auch für alle Listen in diesem Namespace.",
|
||||
"unarchiveText": "Du kannst neue Aufgaben erstellen oder diese bearbeiten.",
|
||||
"success": "Der Namespace wurde erfolgreich archiviert.",
|
||||
"unarchiveSuccess": "Der Namespace wurde erfolgreich wiederhergestellt.",
|
||||
"description": "In einem archivierten Namespace können Listen weder angelegt noch editiert werden."
|
||||
},
|
||||
"delete": {
|
||||
@ -376,7 +385,7 @@
|
||||
"showDoneTasks": "Erledigte Aufgaben anzeigen",
|
||||
"sortAlphabetically": "Alphabetisch sortieren",
|
||||
"enablePriority": "Filter nach Priorität aktivieren",
|
||||
"enablePercentDone": "Filter nach % Erledigt aktivieren",
|
||||
"enablePercentDone": "Filter nach Fortschritt aktivieren",
|
||||
"dueDateRange": "Fälligkeitsbereich",
|
||||
"startDateRange": "Startdatumsbereich",
|
||||
"endDateRange": "Enddatumsbereich",
|
||||
@ -569,7 +578,7 @@
|
||||
"endDate": "Enddatum setzen",
|
||||
"reminders": "Erinnerungen setzen",
|
||||
"repeatAfter": "Wiederholung setzen",
|
||||
"percentDone": "Prozent erledigt setzen",
|
||||
"percentDone": "Fortschritt einstellen",
|
||||
"attachments": "Anhänge hinzufügen",
|
||||
"relatedTasks": "Aufgabenbeziehungen hinzufügen",
|
||||
"moveList": "Aufgabe verschieben",
|
||||
@ -589,7 +598,7 @@
|
||||
"dueDate": "Fälligkeitsdatum",
|
||||
"endDate": "Enddatum",
|
||||
"labels": "Labels",
|
||||
"percentDone": "% erledigt",
|
||||
"percentDone": "Fortschritt",
|
||||
"priority": "Priorität",
|
||||
"relatedTasks": "Verwandte Aufgaben",
|
||||
"reminders": "Erinnerungen",
|
||||
|
@ -31,7 +31,7 @@
|
||||
"username": "Benutzernamä",
|
||||
"usernameEmail": "Benutzernamä oder E-Mail Adrässe",
|
||||
"usernamePlaceholder": "z.B. Hansruedi",
|
||||
"email": "Email address",
|
||||
"email": "E-Mail-Adresse",
|
||||
"emailPlaceholder": "z.B. frederic{'@'}vikunja.io",
|
||||
"password": "Passwort",
|
||||
"passwordPlaceholder": "z.B. •••••••••••",
|
||||
@ -44,19 +44,20 @@
|
||||
"totpTitle": "Zweifaktor Authentifizierigs Ziffere",
|
||||
"totpPlaceholder": "z.B. 123456",
|
||||
"login": "Iihlogge",
|
||||
"createAccount": "Create account",
|
||||
"createAccount": "Account erstellen",
|
||||
"loginWith": "Iihlogge mit {provider}",
|
||||
"authenticating": "Authentifiziere…",
|
||||
"openIdStateError": "Status stimmt nid überiih, ich verweigerä wiiter zmache!",
|
||||
"openIdGeneralError": "Es ist ein Fehler bei der externen Authentisierung aufgetreten.",
|
||||
"logout": "Uuslogge",
|
||||
"emailInvalid": "Please enter a valid email address.",
|
||||
"usernameRequired": "Please provide a username.",
|
||||
"passwordRequired": "Please provide a password.",
|
||||
"showPassword": "Show the password",
|
||||
"hidePassword": "Hide the password",
|
||||
"noAccountYet": "Don't have an account yet?",
|
||||
"alreadyHaveAnAccount": "Already have an account?"
|
||||
"emailInvalid": "Bitte gib eine gültige E-Mail-Adresse ein.",
|
||||
"usernameRequired": "Bitte gib einen Anmeldenamen ein.",
|
||||
"passwordRequired": "Bitte gib ein Passwort ein.",
|
||||
"showPassword": "Passwort anzeigen",
|
||||
"hidePassword": "Passwort verbergen",
|
||||
"noAccountYet": "Noch kein Account?",
|
||||
"alreadyHaveAnAccount": "Hast du bereits einen Account?",
|
||||
"remember": "Angemeldet bleiben"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Iihstellige",
|
||||
@ -67,7 +68,7 @@
|
||||
"currentPasswordPlaceholder": "Diis jetzige Passwort",
|
||||
"passwordsDontMatch": "Dis neue Passwort und siini Bestätigung stimmed nid überiih.",
|
||||
"passwordUpdateSuccess": "Dis Passwort isch erfolgriich aktualisiert wordä.",
|
||||
"updateEmailTitle": "Update Your Email Address",
|
||||
"updateEmailTitle": "Aktualisiere deine E-Mail-Adresse",
|
||||
"updateEmailNew": "Neui E-Mail Adrässä",
|
||||
"updateEmailSuccess": "Dini E-Mail Adrässä isch erfolgriich gänderet worde. Mir hend dir en Link gschickt, um si zu bestätigä.",
|
||||
"general": {
|
||||
@ -84,7 +85,8 @@
|
||||
"weekStartSunday": "Sunntig",
|
||||
"weekStartMonday": "Määntig",
|
||||
"language": "Sproch",
|
||||
"defaultList": "Standard Liste"
|
||||
"defaultList": "Standard Liste",
|
||||
"timezone": "Zeitzone"
|
||||
},
|
||||
"totp": {
|
||||
"title": "Zweifaktor Authentifizierig",
|
||||
@ -101,9 +103,9 @@
|
||||
"disableSuccess": "Zweifaktor Authentifizierig isch erfolgriich uusgschalte wore."
|
||||
},
|
||||
"caldav": {
|
||||
"title": "Caldav",
|
||||
"howTo": "Du chasch Vikunja zu Caldav Applikatione verbinde, um dini Uufgabe vo verschidene Gräät zgseh. Gib die Url i dim Client iih:",
|
||||
"more": "Meh Informatione über Caldav in Vikunja"
|
||||
"title": "CalDAV",
|
||||
"howTo": "Du chasch Vikunja zu CalDAV Applikatione verbinde, um dini Uufgabe vo verschidene Gräät zgseh. Gib die Url i dim Client iih:",
|
||||
"more": "Meh Informatione über CalDAV in Vikunja"
|
||||
},
|
||||
"avatar": {
|
||||
"title": "Herr Der Elemente",
|
||||
@ -333,6 +335,7 @@
|
||||
"archiveText": "Du hesch kei möglichkeit meh de Namensruum z'bearbeite oder neui Listene drin z'erstelle, bis du si wider ent-archiviert hesch. Das archiviert au grad alli Liste im Namensruum.",
|
||||
"unarchiveText": "Du chasch neui Liste erstelle oder bearbeite.",
|
||||
"success": "De Namensruum isch erfolgriich archiviert worde.",
|
||||
"unarchiveSuccess": "Der Namespace wurde erfolgreich wiederhergestellt.",
|
||||
"description": "Wenn en Namensruum archiviert isch, chasch du kei neui Liste erstelle oder die bearbeite."
|
||||
},
|
||||
"delete": {
|
||||
@ -382,7 +385,7 @@
|
||||
"showDoneTasks": "Zeig die fertige Uufgabe",
|
||||
"sortAlphabetically": "Alphabetisch sortieren",
|
||||
"enablePriority": "Filter nach Priorität aktiviere",
|
||||
"enablePercentDone": "Filter nach Prozent iihschalte",
|
||||
"enablePercentDone": "Filter nach Fortschritt aktivieren",
|
||||
"dueDateRange": "Fälligkeitsberiich",
|
||||
"startDateRange": "Startdatumsbreiich",
|
||||
"endDateRange": "Enddatumsberiich",
|
||||
@ -575,7 +578,7 @@
|
||||
"endDate": "Enddatum setze",
|
||||
"reminders": "Errinnerig iihstelle",
|
||||
"repeatAfter": "En wiederholende Intervall setze",
|
||||
"percentDone": "Prozentuelli Erledigung setze",
|
||||
"percentDone": "Fortschritt einstellen",
|
||||
"attachments": "Aahang hinzuefüege",
|
||||
"relatedTasks": "Uufgabsbeziehig hinzufüege",
|
||||
"moveList": "Uufgab verschiebe",
|
||||
@ -595,7 +598,7 @@
|
||||
"dueDate": "Fälligkeitsdatum",
|
||||
"endDate": "Enddatum",
|
||||
"labels": "Labels",
|
||||
"percentDone": "% fertig",
|
||||
"percentDone": "Fortschritt",
|
||||
"priority": "Priorität",
|
||||
"relatedTasks": "Verwandti Uufgabe",
|
||||
"reminders": "Errinnerige",
|
||||
|
@ -56,7 +56,8 @@
|
||||
"showPassword": "Show the password",
|
||||
"hidePassword": "Hide the password",
|
||||
"noAccountYet": "Don't have an account yet?",
|
||||
"alreadyHaveAnAccount": "Already have an account?"
|
||||
"alreadyHaveAnAccount": "Already have an account?",
|
||||
"remember": "Stay logged in"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
@ -84,7 +85,8 @@
|
||||
"weekStartSunday": "Sunday",
|
||||
"weekStartMonday": "Monday",
|
||||
"language": "Language",
|
||||
"defaultList": "Default List"
|
||||
"defaultList": "Default List",
|
||||
"timezone": "Time Zone"
|
||||
},
|
||||
"totp": {
|
||||
"title": "Two Factor Authentication",
|
||||
@ -333,6 +335,7 @@
|
||||
"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.",
|
||||
"unarchiveSuccess": "The namespace was successfully un-archived.",
|
||||
"description": "If a namespace is archived, you cannot create new lists or edit it."
|
||||
},
|
||||
"delete": {
|
||||
@ -382,7 +385,7 @@
|
||||
"showDoneTasks": "Show Done Tasks",
|
||||
"sortAlphabetically": "Sort Alphabetically",
|
||||
"enablePriority": "Enable Filter By Priority",
|
||||
"enablePercentDone": "Enable Filter By Percent Done",
|
||||
"enablePercentDone": "Enable Filter By Progress",
|
||||
"dueDateRange": "Due Date Range",
|
||||
"startDateRange": "Start Date Range",
|
||||
"endDateRange": "End Date Range",
|
||||
@ -619,22 +622,22 @@
|
||||
"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",
|
||||
"assign": "Assign to User",
|
||||
"label": "Add Labels",
|
||||
"priority": "Set Priority",
|
||||
"dueDate": "Set Due Date",
|
||||
"startDate": "Set a Start Date",
|
||||
"endDate": "Set an End Date",
|
||||
"startDate": "Set Start Date",
|
||||
"endDate": "Set 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"
|
||||
"repeatAfter": "Set Repeating Interval",
|
||||
"percentDone": "Set Progress",
|
||||
"attachments": "Add Attachments",
|
||||
"relatedTasks": "Add Relation",
|
||||
"moveList": "Move",
|
||||
"color": "Set Color",
|
||||
"delete": "Delete",
|
||||
"favorite": "Add to Favorites",
|
||||
"unfavorite": "Remove from Favorites"
|
||||
}
|
||||
},
|
||||
"attributes": {
|
||||
@ -647,7 +650,7 @@
|
||||