Merge branch 'main' into feature/date-math

This commit is contained in:
kolaente 2022-01-30 11:55:28 +01:00
commit f5d08e46af
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
56 changed files with 1393 additions and 1197 deletions

View File

@ -1,4 +1,4 @@
import faker from 'faker' import faker from '@faker-js/faker'
import {Factory} from '../support/factory' import {Factory} from '../support/factory'
import {formatISO} from 'date-fns' import {formatISO} from 'date-fns'

View File

@ -1,4 +1,4 @@
import faker from 'faker' import faker from '@faker-js/faker'
import {Factory} from '../support/factory' import {Factory} from '../support/factory'
import {formatISO} from 'date-fns' import {formatISO} from 'date-fns'

View File

@ -1,6 +1,6 @@
import {Factory} from '../support/factory' import {Factory} from '../support/factory'
import {formatISO} from "date-fns" import {formatISO} from "date-fns"
import faker from 'faker' import faker from '@faker-js/faker'
export class LinkShareFactory extends Factory { export class LinkShareFactory extends Factory {
static table = 'link_shares' static table = 'link_shares'

View File

@ -1,6 +1,6 @@
import {Factory} from '../support/factory' import {Factory} from '../support/factory'
import {formatISO} from "date-fns" import {formatISO} from "date-fns"
import faker from 'faker' import faker from '@faker-js/faker'
export class ListFactory extends Factory { export class ListFactory extends Factory {
static table = 'lists' static table = 'lists'

View File

@ -1,4 +1,4 @@
import faker from 'faker' import faker from '@faker-js/faker'
import {Factory} from '../support/factory' import {Factory} from '../support/factory'
import {formatISO} from 'date-fns' import {formatISO} from 'date-fns'

View File

@ -1,4 +1,4 @@
import faker from 'faker' import faker from '@faker-js/faker'
import {Factory} from '../support/factory' import {Factory} from '../support/factory'
import {formatISO} from 'date-fns' import {formatISO} from 'date-fns'

View File

@ -1,4 +1,4 @@
import faker from 'faker' import faker from '@faker-js/faker'
import {Factory} from '../support/factory' import {Factory} from '../support/factory'
import {formatISO} from "date-fns" import {formatISO} from "date-fns"

View File

@ -1,4 +1,4 @@
import faker from 'faker' import faker from '@faker-js/faker'
import {Factory} from '../support/factory' import {Factory} from '../support/factory'
import {formatISO} from 'date-fns' import {formatISO} from 'date-fns'

View File

@ -1,4 +1,4 @@
import faker from 'faker' import faker from '@faker-js/faker'
import {Factory} from '../support/factory' import {Factory} from '../support/factory'
import {formatISO} from "date-fns" import {formatISO} from "date-fns"

View File

@ -20,35 +20,35 @@
"dependencies": { "dependencies": {
"@github/hotkey": "1.6.1", "@github/hotkey": "1.6.1",
"@kyvg/vue3-notification": "2.3.4", "@kyvg/vue3-notification": "2.3.4",
"@sentry/tracing": "6.16.1", "@sentry/tracing": "6.17.3",
"@sentry/vue": "6.16.1", "@sentry/vue": "6.17.3",
"@types/is-touch-device": "1.0.0", "@types/is-touch-device": "1.0.0",
"@vue/compat": "3.2.26", "@vue/compat": "3.2.29",
"@vueuse/core": "7.5.2", "@vueuse/core": "7.5.5",
"@vueuse/router": "7.5.3", "@vueuse/router": "7.5.5",
"bulma-css-variables": "0.9.33", "bulma-css-variables": "0.9.33",
"camel-case": "4.1.2", "camel-case": "4.1.2",
"codemirror": "5.65.0", "codemirror": "5.65.1",
"copy-to-clipboard": "3.3.1", "copy-to-clipboard": "3.3.1",
"date-fns": "2.28.0", "date-fns": "2.28.0",
"dompurify": "2.3.4", "dompurify": "2.3.5",
"easymde": "2.15.0", "easymde": "2.16.1",
"flatpickr": "4.6.9", "flatpickr": "4.6.9",
"flexsearch": "0.7.21", "flexsearch": "0.7.21",
"highlight.js": "11.4.0", "highlight.js": "11.4.0",
"is-touch-device": "1.0.1", "is-touch-device": "1.0.1",
"lodash.clonedeep": "4.5.0", "lodash.clonedeep": "4.5.0",
"lodash.debounce": "4.0.8", "lodash.debounce": "4.0.8",
"marked": "4.0.9", "marked": "4.0.12",
"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.13", "v-tooltip": "4.0.0-beta.17",
"vue": "3.2.26", "vue": "3.2.29",
"vue-advanced-cropper": "2.7.1", "vue-advanced-cropper": "2.8.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.26", "vue-i18n": "9.2.0-beta.30",
"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",
@ -56,41 +56,41 @@
}, },
"devDependencies": { "devDependencies": {
"@4tw/cypress-drag-drop": "2.1.0", "@4tw/cypress-drag-drop": "2.1.0",
"@faker-js/faker": "6.0.0-alpha.5",
"@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",
"@typescript-eslint/eslint-plugin": "5.9.0", "@typescript-eslint/eslint-plugin": "5.10.1",
"@typescript-eslint/parser": "5.9.0", "@typescript-eslint/parser": "5.10.1",
"@vitejs/plugin-legacy": "1.6.4", "@vitejs/plugin-legacy": "1.6.4",
"@vitejs/plugin-vue": "2.0.1", "@vitejs/plugin-vue": "2.1.0",
"@vue/eslint-config-typescript": "10.0.0", "@vue/eslint-config-typescript": "10.0.0",
"autoprefixer": "10.4.2", "autoprefixer": "10.4.2",
"axios": "0.24.0", "axios": "0.25.0",
"browserslist": "4.19.1", "browserslist": "4.19.1",
"caniuse-lite": "1.0.30001298", "caniuse-lite": "1.0.30001304",
"cypress": "9.2.0", "cypress": "9.3.1",
"cypress-file-upload": "5.0.8", "cypress-file-upload": "5.0.8",
"esbuild": "0.14.11", "esbuild": "0.14.14",
"eslint": "8.6.0", "eslint": "8.8.0",
"eslint-plugin-vue": "8.2.0", "eslint-plugin-vue": "8.4.0",
"express": "4.17.2", "express": "4.17.2",
"faker": "5.5.3", "netlify-cli": "8.13.0",
"netlify-cli": "8.6.15", "happy-dom": "2.30.1",
"happy-dom": "2.25.1",
"postcss": "8.4.5", "postcss": "8.4.5",
"postcss-preset-env": "7.2.0", "postcss-preset-env": "7.2.3",
"rollup": "2.63.0", "rollup": "2.66.1",
"rollup-plugin-visualizer": "5.5.2", "rollup-plugin-visualizer": "5.5.4",
"sass": "1.47.0", "sass": "1.49.0",
"slugify": "1.6.5", "slugify": "1.6.5",
"typescript": "4.5.4", "typescript": "4.5.5",
"vite": "2.7.10", "vite": "2.7.13",
"vite-plugin-pwa": "0.11.12", "vite-plugin-pwa": "0.11.13",
"vite-svg-loader": "3.1.1", "vite-svg-loader": "3.1.2",
"vitest": "0.0.139", "vitest": "0.2.5",
"vue-tsc": "0.30.2", "vue-tsc": "0.31.1",
"wait-on": "6.0.0", "wait-on": "6.0.0",
"workbox-cli": "6.4.2" "workbox-cli": "6.4.2"
}, },

View File

@ -3,5 +3,11 @@
"labels": ["dependencies"], "labels": ["dependencies"],
"extends": [ "extends": [
"config:base" "config:base"
],
"packageRules": [
{
"matchPackageNames": ["netlify-cli"],
"extends": ["schedule:weekly"]
}
] ]
} }

View File

@ -42,7 +42,7 @@ import {useBodyClass} from '@/composables/useBodyClass'
const store = useStore() const store = useStore()
const router = useRouter() const router = useRouter()
useBodyClass('is-touch', isTouchDevice) useBodyClass('is-touch', isTouchDevice())
const keyboardShortcutsActive = computed(() => store.state.keyboardShortcutsActive) const keyboardShortcutsActive = computed(() => store.state.keyboardShortcutsActive)
const authUser = computed(() => store.getters['auth/authUser']) const authUser = computed(() => store.getters['auth/authUser'])

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -9,7 +9,7 @@
class="app-container" class="app-container"
> >
<navigation/> <navigation/>
<div <main
:class="[ :class="[
{ 'is-menu-enabled': menuActive }, { 'is-menu-enabled': menuActive },
$route.name, $route.name,
@ -35,7 +35,7 @@
> >
<icon icon="keyboard"/> <icon icon="keyboard"/>
</a> </a>
</div> </main>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,8 +1,8 @@
<template> <template>
<div :class="{'is-active': menuActive}" class="namespace-container"> <aside :class="{'is-active': menuActive}" class="namespace-container">
<div class="menu top-menu"> <nav class="menu top-menu">
<router-link :to="{name: 'home'}" class="logo"> <router-link :to="{name: 'home'}" class="logo">
<Logo width="164" height="48" /> <Logo width="164" height="48"/>
</router-link> </router-link>
<ul class="menu-list"> <ul class="menu-list">
<li> <li>
@ -46,31 +46,35 @@
</router-link> </router-link>
</li> </li>
</ul> </ul>
</div> </nav>
<aside class="menu namespaces-lists loader-container is-loading-small" :class="{'is-loading': loading}"> <nav class="menu namespaces-lists loader-container is-loading-small" :class="{'is-loading': loading}">
<template v-for="(n, nk) in namespaces" :key="n.id" > <template v-for="(n, nk) in namespaces" :key="n.id">
<div class="namespace-title" :class="{'has-menu': n.id > 0}"> <div class="namespace-title" :class="{'has-menu': n.id > 0}">
<span <span
@click="toggleLists(n.id)" @click="toggleLists(n.id)"
class="menu-label" class="menu-label"
v-tooltip="namespaceTitles[nk]"> v-tooltip="namespaceTitles[nk]"
>
<span
v-if="n.hexColor !== ''"
:style="{ backgroundColor: n.hexColor }"
class="color-bubble"
/>
<span class="name"> <span class="name">
<span
:style="{ backgroundColor: n.hexColor }"
class="color-bubble"
v-if="n.hexColor !== ''">
</span>
{{ namespaceTitles[nk] }} {{ namespaceTitles[nk] }}
</span> </span>
<a
class="icon is-small toggle-lists-icon pl-2"
:class="{'active': typeof listsVisible[n.id] !== 'undefined' ? listsVisible[n.id] : true}"
@click="toggleLists(n.id)"
>
<icon icon="chevron-down"/>
</a>
<span class="count" :class="{'ml-2 mr-0': n.id > 0}">
({{ namespaceListsCount[nk] }})
</span>
</span> </span>
<a
class="icon is-small toggle-lists-icon"
:class="{'active': typeof listsVisible[n.id] !== 'undefined' ? listsVisible[n.id] : true}"
@click="toggleLists(n.id)"
>
<icon icon="chevron-down"/>
</a>
<namespace-settings-dropdown :namespace="n" v-if="n.id > 0"/> <namespace-settings-dropdown :namespace="n" v-if="n.id > 0"/>
</div> </div>
<div <div
@ -86,13 +90,15 @@
v-bind="dragOptions" v-bind="dragOptions"
:modelValue="activeLists[nk]" :modelValue="activeLists[nk]"
@update:modelValue="(lists) => updateActiveLists(n, lists)" @update:modelValue="(lists) => updateActiveLists(n, lists)"
:group="`namespace-${n.id}-lists`" group="namespace-lists"
@start="() => drag = true" @start="() => drag = true"
@end="e => saveListPosition(e, nk)" @end="saveListPosition"
handle=".handle" handle=".handle"
:disabled="n.id < 0 || null" :disabled="n.id < 0 || null"
tag="transition-group" tag="transition-group"
item-key="id" item-key="id"
:data-namespace-id="n.id"
:data-namespace-index="nk"
:component-data="{ :component-data="{
type: 'transition', type: 'transition',
tag: 'ul', tag: 'ul',
@ -134,7 +140,7 @@
:class="{'is-favorite': l.isFavorite}" :class="{'is-favorite': l.isFavorite}"
@click.prevent.stop="toggleFavoriteList(l)" @click.prevent.stop="toggleFavoriteList(l)"
class="favorite"> class="favorite">
<icon :icon="l.isFavorite ? 'star' : ['far', 'star']" /> <icon :icon="l.isFavorite ? 'star' : ['far', 'star']"/>
</span> </span>
</a> </a>
</router-link> </router-link>
@ -145,9 +151,9 @@
</draggable> </draggable>
</div> </div>
</template> </template>
</aside> </nav>
<PoweredByLink /> <PoweredByLink/>
</div> </aside>
</template> </template>
<script> <script>
@ -194,13 +200,13 @@ export default {
loading: state => state[LOADING] && state[LOADING_MODULE] === 'namespaces', loading: state => state[LOADING] && state[LOADING_MODULE] === 'namespaces',
}), }),
activeLists() { activeLists() {
return this.namespaces.map(({lists}) => lists?.filter(item => !item.isArchived)) return this.namespaces.map(({lists}) => lists?.filter(item => typeof item !== 'undefined' && !item.isArchived))
}, },
namespaceTitles() { namespaceTitles() {
return this.namespaces.map((namespace, index) => { return this.namespaces.map((namespace) => this.getNamespaceTitle(namespace))
const title = this.getNamespaceTitle(namespace) },
return `${title} (${this.activeLists[index]?.length ?? 0})` namespaceListsCount() {
}) return this.namespaces.map((_, index) => this.activeLists[index]?.length ?? 0)
}, },
}, },
beforeCreate() { beforeCreate() {
@ -237,15 +243,15 @@ export default {
this.listsVisible[namespaceId] = !this.listsVisible[namespaceId] this.listsVisible[namespaceId] = !this.listsVisible[namespaceId]
}, },
updateActiveLists(namespace, activeLists) { updateActiveLists(namespace, activeLists) {
// this is a bit hacky: since we do have to filter out the archived items from the list // This is a bit hacky: since we do have to filter out the archived items from the list
// for vue draggable updating it is not as simple as replacing it. // for vue draggable updating it is not as simple as replacing it.
// instead we iterate over the non archived items in the old list and replace them with the ones in their new order // To work around this, we merge the active lists with the archived ones. Doing so breaks the order
const lists = namespace.lists.map((item) => { // because now all archived lists are sorted after the active ones. This is fine because they are sorted
if (item.isArchived) { // later when showing them anyway, and it makes the merging happening here a lot easier.
return item const lists = [
} ...activeLists,
return activeLists.shift() ...namespace.lists.filter(l => l.isArchived),
}) ]
const newNamespace = { const newNamespace = {
...namespace, ...namespace,
@ -255,8 +261,11 @@ export default {
this.$store.commit('namespaces/setNamespaceById', newNamespace) this.$store.commit('namespaces/setNamespaceById', newNamespace)
}, },
async saveListPosition(e, namespaceIndex) { async saveListPosition(e) {
const listsActive = this.activeLists[namespaceIndex] const namespaceId = parseInt(e.to.dataset.namespaceId)
const newNamespaceIndex = parseInt(e.to.dataset.namespaceIndex)
const listsActive = this.activeLists[newNamespaceIndex]
const list = listsActive[e.newIndex] const list = listsActive[e.newIndex]
const listBefore = listsActive[e.newIndex - 1] ?? null const listBefore = listsActive[e.newIndex - 1] ?? null
const listAfter = listsActive[e.newIndex + 1] ?? null const listAfter = listsActive[e.newIndex + 1] ?? null
@ -269,6 +278,7 @@ export default {
await this.$store.dispatch('lists/updateList', { await this.$store.dispatch('lists/updateList', {
...list, ...list,
position, position,
namespaceId,
}) })
} finally { } finally {
this.listUpdating[list.id] = false this.listUpdating[list.id] = false
@ -365,8 +375,9 @@ $vikunja-nav-selected-width: 0.4rem;
.menu-label { .menu-label {
.color-bubble { .color-bubble {
width: 14px !important; width: 14px;
height: 14px !important; height: 14px;
flex-basis: auto;
} }
.is-archived { .is-archived {
@ -387,6 +398,12 @@ $vikunja-nav-selected-width: 0.4rem;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
margin-right: auto;
}
.count {
color: var(--grey-500);
margin-right: .5rem;
} }
} }

View File

@ -1,9 +1,8 @@
<template> <template>
<nav <header
:class="{'has-background': background}" :class="{'has-background': background}"
aria-label="main navigation" aria-label="main navigation"
class="navbar main-theme is-fixed-top" class="navbar main-theme is-fixed-top"
role="navigation"
> >
<router-link :to="{name: 'home'}" class="logo-link"> <router-link :to="{name: 'home'}" class="logo-link">
<Logo width="164" height="48"/> <Logo width="164" height="48"/>
@ -77,7 +76,7 @@
</dropdown> </dropdown>
</div> </div>
</div> </div>
</nav> </header>
</template> </template>
<script> <script>

View File

@ -85,4 +85,8 @@ export default {
margin-left: .5rem; margin-left: .5rem;
} }
} }
.dark .update-notification {
color: var(--grey-200);
}
</style> </style>

View File

@ -28,19 +28,20 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {ref, watch} from 'vue' import {PropType, ref, watch} from 'vue'
import {useStore} from 'vuex' import {useStore} from 'vuex'
import ListService from '@/services/list' import ListService from '@/services/list'
import {colorIsDark} from '@/helpers/color/colorIsDark' import {colorIsDark} from '@/helpers/color/colorIsDark'
import ListModel from '@/models/list'
const background = ref<string | null>(null) const background = ref<string | null>(null)
const backgroundLoading = ref(false) const backgroundLoading = ref(false)
const props = defineProps({ const props = defineProps({
list: { list: {
type: Object, type: Object as PropType<ListModel>,
required: true, required: true,
}, },
showArchived: { showArchived: {
@ -68,7 +69,7 @@ async function loadBackground() {
const store = useStore() const store = useStore()
function toggleFavoriteList(list) { function toggleFavoriteList(list: ListModel) {
// The favorites pseudo list is always favorite // The favorites pseudo list is always favorite
// Archived lists cannot be marked favorite // Archived lists cannot be marked favorite
if (list.id === -1 || list.isArchived) { if (list.id === -1 || list.isArchived) {

View File

@ -4,7 +4,7 @@
<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>
<message> <message class="mb-4">
{{ {{
s.available($route) ? $t('keyboardShortcuts.currentPageOnly') : $t('keyboardShortcuts.allPages') s.available($route) ? $t('keyboardShortcuts.currentPageOnly') : $t('keyboardShortcuts.allPages')
}} }}

View File

@ -1,7 +1,7 @@
<template> <template>
<x-button <x-button
variant="secondary" variant="secondary"
:icon="icon" :icon="iconName"
v-tooltip="tooltipText" v-tooltip="tooltipText"
@click="changeSubscription" @click="changeSubscription"
:disabled="disabled || null" :disabled="disabled || null"
@ -16,7 +16,7 @@
v-else v-else
> >
<span class="icon"> <span class="icon">
<icon :icon="icon"/> <icon :icon="iconName"/>
</span> </span>
{{ buttonText }} {{ buttonText }}
</a> </a>
@ -38,9 +38,13 @@ const props = defineProps({
}, },
subscription: { subscription: {
required: true, required: true,
validator(value) {
return value instanceof SubscriptionModel || value === null
},
}, },
entityId: { entityId: {
required: true, required: true,
type: Number,
}, },
isButton: { isButton: {
type: Boolean, type: Boolean,
@ -48,6 +52,8 @@ const props = defineProps({
}, },
}) })
const subscriptionEntity = computed<string>(() => props.subscription.entity)
const emit = defineEmits(['change']) const emit = defineEmits(['change'])
const subscriptionService = shallowRef(new SubscriptionService()) const subscriptionService = shallowRef(new SubscriptionService())
@ -57,7 +63,7 @@ const tooltipText = computed(() => {
if (disabled.value) { if (disabled.value) {
return t('task.subscription.subscribedThroughParent', { return t('task.subscription.subscribedThroughParent', {
entity: props.entity, entity: props.entity,
parent: props.subscription.entity, parent: subscriptionEntity.value,
}) })
} }
@ -67,13 +73,13 @@ const tooltipText = computed(() => {
}) })
const buttonText = computed(() => props.subscription !== null ? t('task.subscription.unsubscribe') : t('task.subscription.subscribe')) const buttonText = computed(() => props.subscription !== null ? t('task.subscription.unsubscribe') : t('task.subscription.subscribe'))
const icon = computed(() => props.subscription !== null ? ['far', 'bell-slash'] : 'bell') const iconName = computed(() => props.subscription !== null ? ['far', 'bell-slash'] : 'bell')
const disabled = computed(() => { const disabled = computed(() => {
if (props.subscription === null) { if (props.subscription === null) {
return false return false
} }
return props.subscription.entity !== props.entity return subscriptionEntity.value !== props.entity
}) })
function changeSubscription() { function changeSubscription() {

View File

@ -13,6 +13,7 @@
import {ref, computed} from 'vue' import {ref, computed} from 'vue'
import {useStore} from 'vuex' import {useStore} from 'vuex'
import Multiselect from '@/components/input/multiselect.vue' import Multiselect from '@/components/input/multiselect.vue'
import NamespaceModel from '@/models/namespace'
const emit = defineEmits(['selected']) const emit = defineEmits(['selected'])
@ -25,7 +26,7 @@ function findNamespaces(newQuery: string) {
query.value = newQuery query.value = newQuery
} }
function select(namespace) { function select(namespace: NamespaceModel) {
emit('selected', namespace) emit('selected', namespace)
} }
</script> </script>

View File

@ -34,7 +34,7 @@
> >
<div class="filename">{{ a.file.name }}</div> <div class="filename">{{ a.file.name }}</div>
<div class="info"> <div class="info">
<p class="collapses"> <p class="attachment-info-meta">
<i18n-t keypath="task.attachment.createdBy"> <i18n-t keypath="task.attachment.createdBy">
<span v-tooltip="formatDate(a.created)"> <span v-tooltip="formatDate(a.created)">
{{ formatDateSince(a.created) }} {{ formatDateSince(a.created) }}
@ -289,21 +289,6 @@ export default {
content: '·'; content: '·';
padding: 0 .25rem; padding: 0 .25rem;
} }
@media screen and (max-width: $mobile) {
&.collapses {
flex-direction: column;
> span:not(:last-child):after,
> a:not(:last-child):after {
display: none;
}
.user .username {
display: none;
}
}
}
} }
} }
} }
@ -341,6 +326,10 @@ export default {
height: auto; height: auto;
text-shadow: var(--shadow-md); text-shadow: var(--shadow-md);
animation: bounce 2s infinite; animation: bounce 2s infinite;
@media (prefers-reduced-motion: reduce) {
animation: none;
}
} }
.hint { .hint {
@ -357,6 +346,35 @@ export default {
} }
} }
.attachment-info-meta {
display: flex;
align-items: center;
:deep(.user) {
display: flex !important;
align-items: center;
margin: 0 .5rem;
}
@media screen and (max-width: $mobile) {
flex-direction: column;
align-items: flex-start;
:deep(.user) {
margin: .5rem 0;
}
> span:not(:last-child):after,
> a:not(:last-child):after {
display: none;
}
.user .username {
display: none;
}
}
}
@keyframes bounce { @keyframes bounce {
from, from,
20%, 20%,

View File

@ -1,6 +1,8 @@
<template> <template>
<td v-tooltip="+date === 0 ? '' : formatDate(date)"> <td v-tooltip="+date === 0 ? '' : formatDate(date)">
{{ +date === 0 ? '-' : formatDateSince(date) }} <time :datetime="date ? formatISO(date) : null">
{{ +date === 0 ? '-' : formatDateSince(date) }}
</time>
</td> </td>
</template> </template>

View File

@ -25,13 +25,13 @@
<template #searchResult="props"> <template #searchResult="props">
<span <span
v-if="typeof props.option === 'string'" v-if="typeof props.option === 'string'"
class="tag"> class="tag search-result">
<span>{{ props.option }}</span> <span>{{ props.option }}</span>
</span> </span>
<span <span
v-else v-else
:style="{'background': props.option.hexColor, 'color': props.option.textColor}" :style="{'background': props.option.hexColor, 'color': props.option.textColor}"
class="tag"> class="tag search-result">
<span>{{ props.option.title }}</span> <span>{{ props.option.title }}</span>
</span> </span>
</template> </template>
@ -152,6 +152,18 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.tag { .tag {
margin: .5rem 0 0 .5rem; margin: .25rem !important;
}
.tag.search-result {
margin: 0 !important;
}
:deep(.input-wrapper) {
padding: .25rem !important;
}
:deep(input.input) {
padding: 0 .5rem;
} }
</style> </style>

View File

@ -28,9 +28,9 @@
<span class="icon"> <span class="icon">
<icon :icon="['far', 'calendar-alt']"/> <icon :icon="['far', 'calendar-alt']"/>
</span> </span>
<span> <time :datetime="formatISO(task.dueDate)">
{{ formatDateSince(task.dueDate) }} {{ formatDateSince(task.dueDate) }}
</span> </time>
</span> </span>
<h3>{{ task.title }}</h3> <h3>{{ task.title }}</h3>
<progress <progress

View File

@ -39,14 +39,17 @@
:user="a" :user="a"
v-for="(a, i) in task.assignees" v-for="(a, i) in task.assignees"
/> />
<i <time
:datetime="formatISO(task.dueDate)"
:class="{'overdue': task.dueDate <= new Date() && !task.done}" :class="{'overdue': task.dueDate <= new Date() && !task.done}"
class="is-italic"
@click.prevent.stop="showDefer = !showDefer" @click.prevent.stop="showDefer = !showDefer"
v-if="+new Date(task.dueDate) > 0" v-if="+new Date(task.dueDate) > 0"
v-tooltip="formatDate(task.dueDate)" v-tooltip="formatDate(task.dueDate)"
:aria-expanded="showDefer ? 'true' : 'false'"
> >
- {{ $t('task.detail.due', {at: formatDateSince(task.dueDate)}) }} - {{ $t('task.detail.due', {at: formatDateSince(task.dueDate)}) }}
</i> </time>
<transition name="fade"> <transition name="fade">
<defer-task v-if="+new Date(task.dueDate) > 0 && showDefer" v-model="task" ref="deferDueDate"/> <defer-task v-if="+new Date(task.dueDate) > 0 && showDefer" v-model="task" ref="deferDueDate"/>
</transition> </transition>

View File

@ -1,9 +1,9 @@
import {computed, watch, readonly} from 'vue' import {computed, watch, readonly} from 'vue'
import {useStorage, createSharedComposable, ColorSchema, usePreferredColorScheme, tryOnMounted} from '@vueuse/core' import {useStorage, createSharedComposable, BasicColorSchema, usePreferredColorScheme, tryOnMounted} from '@vueuse/core'
const STORAGE_KEY = 'color-scheme' const STORAGE_KEY = 'color-scheme'
const DEFAULT_COLOR_SCHEME_SETTING: ColorSchema = 'light' const DEFAULT_COLOR_SCHEME_SETTING: BasicColorSchema = '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<ColorSchema>(STORAGE_KEY, DEFAULT_COLOR_SCHEME_SETTING) const store = useStorage<BasicColorSchema>(STORAGE_KEY, DEFAULT_COLOR_SCHEME_SETTING)
const preferredColorScheme = usePreferredColorScheme() const preferredColorScheme = usePreferredColorScheme()

View File

@ -1,5 +1,5 @@
import {createDateFromString} from '@/helpers/time/createDateFromString' import {createDateFromString} from '@/helpers/time/createDateFromString'
import {format, formatDistanceToNow} from 'date-fns' import {format, formatDistanceToNow, formatISO as formatISOfns} from 'date-fns'
import {enGB, de, fr, ru} from 'date-fns/locale' import {enGB, de, fr, ru} from 'date-fns/locale'
import {i18n} from '@/i18n' import {i18n} from '@/i18n'
@ -44,3 +44,7 @@ export const formatDateSince = (date) => {
addSuffix: true, addSuffix: true,
}) })
} }
export function formatISO(date) {
return date ? formatISOfns(date) : ''
}

View File

@ -899,7 +899,7 @@
"4015": "Komentář k úkolu neexistuje.", "4015": "Komentář k úkolu neexistuje.",
"4016": "Neplatné pole úkolu.", "4016": "Neplatné pole úkolu.",
"4017": "Neplatný komparátor filtru úkolů.", "4017": "Neplatný komparátor filtru úkolů.",
"4018": "Neplatný koncatinátor filtru úkolů.", "4018": "Invalid task filter concatenator.",
"4019": "Neplatná hodnota filtru úkolů.", "4019": "Neplatná hodnota filtru úkolů.",
"5001": "Prostor neexistuje.", "5001": "Prostor neexistuje.",
"5003": "Nemáte přístup ke zvolenému prostoru.", "5003": "Nemáte přístup ke zvolenému prostoru.",

View File

@ -7,7 +7,7 @@
"lastViewed": "Zuletzt angesehen", "lastViewed": "Zuletzt angesehen",
"list": { "list": {
"newText": "Du kannst eine neue Liste für deine neuen Aufgaben erstellen:", "newText": "Du kannst eine neue Liste für deine neuen Aufgaben erstellen:",
"new": "New list", "new": "Neue Liste",
"importText": "Oder importiere deine Listen und Aufgaben aus anderen Diensten in Vikunja:", "importText": "Oder importiere deine Listen und Aufgaben aus anderen Diensten in Vikunja:",
"import": "Deine Daten in Vikunja importieren" "import": "Deine Daten in Vikunja importieren"
} }
@ -157,7 +157,7 @@
"searchSelect": "Klicke auf oder drücke die Eingabetaste, um diese Liste auszuwählen", "searchSelect": "Klicke auf oder drücke die Eingabetaste, um diese Liste auszuwählen",
"shared": "Geteilte Listen", "shared": "Geteilte Listen",
"create": { "create": {
"header": "New list", "header": "Neue Liste",
"titlePlaceholder": "Der Titel der Liste steht hier…", "titlePlaceholder": "Der Titel der Liste steht hier…",
"addTitleRequired": "Bitte gebe einen Namen an.", "addTitleRequired": "Bitte gebe einen Namen an.",
"createdSuccess": "Die Liste wurde erfolgreich erstellt.", "createdSuccess": "Die Liste wurde erfolgreich erstellt.",
@ -315,7 +315,7 @@
"namespaces": "Namespaces", "namespaces": "Namespaces",
"search": "Beginne zu schreiben, um einen Namespace zu suchen…", "search": "Beginne zu schreiben, um einen Namespace zu suchen…",
"create": { "create": {
"title": "New namespace", "title": "Neuer Namespace",
"titleRequired": "Bitte gebe einen Titel an.", "titleRequired": "Bitte gebe einen Titel an.",
"explanation": "Ein Namespace ist eine Sammlung von Listen, die du teilen und zur Organisation verwenden kannst. Jede Liste zu einem Namespace.", "explanation": "Ein Namespace ist eine Sammlung von Listen, die du teilen und zur Organisation verwenden kannst. Jede Liste zu einem Namespace.",
"tooltip": "Was ist ein Namespace?", "tooltip": "Was ist ein Namespace?",
@ -383,7 +383,7 @@
"reminderRange": "Erinnerungs-Datumsbereich" "reminderRange": "Erinnerungs-Datumsbereich"
}, },
"create": { "create": {
"title": "New Saved Filter", "title": "Neuer gespeicherter Filter",
"description": "Ein gespeicherter Filter ist eine virtuelle Liste, die bei jedem Zugriff aus einem Satz von Filtern errechnet wird. Einmal erstellt, erscheint diese in einem speziellen Namespace.", "description": "Ein gespeicherter Filter ist eine virtuelle Liste, die bei jedem Zugriff aus einem Satz von Filtern errechnet wird. Einmal erstellt, erscheint diese in einem speziellen Namespace.",
"action": "Neuen gespeicherten Filter erstellen" "action": "Neuen gespeicherten Filter erstellen"
}, },
@ -545,7 +545,7 @@
"chooseStartDate": "Klicke hier, um ein Startdatum zu setzen", "chooseStartDate": "Klicke hier, um ein Startdatum zu setzen",
"chooseEndDate": "Klicke hier, um ein Enddatum zu setzen", "chooseEndDate": "Klicke hier, um ein Enddatum zu setzen",
"move": "Aufgabe in eine andere Liste verschieben", "move": "Aufgabe in eine andere Liste verschieben",
"done": "Mark task done!", "done": "Als erledigt markieren!",
"undone": "Als nicht erledigt markieren", "undone": "Als nicht erledigt markieren",
"created": "Erstellt {0} von {1}", "created": "Erstellt {0} von {1}",
"updated": "Aktualisiert {0}", "updated": "Aktualisiert {0}",
@ -781,7 +781,7 @@
"then": "dann", "then": "dann",
"task": { "task": {
"title": "Aufgabenseite", "title": "Aufgabenseite",
"done": "Done", "done": "Fertig",
"assign": "Benutzer:in zuweisen", "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",
@ -899,7 +899,7 @@
"4015": "Dieser Aufgabenkommentar existiert nicht.", "4015": "Dieser Aufgabenkommentar existiert nicht.",
"4016": "Ungültiges Aufgabenfeld.", "4016": "Ungültiges Aufgabenfeld.",
"4017": "Ungültiger Aufgabenfilter (Vergleichskriterium).", "4017": "Ungültiger Aufgabenfilter (Vergleichskriterium).",
"4018": "Ungültiger Aufgabenfilter (Kombination).", "4018": "Ungültige Verkettung von Aufgabenfiltern.",
"4019": "Ungültiger Aufgabenfilter (Wert).", "4019": "Ungültiger Aufgabenfilter (Wert).",
"5001": "Dieser Namespace existiert nicht.", "5001": "Dieser Namespace existiert nicht.",
"5003": "Du hast keinen Zugriff auf den Namespace.", "5003": "Du hast keinen Zugriff auf den Namespace.",

View File

@ -7,7 +7,7 @@
"lastViewed": "Zletscht ahglueget", "lastViewed": "Zletscht ahglueget",
"list": { "list": {
"newText": "Du chasch e Liste für dini neue Uufgabe erstelle:", "newText": "Du chasch e Liste für dini neue Uufgabe erstelle:",
"new": "New list", "new": "Neue Liste",
"importText": "Oder importier dini Liste und Uufgabe us anderne Dienst nach Vikunja:", "importText": "Oder importier dini Liste und Uufgabe us anderne Dienst nach Vikunja:",
"import": "Dini Date in Vikunja importiere" "import": "Dini Date in Vikunja importiere"
} }
@ -157,7 +157,7 @@
"searchSelect": "Druck uf Enter um die Liste uuszwähle", "searchSelect": "Druck uf Enter um die Liste uuszwähle",
"shared": "Teilti Liste", "shared": "Teilti Liste",
"create": { "create": {
"header": "New list", "header": "Neue Liste",
"titlePlaceholder": "Listetitl da ahgeh…", "titlePlaceholder": "Listetitl da ahgeh…",
"addTitleRequired": "Bitte gib en Titl ah.", "addTitleRequired": "Bitte gib en Titl ah.",
"createdSuccess": "Liste erfolgriich erstellt.", "createdSuccess": "Liste erfolgriich erstellt.",
@ -315,7 +315,7 @@
"namespaces": "Namensrüüm", "namespaces": "Namensrüüm",
"search": "Schriib, um nachemne Namensruum z'sueche…", "search": "Schriib, um nachemne Namensruum z'sueche…",
"create": { "create": {
"title": "New namespace", "title": "Neuer Namespace",
"titleRequired": "Bitte gib en Titl ah.", "titleRequired": "Bitte gib en Titl ah.",
"explanation": "En Namensruum isch e Gruppe vo Liste, wo du chasch zur Organisation benutze. Tatsächlich sind alli Listene emne Namensruum zuegwise.", "explanation": "En Namensruum isch e Gruppe vo Liste, wo du chasch zur Organisation benutze. Tatsächlich sind alli Listene emne Namensruum zuegwise.",
"tooltip": "Was isch en Namensruum?", "tooltip": "Was isch en Namensruum?",
@ -383,7 +383,7 @@
"reminderRange": "Errinnerigs Datumbereich" "reminderRange": "Errinnerigs Datumbereich"
}, },
"create": { "create": {
"title": "New Saved Filter", "title": "Neuer gespeicherter Filter",
"description": "En gspeicherete Filter isch e virtuelli Liste, welche vomene Satz a Filter zemmegsetzt wird, sobald me uf sie zuegriift. Wenn sie mal erstellt worde isch, erhaltet si ihren eigene Namensruum.", "description": "En gspeicherete Filter isch e virtuelli Liste, welche vomene Satz a Filter zemmegsetzt wird, sobald me uf sie zuegriift. Wenn sie mal erstellt worde isch, erhaltet si ihren eigene Namensruum.",
"action": "Neue gspeicherete Filter erstelle" "action": "Neue gspeicherete Filter erstelle"
}, },
@ -545,7 +545,7 @@
"chooseStartDate": "Druck dah, um es Startdatum z'setze", "chooseStartDate": "Druck dah, um es Startdatum z'setze",
"chooseEndDate": "Druck da, um es Enddatum z'setze", "chooseEndDate": "Druck da, um es Enddatum z'setze",
"move": "Schieb die Uufgab in e anderi Liste", "move": "Schieb die Uufgab in e anderi Liste",
"done": "Mark task done!", "done": "Als erledigt markieren!",
"undone": "Als unerledigt markierä", "undone": "Als unerledigt markierä",
"created": "Erstellt am {0} vo {1}", "created": "Erstellt am {0} vo {1}",
"updated": "{0} g'updatet", "updated": "{0} g'updatet",
@ -781,7 +781,7 @@
"then": "dann", "then": "dann",
"task": { "task": {
"title": "Uufgabesiite", "title": "Uufgabesiite",
"done": "Done", "done": "Fertig",
"assign": "Benutzer:in zuweisen", "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",
@ -899,7 +899,7 @@
"4015": "De Uufgabe Kommentar giz nid.", "4015": "De Uufgabe Kommentar giz nid.",
"4016": "Ungültigs Uufgabefeld.", "4016": "Ungültigs Uufgabefeld.",
"4017": "Ungültige Uufgabefilter vergliich.", "4017": "Ungültige Uufgabefilter vergliich.",
"4018": "Ungültige Uufgabefilter Zemmezug.", "4018": "Ungültige Verkettung von Aufgabenfiltern.",
"4019": "Ungültigi Uufgabe Filter Wert.", "4019": "Ungültigi Uufgabe Filter Wert.",
"5001": "De Namensruum existiert nid.", "5001": "De Namensruum existiert nid.",
"5003": "Du hesch kei Zuegriff zu dem Namensruum.", "5003": "Du hesch kei Zuegriff zu dem Namensruum.",

View File

@ -951,7 +951,7 @@
"4015": "The task comment does not exist.", "4015": "The task comment does not exist.",
"4016": "Invalid task field.", "4016": "Invalid task field.",
"4017": "Invalid task filter comparator.", "4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.", "4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.", "4019": "Invalid task filter value.",
"5001": "The namespace does not exist.", "5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.", "5003": "You do not have access to the specified namespace.",

View File

@ -899,7 +899,7 @@
"4015": "The task comment does not exist.", "4015": "The task comment does not exist.",
"4016": "Invalid task field.", "4016": "Invalid task field.",
"4017": "Invalid task filter comparator.", "4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.", "4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.", "4019": "Invalid task filter value.",
"5001": "The namespace does not exist.", "5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.", "5003": "You do not have access to the specified namespace.",

View File

@ -116,12 +116,12 @@
"vikunja": "Vikunja" "vikunja": "Vikunja"
}, },
"appearance": { "appearance": {
"title": "Color Scheme", "title": "Jeu de couleurs",
"setSuccess": "Saved change of color scheme to {colorScheme}", "setSuccess": "Changement du jeu de couleurs enregistré vers {colorScheme}",
"colorScheme": { "colorScheme": {
"light": "Light", "light": "Clair",
"system": "System", "system": "Système",
"dark": "Dark" "dark": "Sombre"
} }
} }
}, },
@ -475,7 +475,7 @@
"download": "Télécharger", "download": "Télécharger",
"showMenu": "Afficher le menu", "showMenu": "Afficher le menu",
"hideMenu": "Masquer le menu", "hideMenu": "Masquer le menu",
"forExample": "For example:", "forExample": "Par exemple :",
"welcomeBack": "Welcome Back!" "welcomeBack": "Welcome Back!"
}, },
"input": { "input": {
@ -561,7 +561,7 @@
"text2": "Ceci supprimera également toutes les pièces jointes, les rappels et les relations associés à cette tâche et ne pourra pas être annulé !" "text2": "Ceci supprimera également toutes les pièces jointes, les rappels et les relations associés à cette tâche et ne pourra pas être annulé !"
}, },
"actions": { "actions": {
"assign": "Assign to a user", "assign": "Attribuer à un utilisateur",
"label": "Ajouter des étiquettes", "label": "Ajouter des étiquettes",
"priority": "Définir la priorité", "priority": "Définir la priorité",
"dueDate": "Définir léchéance", "dueDate": "Définir léchéance",
@ -726,8 +726,8 @@
"dateCurrentYear": "utilisera lannée en cours", "dateCurrentYear": "utilisera lannée en cours",
"dateNth": "utilisera le {day}e du mois en cours", "dateNth": "utilisera le {day}e du mois en cours",
"dateTime": "Combinez nimporte lequel des formats de date avec « {time} » (ou {timePM}) pour définir une heure.", "dateTime": "Combinez nimporte lequel des formats de date avec « {time} » (ou {timePM}) pour définir une heure.",
"repeats": "Repeating tasks", "repeats": "Tâches répétitives",
"repeatsDescription": "To set a task as repeating in an interval, simply add '{suffix}' to the task text. The amount needs to be a number and can be omitted to use just the type (see examples)." "repeatsDescription": "Pour définir une tâche comme répétitive dans un intervalle, il suffit d'ajouter « {suffix} » au texte de la tâche. Le montant doit être un nombre et peut être omis pour utiliser uniquement le type (voir exemples)."
} }
}, },
"team": { "team": {
@ -782,7 +782,7 @@
"task": { "task": {
"title": "Page de tâche", "title": "Page de tâche",
"done": "Done", "done": "Done",
"assign": "Assign to a user", "assign": "Attribuer à un utilisateur",
"labels": "Ajouter des étiquettes à cette tâche", "labels": "Ajouter des étiquettes à cette tâche",
"dueDate": "Modifier la date déchéance de cette tâche", "dueDate": "Modifier la date déchéance de cette tâche",
"attachment": "Ajouter une pièce jointe à cette tâche", "attachment": "Ajouter une pièce jointe à cette tâche",
@ -899,7 +899,7 @@
"4015": "Le commentaire de la tâche nexiste pas.", "4015": "Le commentaire de la tâche nexiste pas.",
"4016": "Champ de tâche invalide.", "4016": "Champ de tâche invalide.",
"4017": "Comparateur de filtre de tâche invalide.", "4017": "Comparateur de filtre de tâche invalide.",
"4018": "Concaténateur de filtre de tâche invalide.", "4018": "Invalid task filter concatenator.",
"4019": "Valeur de filtre de tâche invalide.", "4019": "Valeur de filtre de tâche invalide.",
"5001": "Lespace de noms nexiste pas.", "5001": "Lespace de noms nexiste pas.",
"5003": "Tu nas pas accès à lespace de noms indiqué.", "5003": "Tu nas pas accès à lespace de noms indiqué.",
@ -908,7 +908,7 @@
"5010": "Cette équipe na pas accès à cet espace de noms.", "5010": "Cette équipe na 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": "Lespace de noms est archivé et ne peut donc être consulté quen lecture seule.", "5012": "Lespace de noms est archivé et ne peut donc être consulté quen lecture seule.",
"6001": "The team name cannot be empty.", "6001": "Le nom de l'équipe ne peut pas être vide.",
"6002": "Léquipe nexiste pas.", "6002": "Léquipe nexiste 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": "Lutilisateur·rice est déjà membre de cette équipe.", "6005": "Lutilisateur·rice est déjà membre de cette équipe.",

View File

@ -7,7 +7,7 @@
"lastViewed": "Ultima visualizzazione", "lastViewed": "Ultima visualizzazione",
"list": { "list": {
"newText": "È possibile creare una nuova lista per le nuove attività:", "newText": "È possibile creare una nuova lista per le nuove attività:",
"new": "New list", "new": "Nuova lista",
"importText": "O importare le liste e le attività da altri servizi in Vikunja:", "importText": "O importare le liste e le attività da altri servizi in Vikunja:",
"import": "Importa i tuoi dati in Vikunja" "import": "Importa i tuoi dati in Vikunja"
} }
@ -17,14 +17,14 @@
"text": "La pagina richiesta non esiste." "text": "La pagina richiesta non esiste."
}, },
"ready": { "ready": {
"loading": "Vikunja is loading…", "loading": "Vikunja sta caricando…",
"errorOccured": "An error occured:", "errorOccured": "Si è verificato un errore:",
"checkApiUrl": "Please check if the api url is correct.", "checkApiUrl": "Controlla se l'URL API è corretto.",
"noApiUrlConfigured": "No API url was configured. Please set one below:" "noApiUrlConfigured": "Nessun URL API configurato. Impostane uno qui sotto:"
}, },
"offline": { "offline": {
"title": "You are offline.", "title": "Sei offline.",
"text": "Please check your network connection and try again." "text": "Controlla la connessione di rete e riprova."
}, },
"user": { "user": {
"auth": { "auth": {
@ -36,7 +36,7 @@
"password": "Password", "password": "Password",
"passwordRepeat": "Digita di nuovo la tua password", "passwordRepeat": "Digita di nuovo la tua password",
"passwordPlaceholder": "es. ••••••••••••", "passwordPlaceholder": "es. ••••••••••••",
"forgotPassword": "Forgot your password?", "forgotPassword": "Password dimenticata?",
"resetPassword": "Reimposta la tua password", "resetPassword": "Reimposta la tua password",
"resetPasswordAction": "Inviami il link per reimpostare la password", "resetPasswordAction": "Inviami il link per reimpostare la password",
"resetPasswordSuccess": "Controlla la tua casella di posta! Dovresti avere un'e-mail con le istruzioni su come reimpostare la password.", "resetPasswordSuccess": "Controlla la tua casella di posta! Dovresti avere un'e-mail con le istruzioni su come reimpostare la password.",
@ -48,7 +48,7 @@
"register": "Registrati", "register": "Registrati",
"loginWith": "Accedi con {provider}", "loginWith": "Accedi con {provider}",
"authenticating": "Autenticazione…", "authenticating": "Autenticazione…",
"openIdStateError": "State does not match, refusing to continue!", "openIdStateError": "Stato non corrispondente, impossibile continuare!",
"openIdGeneralError": "Si è verificato un errore durante l'autenticazione con terze parti.", "openIdGeneralError": "Si è verificato un errore durante l'autenticazione con terze parti.",
"logout": "Esci" "logout": "Esci"
}, },
@ -103,31 +103,31 @@
"title": "Avatar", "title": "Avatar",
"initials": "Iniziali", "initials": "Iniziali",
"gravatar": "Gravatar", "gravatar": "Gravatar",
"marble": "Marble", "marble": "Marmo",
"upload": "Carica", "upload": "Carica",
"uploadAvatar": "Carica Avatar", "uploadAvatar": "Carica Avatar",
"statusUpdateSuccess": "Avatar status was updated successfully!", "statusUpdateSuccess": "Avatar aggiornato!",
"setSuccess": "L'avatar è stato impostato con successo!" "setSuccess": "L'avatar è stato impostato con successo!"
}, },
"quickAddMagic": { "quickAddMagic": {
"title": "Quick Add Magic Mode", "title": "Modalità Aggiunta Rapida Magica",
"disabled": "Disabilitato", "disabled": "Disabilitato",
"todoist": "Todoist", "todoist": "Todoist",
"vikunja": "Vikunja" "vikunja": "Vikunja"
}, },
"appearance": { "appearance": {
"title": "Color Scheme", "title": "Tema",
"setSuccess": "Saved change of color scheme to {colorScheme}", "setSuccess": "Tema cambiato in {colorScheme}",
"colorScheme": { "colorScheme": {
"light": "Light", "light": "Chiaro",
"system": "System", "system": "Sistema",
"dark": "Dark" "dark": "Scuro"
} }
} }
}, },
"deletion": { "deletion": {
"title": "Delete your Vikunja Account", "title": "Elimina il tuo Account Vikunja",
"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.", "text1": "La cancellazione del tuo account è permanente e non può essere annullata. Elimineremo tutti i tuoi namespace, liste, attività e tutto ciò che è ad esso associato.",
"text2": "Per continuare, inserisci la tua password. Riceverai un'e-mail con ulteriori istruzioni.", "text2": "Per continuare, inserisci la tua password. Riceverai un'e-mail con ulteriori istruzioni.",
"confirm": "Elimina il mio profilo", "confirm": "Elimina il mio profilo",
"requestSuccess": "Richiesta riuscita. Riceverai un'e-mail con ulteriori istruzioni.", "requestSuccess": "Richiesta riuscita. Riceverai un'e-mail con ulteriori istruzioni.",
@ -141,7 +141,7 @@
}, },
"export": { "export": {
"title": "Esporta i tuoi dati Vikunja", "title": "Esporta i tuoi dati Vikunja",
"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.", "description": "Puoi richiedere una copia di tutti i tuoi dati all'interno di Vikunja. Questo include i Namespace, le Liste, le Attività e tutto ciò che è loro associato. È possibile importare questi dati in qualsiasi istanza Vikunja attraverso la funzione di migrazione.",
"descriptionPasswordRequired": "Inserisci la tua password per procedere:", "descriptionPasswordRequired": "Inserisci la tua password per procedere:",
"request": "Richiedi una copia dei miei dati Vikunja", "request": "Richiedi una copia dei miei dati Vikunja",
"success": "Hai richiesto con successo i tuoi dati Vikunja! Ti invieremo un'e-mail una volta che saranno pronti da scaricare.", "success": "Hai richiesto con successo i tuoi dati Vikunja! Ti invieremo un'e-mail una volta che saranno pronti da scaricare.",
@ -157,7 +157,7 @@
"searchSelect": "Fare clic o premere invio per selezionare questa lista", "searchSelect": "Fare clic o premere invio per selezionare questa lista",
"shared": "Liste Condivise", "shared": "Liste Condivise",
"create": { "create": {
"header": "New list", "header": "Nuova lista",
"titlePlaceholder": "Il titolo della lista va qui…", "titlePlaceholder": "Il titolo della lista va qui…",
"addTitleRequired": "Specifica un titolo.", "addTitleRequired": "Specifica un titolo.",
"createdSuccess": "La lista è stata creata correttamente.", "createdSuccess": "La lista è stata creata correttamente.",
@ -191,7 +191,7 @@
"duplicate": { "duplicate": {
"title": "Duplica questa lista", "title": "Duplica questa lista",
"label": "Duplica", "label": "Duplica",
"text": "Select a namespace which should hold the duplicated list:", "text": "Seleziona un namespace che dovrebbe contenere l'elenco duplicato:",
"success": "Lista duplicata." "success": "Lista duplicata."
}, },
"edit": { "edit": {
@ -279,23 +279,23 @@
"title": "Kanban", "title": "Kanban",
"limit": "Limite: {limit}", "limit": "Limite: {limit}",
"noLimit": "Non Impostato", "noLimit": "Non Impostato",
"doneBucket": "Done bucket", "doneBucket": "Colonna attività completate",
"doneBucketHint": "All tasks moved into this bucket will automatically marked as done.", "doneBucketHint": "Tutte le attività spostate in questa colonna verranno automaticamente contrassegnate come completate.",
"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.", "doneBucketHintExtended": "Tutte le attività spostate nella colonna attività completate saranno contrassegnate automaticamente come completate. Tutte le attività contrassegnate come completate altrove verranno anche spostate.",
"doneBucketSavedSuccess": "The done bucket has been saved successfully.", "doneBucketSavedSuccess": "Colonna attività completate salvata.",
"deleteLast": "You cannot remove the last bucket.", "deleteLast": "Impossibile eliminare l'ultima colonna.",
"addTaskPlaceholder": "Enter the new task title…", "addTaskPlaceholder": "Inserisci il nuovo titolo dell'attività…",
"addTask": "Aggiungi un'attività", "addTask": "Aggiungi un'attività",
"addAnotherTask": "Aggiungi un'altra attività", "addAnotherTask": "Aggiungi un'altra attività",
"addBucket": "Create a new bucket", "addBucket": "Crea una nuova colonna",
"addBucketPlaceholder": "Enter the new bucket title…", "addBucketPlaceholder": "Inserisci il titolo della nuova colonna…",
"deleteHeaderBucket": "Delete the bucket", "deleteHeaderBucket": "Elimina la colonna",
"deleteBucketText1": "Are you sure you want to delete this bucket?", "deleteBucketText1": "Confermi di voler eliminare questa colonna?",
"deleteBucketText2": "This will not delete any tasks but move them into the default bucket.", "deleteBucketText2": "Questo non eliminerà nessuna attività, ma la sposterà nel bucket predefinito.",
"deleteBucketSuccess": "The bucket has been deleted successfully.", "deleteBucketSuccess": "Colonna eliminata.",
"bucketTitleSavedSuccess": "The bucket title has been saved successfully.", "bucketTitleSavedSuccess": "Titolo della colonna salvato.",
"bucketLimitSavedSuccess": "The bucket limit been saved successfully.", "bucketLimitSavedSuccess": "Limite della colonna salvato.",
"collapse": "Collapse this bucket" "collapse": "Comprimi questa colonna"
}, },
"pseudo": { "pseudo": {
"favorites": { "favorites": {
@ -304,52 +304,52 @@
} }
}, },
"namespace": { "namespace": {
"title": "Namespaces & Lists", "title": "Namespace e Liste",
"namespace": "Namespace", "namespace": "Namespace",
"showArchived": "Show Archived", "showArchived": "Mostra Archiviati",
"noneAvailable": "You don't have any namespaces right now.", "noneAvailable": "Non hai alcun namespace in questo momento.",
"unarchive": "Un-Archive", "unarchive": "De-Archivia",
"archived": "Archived", "archived": "Archiviato",
"noLists": "This namespace does not contain any lists.", "noLists": "Questo namespace non contiene alcuna lista.",
"createList": "Create a new list in this namespace.", "createList": "Crea una nuova lista in questo namespace.",
"namespaces": "Namespaces", "namespaces": "Namespace",
"search": "Type to search for a namespace…", "search": "Digita per cercare un namespace…",
"create": { "create": {
"title": "New namespace", "title": "Nuovo namespace",
"titleRequired": "Please specify a title.", "titleRequired": "Specifica un titolo.",
"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.", "explanation": "Un namespace è una raccolta di liste che puoi condividere e che puoi usare per organizzare le tue liste. Infatti, ogni lista appartiene a un namespace.",
"tooltip": "What's a namespace?", "tooltip": "Che cos'è un namespace?",
"success": "The namespace was successfully created." "success": "Namespace creato."
}, },
"archive": { "archive": {
"titleArchive": "Archivia \"{namespace}\"", "titleArchive": "Archivia \"{namespace}\"",
"titleUnarchive": "Un-Archive \"{namespace}\"", "titleUnarchive": "Disarchivia \"{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.", "archiveText": "Non sarà possibile modificare questo namespace o creare nuove liste fino a quando non verrà disarchiviato. Questo archivierà anche tutte le liste in questo namespace.",
"unarchiveText": "You will be able to create new lists or edit it.", "unarchiveText": "Potrai creare nuove liste o modificarle.",
"success": "The namespace was successfully archived.", "success": "Namespace creato.",
"description": "If a namespace is archived, you cannot create new lists or edit it." "description": "Se un namespace è archiviato, non è possibile creare nuove liste o modificarlo."
}, },
"delete": { "delete": {
"title": "Delete \"{namespace}\"", "title": "Elimina \"{namespace}\"",
"text1": "Are you sure you want to delete this namespace and all of its contents?", "text1": "Sei sicuro di voler rimuovere questo namespace e tutto il relativo contenuto?",
"text2": "Questo include tutte le liste e le attività e NON PUÒ ESSERE RIPRISTINATO!", "text2": "Questo include tutte le liste e le attività e NON PUÒ ESSERE RIPRISTINATO!",
"success": "The namespace was successfully deleted." "success": "Namespace eliminato."
}, },
"edit": { "edit": {
"title": "Modifica \"{namespace}\"", "title": "Modifica \"{namespace}\"",
"success": "The namespace was successfully updated." "success": "Namespace aggiornato."
}, },
"share": { "share": {
"title": "Condividi \"{namespace}\"" "title": "Condividi \"{namespace}\""
}, },
"attributes": { "attributes": {
"title": "Namespace Title", "title": "Titolo del Namespace",
"titlePlaceholder": "The namespace title goes here…", "titlePlaceholder": "Il titolo del namespace va qui…",
"description": "Descrizione", "description": "Descrizione",
"descriptionPlaceholder": "The namespaces description goes here…", "descriptionPlaceholder": "La descrizione del namespace va qui…",
"color": "Colore", "color": "Colore",
"archived": "Is Archived", "archived": "Archiviato",
"isArchived": "This namespace is archived" "isArchived": "Questo namespace è archiviato"
}, },
"pseudo": { "pseudo": {
"sharedLists": { "sharedLists": {
@ -365,7 +365,7 @@
}, },
"filters": { "filters": {
"title": "Filtri", "title": "Filtri",
"clear": "Clear Filters", "clear": "Pulisci Filtri",
"attributes": { "attributes": {
"title": "Titolo", "title": "Titolo",
"titlePlaceholder": "Il titolo del filtro salvato va qui…", "titlePlaceholder": "Il titolo del filtro salvato va qui…",
@ -374,17 +374,17 @@
"includeNulls": "Includi attività che non hanno un valore impostato", "includeNulls": "Includi attività che non hanno un valore impostato",
"requireAll": "Tutti i filtri devono essere veri affinché l'attività venga mostrata", "requireAll": "Tutti i filtri devono essere veri affinché l'attività venga mostrata",
"showDoneTasks": "Mostra Attività Fatte", "showDoneTasks": "Mostra Attività Fatte",
"sortAlphabetically": "Sort Alphabetically", "sortAlphabetically": "Ordine alfabetico",
"enablePriority": "Abilita Filtro Per Priorità", "enablePriority": "Abilita Filtro Per Priorità",
"enablePercentDone": "Abilitare Filtro Per Percentuale Fatta", "enablePercentDone": "Abilitare Filtro Per Percentuale Fatta",
"dueDateRange": "Intervallo Data Di Scadenza", "dueDateRange": "Intervallo Data Di Scadenza",
"startDateRange": "Intervallo Data Iniziale", "startDateRange": "Intervallo Data Iniziale",
"endDateRange": "Intervallo Data Finale", "endDateRange": "Intervallo Data Finale",
"reminderRange": "Reminder Date Range" "reminderRange": "Intervallo date dei promemoria"
}, },
"create": { "create": {
"title": "New Saved Filter", "title": "Nuovo Filtro Salvato",
"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.", "description": "Un filtro salvato è una lista virtuale che viene calcolata da un insieme di filtri di volta in volta. Una volta creato, apparirà in un namespace speciale.",
"action": "Crea nuovo filtro salvato" "action": "Crea nuovo filtro salvato"
}, },
"delete": { "delete": {
@ -446,9 +446,9 @@
}, },
"navigation": { "navigation": {
"overview": "Panoramica", "overview": "Panoramica",
"upcoming": "Upcoming", "upcoming": "Prossimamente",
"settings": "Impostazioni", "settings": "Impostazioni",
"imprint": "Imprint", "imprint": "Informazioni legali",
"privacy": "Politica sulla Privacy" "privacy": "Politica sulla Privacy"
}, },
"misc": { "misc": {
@ -464,19 +464,19 @@
"searchPlaceholder": "Digita per cercare…", "searchPlaceholder": "Digita per cercare…",
"previous": "Precedente", "previous": "Precedente",
"next": "Successivo", "next": "Successivo",
"poweredBy": "Powered by Vikunja", "poweredBy": "Creato con Vikunja",
"info": "Info", "info": "Info",
"create": "Create", "create": "Crea",
"doit": "Fallo!", "doit": "Fallo!",
"saving": "Salvataggio…", "saving": "Salvataggio…",
"saved": "Salvato!", "saved": "Salvato!",
"default": "Predefinito", "default": "Predefinito",
"close": "Chiudi", "close": "Chiudi",
"download": "Scarica", "download": "Scarica",
"showMenu": "Show the menu", "showMenu": "Mostra il menu",
"hideMenu": "Hide the menu", "hideMenu": "Nascondi il menù",
"forExample": "For example:", "forExample": "Ad esempio:",
"welcomeBack": "Welcome Back!" "welcomeBack": "Bentornato!"
}, },
"input": { "input": {
"resetColor": "Ripristina Colore", "resetColor": "Ripristina Colore",
@ -485,9 +485,9 @@
"tomorrow": "Domani", "tomorrow": "Domani",
"nextMonday": "Lunedì Prossimo", "nextMonday": "Lunedì Prossimo",
"thisWeekend": "Questo fine settimana", "thisWeekend": "Questo fine settimana",
"laterThisWeek": "Later This Week", "laterThisWeek": "Alla fine di questa settimana",
"nextWeek": "Prossima Settimana", "nextWeek": "Prossima Settimana",
"chooseDate": "Choose a date" "chooseDate": "Seleziona una data"
}, },
"editor": { "editor": {
"edit": "Modifica", "edit": "Modifica",
@ -504,16 +504,16 @@
"quote": "Citazione", "quote": "Citazione",
"unorderedList": "Elenco puntato", "unorderedList": "Elenco puntato",
"orderedList": "Elenco numerato", "orderedList": "Elenco numerato",
"cleanBlock": "Clean Block", "cleanBlock": "Pulisci Blocco",
"link": "Link", "link": "Link",
"image": "Immagine", "image": "Immagine",
"table": "Tabella", "table": "Tabella",
"horizontalRule": "Horizontal Rule", "horizontalRule": "Divisore Orizzontale",
"sideBySide": "Side By Side", "sideBySide": "Affianca",
"guide": "Guide" "guide": "Guida"
}, },
"multiselect": { "multiselect": {
"createPlaceholder": "Create new", "createPlaceholder": "Crea nuovo",
"selectPlaceholder": "Clicca o premere invio per selezionare" "selectPlaceholder": "Clicca o premere invio per selezionare"
} }
}, },
@ -533,19 +533,19 @@
"titleDates": "Attività dal {from} al {to}", "titleDates": "Attività dal {from} al {to}",
"noDates": "Mostra attività senza date", "noDates": "Mostra attività senza date",
"current": "Attività attuali", "current": "Attività attuali",
"from": "Tasks from", "from": "Attività dal",
"until": "until", "until": "fino al",
"today": "Oggi", "today": "Oggi",
"nextWeek": "Settimana Prossima", "nextWeek": "Settimana Prossima",
"nextMonth": "Prossimo Mese", "nextMonth": "Prossimo Mese",
"noTasks": "Nothing to do — Have a nice day!" "noTasks": "Nessuna attività — Buona giornata!"
}, },
"detail": { "detail": {
"chooseDueDate": "Clicca qui per impostare una data di scadenza", "chooseDueDate": "Clicca qui per impostare una data di scadenza",
"chooseStartDate": "Clicca qui per impostare una data di inizio", "chooseStartDate": "Clicca qui per impostare una data di inizio",
"chooseEndDate": "Clicca qui per impostare una data di fine", "chooseEndDate": "Clicca qui per impostare una data di fine",
"move": "Sposta attività in un'altra lista", "move": "Sposta attività in un'altra lista",
"done": "Mark task done!", "done": "Segna attività fatta!",
"undone": "Segna come non completato", "undone": "Segna come non completato",
"created": "Creato {0} da {1}", "created": "Creato {0} da {1}",
"updated": "Aggiornato {0}", "updated": "Aggiornato {0}",
@ -554,21 +554,21 @@
"deleteSuccess": "L'attività è stata eliminata con successo.", "deleteSuccess": "L'attività è stata eliminata con successo.",
"belongsToList": "Questa attività appartiene alla lista '{list}'", "belongsToList": "Questa attività appartiene alla lista '{list}'",
"due": "Scadenza {at}", "due": "Scadenza {at}",
"closePopup": "Close popup", "closePopup": "Chiudi popup",
"delete": { "delete": {
"header": "Elimina questa attività", "header": "Elimina questa attività",
"text1": "Sei sicuro di voler eliminare questa attività?", "text1": "Sei sicuro di voler eliminare questa attività?",
"text2": "Questo rimuoverà anche tutti gli allegati, i promemoria e le relazioni associati a questa attività e non può essere ripristinato!" "text2": "Questo rimuoverà anche tutti gli allegati, i promemoria e le relazioni associati a questa attività e non può essere ripristinato!"
}, },
"actions": { "actions": {
"assign": "Assign to a user", "assign": "Assegna ad un utente",
"label": "Aggiungi etichette", "label": "Aggiungi etichette",
"priority": "Imposta Priorità", "priority": "Imposta Priorità",
"dueDate": "Imposta data di scadenza", "dueDate": "Imposta data di scadenza",
"startDate": "Imposta una data di inizio", "startDate": "Imposta una data di inizio",
"endDate": "Imposta una data di fine", "endDate": "Imposta una data di fine",
"reminders": "Imposta promemoria", "reminders": "Imposta promemoria",
"repeatAfter": "Set a repeating interval", "repeatAfter": "Imposta ricorrenza",
"percentDone": "Imposta Percentuale Completata", "percentDone": "Imposta Percentuale Completata",
"attachments": "Aggiungi allegati", "attachments": "Aggiungi allegati",
"relatedTasks": "Aggiungi attività collegate", "relatedTasks": "Aggiungi attività collegate",
@ -599,13 +599,13 @@
"updated": "Aggiornato" "updated": "Aggiornato"
}, },
"subscription": { "subscription": {
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.", "subscribedThroughParent": "Non puoi annullare l'iscrizione qui perché sei iscritto a questo {entity} attraverso il suo {parent}.",
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.", "subscribed": "Sei attualmente iscritto a questo {entity} e riceverai notifiche per le modifiche.",
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.", "notSubscribed": "Non sei iscritto a questo {entity} e non riceverai notifiche per le modifiche.",
"subscribe": "Subscribe", "subscribe": "Iscriviti",
"unsubscribe": "Unsubscribe", "unsubscribe": "Disiscriviti",
"subscribeSuccess": "You are now subscribed to this {entity}", "subscribeSuccess": "Ti sei iscritto a questo {entity}",
"unsubscribeSuccess": "You are now unsubscribed to this {entity}" "unsubscribeSuccess": "Ti sei disiscritto a questo {entity}"
}, },
"attachment": { "attachment": {
"title": "Allegati", "title": "Allegati",
@ -623,41 +623,41 @@
"comment": { "comment": {
"title": "Commenti", "title": "Commenti",
"loading": "Caricamento commenti…", "loading": "Caricamento commenti…",
"edited": "edited {date}", "edited": "modificato il {date}",
"creating": "Creazione del commento…", "creating": "Creazione del commento…",
"placeholder": "Aggiungi un commento…", "placeholder": "Aggiungi un commento…",
"comment": "Comment", "comment": "Commenta",
"delete": "Elimina questo commento", "delete": "Elimina questo commento",
"deleteText1": "Sei sicuro di voler eliminare questo commento?", "deleteText1": "Sei sicuro di voler eliminare questo commento?",
"deleteText2": "Questa azione non può essere annullata!", "deleteText2": "Questa azione non può essere annullata!",
"addedSuccess": "Il commento è stato aggiunto correttamente." "addedSuccess": "Il commento è stato aggiunto correttamente."
}, },
"deferDueDate": { "deferDueDate": {
"title": "Defer due date", "title": "Rinvia data di scadenza",
"1day": "1 giorno", "1day": "1 giorno",
"3days": "3 giorni", "3days": "3 giorni",
"1week": "1 settimana" "1week": "1 settimana"
}, },
"description": { "description": {
"placeholder": "Click here to enter a description…", "placeholder": "Clicca qui per inserire una descrizione…",
"empty": "No description available yet." "empty": "Nessuna descrizione."
}, },
"assignee": { "assignee": {
"placeholder": "Type to assign a user…", "placeholder": "Digita per assegnare un utente…",
"selectPlaceholder": "Assegna questo utente", "selectPlaceholder": "Assegna questo utente",
"assignSuccess": "The user has been assigned successfully.", "assignSuccess": "Utente assegnato.",
"unassignSuccess": "The user has been unassigned successfully." "unassignSuccess": "Utente disassegnato."
}, },
"label": { "label": {
"placeholder": "Type to add a new label…", "placeholder": "Digita per aggiungere una nuova etichetta…",
"createPlaceholder": "Add this as new label", "createPlaceholder": "Aggiungila come nuova etichetta",
"addSuccess": "Etichetta aggiunta.", "addSuccess": "Etichetta aggiunta.",
"createSuccess": "Etichetta creata.", "createSuccess": "Etichetta creata.",
"removeSuccess": "Etichetta eliminata.", "removeSuccess": "Etichetta eliminata.",
"addCreateSuccess": "Etichetta creata e aggiunta." "addCreateSuccess": "Etichetta creata e aggiunta."
}, },
"priority": { "priority": {
"unset": "Unset", "unset": "Azzera",
"low": "Bassa", "low": "Bassa",
"medium": "Media", "medium": "Media",
"high": "Alta", "high": "Alta",
@ -665,38 +665,38 @@
"doNow": "FARE ORA" "doNow": "FARE ORA"
}, },
"relation": { "relation": {
"add": "Add a New Task Relation", "add": "Aggiungi Attività Collegata",
"new": "New Task Relation", "new": "Nuova Attività Collegata",
"searchPlaceholder": "Type search for a new task to add as related…", "searchPlaceholder": "Digita per cercare un'attività da aggiungere come collegata…",
"createPlaceholder": "Add this as new related task", "createPlaceholder": "Aggiungi come attività collegata",
"differentList": "This task belongs to a different list.", "differentList": "Questa attività è di una lista diversa.",
"differentNamespace": "This task belongs to a different namespace.", "differentNamespace": "Questa attività appartiene ad un namespace diverso.",
"noneYet": "No task relations yet.", "noneYet": "Nessuna attività collegata.",
"delete": "Delete Task Relation", "delete": "Elimina Collegamento Attività",
"deleteText1": "Are you sure you want to delete this task relation?", "deleteText1": "Confermi di voler eliminare questo collegamento attività?",
"deleteText2": "Questa azione non può essere annullata!", "deleteText2": "Questa azione non può essere annullata!",
"select": "Select a relation kind", "select": "Seleziona un tipo di collegamento",
"kinds": { "kinds": {
"subtask": "Subtask | Subtasks", "subtask": "Sotto-attività | Sotto-attività",
"parenttask": "Parent Task | Parent Tasks", "parenttask": "Attività Principale | Attività Principale",
"related": "Related Task | Related Tasks", "related": "Attività Correlata | Attività Correlata",
"duplicateof": "Duplicato Di | Duplicati Di", "duplicateof": "Duplicato Di | Duplicati Di",
"duplicates": "Duplicates | Duplicates", "duplicates": "Duplicato | Duplicati",
"blocking": "Blocking | Blocking", "blocking": "Bloccante | Bloccanti",
"blocked": "Blocked By | Blocked By", "blocked": "Bloccato Da | Bloccati Da",
"precedes": "Precedes | Precedes", "precedes": "Precede | Precede",
"follows": "Follows | Follows", "follows": "Segue | Segue",
"copiedfrom": "Copied From | Copied From", "copiedfrom": "Copiata Da | Copiate Da",
"copiedto": "Copied To | Copied To" "copiedto": "Copiata In | Copiate In"
} }
}, },
"repeat": { "repeat": {
"everyDay": "Ogni Giorno", "everyDay": "Ogni Giorno",
"everyWeek": "Ogni Settimana", "everyWeek": "Ogni Settimana",
"everyMonth": "Ogni Mese", "everyMonth": "Ogni Mese",
"mode": "Repeat mode", "mode": "Modalità Ripetizione",
"monthly": "Mensilmente", "monthly": "Mensilmente",
"fromCurrentDate": "From Current Date", "fromCurrentDate": "Dalla Data Attuale",
"each": "Ogni", "each": "Ogni",
"specifyAmount": "Specifica una quantità…", "specifyAmount": "Specifica una quantità…",
"hours": "Ore", "hours": "Ore",
@ -706,32 +706,32 @@
"years": "Anni" "years": "Anni"
}, },
"quickAddMagic": { "quickAddMagic": {
"hint": "You can use Quick Add Magic", "hint": "Puoi usare l'Aggiunta Rapida Magica",
"what": "Cosa?", "what": "Cosa?",
"title": "Quick Add Magic", "title": "Aggiunta Rapida Magica",
"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.", "intro": "Quando si crea un'attività, è possibile utilizzare parole chiave speciali per aggiungere direttamente attributi all'attività appena creata. Questo permette di aggiungere gli attributi comuni molto più velocemente.",
"multiple": "Puoi usarlo più volte.", "multiple": "Puoi usarlo più volte.",
"label1": "To add a label, simply prefix the name of the label with {prefix}.", "label1": "Per aggiungere un'etichetta, basta aggiungere il nome dell'etichetta preceduto da {prefix}.",
"label2": "Vikunja will first check if the label already exist and create it if not.", "label2": "Vikunja controllerà prima se l'etichetta esiste già e nel caso la creerà.",
"label3": "To use spaces, simply add a \" around the label name.", "label3": "Per usare gli spazi, basta \" prima e dopo del nome dell'etichetta.",
"label4": "For example: {prefix}\"Label with spaces\".", "label4": "Per esempio: {prefix}\"Etichetta con spazi\".",
"priority1": "To set a task's priority, add a number 1-5, prefixed with a {prefix}.", "priority1": "Per impostare la priorità di un'attività, aggiungi un numero 1-5, preceduto da {prefix}.",
"priority2": "The higher the number, the higher the priority.", "priority2": "Più alto è il numero, più alta è la priorità.",
"assignees": "To directly assign the task to a user, add their username prefixed with {prefix} to the task.", "assignees": "Per assegnare direttamente l'attività a un utente, aggiungere il suo nome utente preceduto da {prefix} all'attività.",
"list1": "To set a list for the task to appear in, enter its name prefixed with {prefix}.", "list1": "Per impostare una lista di appartenenza all'attività, inserisci il suo nome prefisso con {prefix}.",
"list2": "This will return an error if the list does not exist.", "list2": "Ciò restituirà un errore se la lista non esiste.",
"dateAndTime": "Data e ora", "dateAndTime": "Data e ora",
"date": "Any date will be used as the due date of the new task. You can use dates in any of these formats:", "date": "Qualsiasi data verrà utilizzata come data di scadenza della nuova attività. È possibile utilizzare le date in uno qualsiasi di questi formati:",
"dateWeekday": "any weekday, will use the next date with that date", "dateWeekday": "qualsiasi giorno della settimana, userà la data più vicina",
"dateCurrentYear": "will use the current year", "dateCurrentYear": "userà lanno corrente",
"dateNth": "will use the {day}th of the current month", "dateNth": "userà il {day} del mese corrente",
"dateTime": "Combine any of the date formats with \"{time}\" (or {timePM}) to set a time.", "dateTime": "Combina uno qualsiasi dei formati di data con \"{time}\" (o {timePM}) per impostare un orario.",
"repeats": "Repeating tasks", "repeats": "Attività ricorrenti",
"repeatsDescription": "To set a task as repeating in an interval, simply add '{suffix}' to the task text. The amount needs to be a number and can be omitted to use just the type (see examples)." "repeatsDescription": "Per impostare un'attività come ricorrente in un intervallo, basta aggiungere '{suffix}' al testo dell'attività. La quantità deve essere un numero e può essere omesso per usare solo il tipo (vedi esempi)."
} }
}, },
"team": { "team": {
"title": "Teams", "title": "Gruppi",
"noTeams": "Non fai parte di nessun gruppo.", "noTeams": "Non fai parte di nessun gruppo.",
"create": { "create": {
"title": "Crea un nuovo gruppo", "title": "Crea un nuovo gruppo",
@ -746,23 +746,23 @@
"makeAdmin": "Rendi Amministratore", "makeAdmin": "Rendi Amministratore",
"success": "Gruppo aggiornato.", "success": "Gruppo aggiornato.",
"userAddedSuccess": "Membro del gruppo aggiunto.", "userAddedSuccess": "Membro del gruppo aggiunto.",
"madeMember": "The team member was successfully made member.", "madeMember": "Membro del gruppo reso membro.",
"madeAdmin": "The team member was successfully made admin.", "madeAdmin": "Membro del gruppo reso amministratore.",
"delete": { "delete": {
"header": "Elimina il gruppo", "header": "Elimina il gruppo",
"text1": "Sei sicuro di voler eliminare questo gruppo e tutti i suoi membri?", "text1": "Sei sicuro di voler eliminare questo gruppo e tutti i suoi membri?",
"text2": "All team members will lose access to lists and namespaces shared with this team. This CANNOT BE UNDONE!", "text2": "Tutti i membri del gruppo perderanno l'accesso alle liste e ai namespace condivisi con questo gruppo. NON PUÒ ESSERE RIPRISTINATO!",
"success": "Gruppo eliminato." "success": "Gruppo eliminato."
}, },
"deleteUser": { "deleteUser": {
"header": "Rimuovi un utente dal gruppo", "header": "Rimuovi un utente dal gruppo",
"text1": "Confermi di voler rimuovere questo utente dal gruppo?", "text1": "Confermi di voler rimuovere questo utente dal gruppo?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!", "text2": "Perderanno l'accesso a tutte le liste e i namespace a cui questo gruppo ha accesso. NON PUÒ ESSERE RIPRISTINATO!",
"success": "Utente rimosso dal gruppo." "success": "Utente rimosso dal gruppo."
} }
}, },
"attributes": { "attributes": {
"name": "Team Name", "name": "Nome Gruppo",
"namePlaceholder": "Il nome del gruppo va qui…", "namePlaceholder": "Il nome del gruppo va qui…",
"nameRequired": "Specifica un nome.", "nameRequired": "Specifica un nome.",
"description": "Descrizione", "description": "Descrizione",
@ -772,32 +772,32 @@
} }
}, },
"keyboardShortcuts": { "keyboardShortcuts": {
"title": "Keyboard Shortcuts", "title": "Tasti Rapidi",
"general": "General", "general": "Generali",
"allPages": "Queste scorciatoie funzionano in tutte le pagine.", "allPages": "Queste scorciatoie funzionano in tutte le pagine.",
"currentPageOnly": "Queste scorciatoie funzionano solo nella pagina attuale.", "currentPageOnly": "Queste scorciatoie funzionano solo nella pagina attuale.",
"toggleMenu": "Attiva/Disattiva Menu", "toggleMenu": "Attiva/Disattiva Menu",
"quickSearch": "Apri la barra di ricerca/azione rapida", "quickSearch": "Apri la barra di ricerca/azione rapida",
"then": "then", "then": "e dopo",
"task": { "task": {
"title": "Task Page", "title": "Pagina Attività",
"done": "Done", "done": "Fatto",
"assign": "Assign to a user", "assign": "Assegna a un utente",
"labels": "Add labels to this task", "labels": "Aggiungi etichette a questa attività",
"dueDate": "Change the due date of this task", "dueDate": "Modifica la data di scadenza di questa attività",
"attachment": "Add an attachment to this task", "attachment": "Aggiungi un allegato a questa attività",
"related": "Modify related tasks of this task" "related": "Modifica le attività collegate a questa"
}, },
"list": { "list": {
"title": "List Views", "title": "Viste Liste",
"switchToListView": "Switch to list view", "switchToListView": "Passa alla vista Lista",
"switchToGanttView": "Switch to gantt view", "switchToGanttView": "Passa alla vista Gantt",
"switchToKanbanView": "Switch to kanban view", "switchToKanbanView": "Passa alla vista Kanban",
"switchToTableView": "Switch to table view" "switchToTableView": "Passa alla vista Tabella"
} }
}, },
"update": { "update": {
"available": "There is an update for Vikunja available!", "available": "È disponibile un aggiornamento per Vikunja!",
"do": "Aggiorna Adesso" "do": "Aggiorna Adesso"
}, },
"menu": { "menu": {
@ -805,136 +805,136 @@
"archive": "Archivia", "archive": "Archivia",
"duplicate": "Duplica", "duplicate": "Duplica",
"delete": "Elimina", "delete": "Elimina",
"unarchive": "Un-Archive", "unarchive": "Disarchivia",
"setBackground": "Set background", "setBackground": "Imposta sfondo",
"share": "Condividi", "share": "Condividi",
"newList": "Nuova lista" "newList": "Nuova lista"
}, },
"apiConfig": { "apiConfig": {
"url": "URL Vikunja", "url": "URL Vikunja",
"urlPlaceholder": "es. http://localhost:8080", "urlPlaceholder": "es. http://localhost:8080",
"change": "change", "change": "modifica",
"use": "Using Vikunja installation at {0}", "use": "Usa l'installazione di Vikunja a {0}",
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.", "error": "Impossibile trovare o usare l'installazione di Vikunja su \"{domain}\". Prova per favore con un altro Url.",
"success": "Using Vikunja installation at \"{domain}\".", "success": "Utilizzando l'installazione di Vikunja su \"{domain}\".",
"urlRequired": "A url is required." "urlRequired": "L'URL è obbligatorio."
}, },
"loadingError": { "loadingError": {
"failed": "Loading failed, please {0}. If the error persists, please {1}.", "failed": "Caricamento non riuscito, si prega di {0}. Se l'errore persiste, per favore {1}.",
"tryAgain": "try again", "tryAgain": "riprova",
"contact": "contact us" "contact": "Contattaci"
}, },
"notification": { "notification": {
"title": "Notifications", "title": "Notifiche",
"none": "You don't have any notifications. Have a nice day!", "none": "Nessuna notifica. Buona giornata!",
"explainer": "Notifications will appear here when actions on namespaces, lists or tasks you subscribed to happen." "explainer": "Le notifiche appariranno qui quando le azioni su Namespace, liste o attività a cui hai sottoscritto la sottoscrizione avvengono."
}, },
"quickActions": { "quickActions": {
"commands": "Commands", "commands": "Comandi",
"placeholder": "Type a command or search…", "placeholder": "Digita un comando o cerca…",
"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.", "hint": "Puoi usare {list} per limitare la ricerca a una lista. Unisci {list} o {label} (etichette) alla ricerca per trovare un'attività con quelle etichette o in quella lista. Usa {assignee} per cercare solo i gruppi.",
"tasks": "Tasks", "tasks": "Attivitá",
"lists": "Liste", "lists": "Liste",
"teams": "Teams", "teams": "Gruppi",
"newList": "Enter the title of the new list…", "newList": "Inserisci il titolo della nuova lista…",
"newTask": "Enter the title of the new task…", "newTask": "Inserisci il titolo della nuova attività…",
"newNamespace": "Enter the title of the new namespace…", "newNamespace": "Inserisci il titolo del nuovo namespace…",
"newTeam": "Enter the name of the new team…", "newTeam": "Inserisci il nome del nuovo gruppo…",
"createTask": "Create a task in the current list ({title})", "createTask": "Crea un'attività nella lista attuale ({title})",
"createList": "Create a list in the current namespace ({title})", "createList": "Crea una lista nel namespace attuale ({title})",
"cmds": { "cmds": {
"newTask": "New task", "newTask": "Nuova attività",
"newList": "New list", "newList": "Nuova lista",
"newNamespace": "New namespace", "newNamespace": "Nuovo Namespace",
"newTeam": "New team" "newTeam": "Nuovo gruppo"
} }
}, },
"date": { "date": {
"locale": "en", "locale": "it",
"altFormatLong": "j M Y H:i", "altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y" "altFormatShort": "j M Y"
}, },
"error": { "error": {
"error": "Errore", "error": "Errore",
"success": "Success", "success": "Fatto",
"0001": "Non ti è permesso farlo.", "0001": "Non ti è permesso farlo.",
"1001": "A user with this username already exists.", "1001": "Esiste già un utente con questo nome utente.",
"1002": "Un utente con questo indirizzo e-mail esiste già.", "1002": "Un utente con questo indirizzo e-mail esiste già.",
"1004": "No username and password specified.", "1004": "Nessun nome utente e password specificati.",
"1005": "L'utente non esiste.", "1005": "L'utente non esiste.",
"1006": "Impossibile ottenere l'id utente.", "1006": "Impossibile ottenere l'id utente.",
"1008": "No password reset token provided.", "1008": "Nessun codice di reimpostazione password fornito.",
"1009": "Invalid password reset token.", "1009": "Codice di reimpostazione password non valido.",
"1010": "Token di conferma dell'e-mail non valido.", "1010": "Token di conferma dell'e-mail non valido.",
"1011": "Wrong username or password.", "1011": "Nome utente o password errati.",
"1012": "Indirizzo e-mail dell'utente non confermato.", "1012": "Indirizzo e-mail dell'utente non confermato.",
"1013": "La nuova password è vuota.", "1013": "La nuova password è vuota.",
"1014": "La vecchia password è vuota.", "1014": "La vecchia password è vuota.",
"1015": "Autenticazione TOTP già abilitata per questo utente.", "1015": "Autenticazione TOTP già abilitata per questo utente.",
"1016": "Autenticazione TOTP non abilitata per questo utente.", "1016": "Autenticazione TOTP non abilitata per questo utente.",
"1017": "Codice TOTP non valido.", "1017": "Codice TOTP non valido.",
"1018": "The user avatar type setting is invalid.", "1018": "L'impostazione del tipo di avatar utente non è valida.",
"2001": "L'ID non può essere vuoto o 0.", "2001": "L'ID non può essere vuoto o 0.",
"2002": "Alcuni dati della richiesta non erano validi.", "2002": "Alcuni dati della richiesta non erano validi.",
"3001": "La lista non esiste.", "3001": "La lista non esiste.",
"3004": "You need to have read permissions on that list to perform that action.", "3004": "Devi avere i permessi di lettura su quella lista per eseguire quell'azione.",
"3005": "Il titolo della lista non può essere vuoto.", "3005": "Il titolo della lista non può essere vuoto.",
"3006": "The list share does not exist.", "3006": "La condivisione della lista non esiste.",
"3007": "Esiste già una lista con questo identificatore.", "3007": "Esiste già una lista con questo identificatore.",
"3008": "The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list.", "3008": "La lista è archiviata e può quindi essere consultata solo in sola lettura. Questo vale anche per tutte le attività associate a questa lista.",
"4001": "The list task text cannot be empty.", "4001": "Il testo delle attività della lista non può essere vuoto.",
"4002": "The list task does not exist.", "4002": "Lista di attività non esistente.",
"4003": "Tutte le attività di modifica in blocco devono appartenere alla stessa lista.", "4003": "Tutte le attività di modifica in blocco devono appartenere alla stessa lista.",
"4004": "Hai bisogno di almeno un'attività quando si modificano in blocco le attività.", "4004": "Hai bisogno di almeno un'attività quando si modificano in blocco le attività.",
"4005": "Non hai il permesso di vedere l'attività.", "4005": "Non hai il permesso di vedere l'attività.",
"4006": "You can't set a parent task as the task itself.", "4006": "Non è possibile impostare un'attività principale come l'attività stessa.",
"4007": "You can't create a task relation with an invalid kind of relation.", "4007": "Non è possibile creare una relazione di attività con un tipo di relazione non valido.",
"4008": "You can't create a task relation which already exists.", "4008": "Non è possibile creare una relazione di attività già esistente.",
"4009": "The task relation does not exist.", "4009": "La relazione di attività non esiste.",
"4010": "Cannot relate a task with itself.", "4010": "Non è possibile relazionare un'attività con se stessa.",
"4011": "The task attachment does not exist.", "4011": "L'allegato dell'attività non esiste.",
"4012": "The task attachment is too large.", "4012": "L'allegato dell'attività è troppo grande.",
"4013": "The task sort param is invalid.", "4013": "Il parametro di ordinamento dei task non è valido.",
"4014": "The task sort order is invalid.", "4014": "L' ordinamento dei task non è valido.",
"4015": "The task comment does not exist.", "4015": "Il commento all'attività non esiste.",
"4016": "Invalid task field.", "4016": "Campo attività non valido.",
"4017": "Invalid task filter comparator.", "4017": "Comparatore di filtri attività non valido.",
"4018": "Invalid task filter concatinator.", "4018": "Concatenatore filtro attività non valido.",
"4019": "Invalid task filter value.", "4019": "Filtro attività non valido.",
"5001": "The namespace does not exist.", "5001": "Il namespace non esiste.",
"5003": "You do not have access to the specified namespace.", "5003": "Non hai accesso a questo namespace.",
"5006": "The namespace name cannot be empty.", "5006": "Il nome del namespace non può essere vuoto.",
"5009": "You need to have namespace read access to perform that action.", "5009": "Devi avere accesso in lettura al namespace per effettuare questa operazione.",
"5010": "This team does not have access to that namespace.", "5010": "Il tuo gruppo non ha accesso a questo namespace.",
"5011": "This user has already access to that namespace.", "5011": "Questo utente ha già accesso a quel namespace.",
"5012": "The namespace is archived and can therefore only be accessed read only.", "5012": "Il namespace è archiviato e può quindi essere accessibile solo in sola lettura.",
"6001": "The team name cannot be empty.", "6001": "Il nome del gruppo non può essere vuoto.",
"6002": "The team does not exist.", "6002": "Gruppo non esistente.",
"6004": "The team already has access to that namespace or list.", "6004": "Il team ha già accesso a questo namespace o lista.",
"6005": "The user is already a member of that team.", "6005": "L'utente è già membro di quel gruppo.",
"6006": "Cannot delete the last team member.", "6006": "Non è possibile eliminare l'ultimo membro del gruppo.",
"6007": "The team does not have access to the list to perform that action.", "6007": "Il gruppo non ha accesso alla lista per eseguire quell'azione.",
"7002": "The user already has access to that list.", "7002": "L'utente ha già accesso a quella lista.",
"7003": "Non hai accesso a quella lista.", "7003": "Non hai accesso a quella lista.",
"8001": "Questa etichetta esiste già in quell'attività.", "8001": "Questa etichetta esiste già in quell'attività.",
"8002": "L'etichetta non esiste.", "8002": "L'etichetta non esiste.",
"8003": "Non hai accesso a questa etichetta.", "8003": "Non hai accesso a questa etichetta.",
"9001": "The right is invalid.", "9001": "Permesso non valido.",
"10001": "The bucket does not exist.", "10001": "Colonna non esistente.",
"10002": "The bucket does not belong to that list.", "10002": "La colonna non appartiene a quella lista.",
"10003": "You cannot remove the last bucket on a list.", "10003": "Non puoi rimuovere l'ultima colonna di una lista.",
"10004": "You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold.", "10004": "Non puoi aggiungere l'attività a questa colonna perché ha già superato il limite di attività che può contenere.",
"10005": "There can be only one done bucket per list.", "10005": "Ci può essere solo una colonna completati per lista.",
"11001": "The saved filter does not exist.", "11001": "Filtro salvato non esistente.",
"11002": "Saved filters are not available for link shares.", "11002": "I filtri salvati non sono disponibili per i link di condivisione.",
"12001": "The subscription entity type is invalid.", "12001": "Il tipo di entità sottoscritto non è valido.",
"12002": "You are already subscribed to the entity itself or a parent entity.", "12002": "Sei già iscritto all'entità stessa o a un'entità principale.",
"13001": "This link share requires a password for authentication, but none was provided.", "13001": "Questa condivisione di link richiede una password per l'autenticazione, ma non è stato inserita.",
"13002": "The provided link share password was invalid." "13002": "La password inserita per il link di condivisione è valida."
}, },
"about": { "about": {
"title": "About", "title": "Informazioni",
"frontendVersion": "Frontend Version: {version}", "frontendVersion": "Versione Frontend: {version}",
"apiVersion": "API Version: {version}" "apiVersion": "Versione API: {version}"
} }
} }

View File

@ -899,7 +899,7 @@
"4015": "The task comment does not exist.", "4015": "The task comment does not exist.",
"4016": "Invalid task field.", "4016": "Invalid task field.",
"4017": "Invalid task filter comparator.", "4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.", "4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.", "4019": "Invalid task filter value.",
"5001": "The namespace does not exist.", "5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.", "5003": "You do not have access to the specified namespace.",

View File

@ -899,7 +899,7 @@
"4015": "The task comment does not exist.", "4015": "The task comment does not exist.",
"4016": "Invalid task field.", "4016": "Invalid task field.",
"4017": "Invalid task filter comparator.", "4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.", "4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.", "4019": "Invalid task filter value.",
"5001": "The namespace does not exist.", "5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.", "5003": "You do not have access to the specified namespace.",

View File

@ -899,7 +899,7 @@
"4015": "The task comment does not exist.", "4015": "The task comment does not exist.",
"4016": "Invalid task field.", "4016": "Invalid task field.",
"4017": "Invalid task filter comparator.", "4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.", "4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.", "4019": "Invalid task filter value.",
"5001": "The namespace does not exist.", "5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.", "5003": "You do not have access to the specified namespace.",

View File

@ -899,7 +899,7 @@
"4015": "The task comment does not exist.", "4015": "The task comment does not exist.",
"4016": "Invalid task field.", "4016": "Invalid task field.",
"4017": "Invalid task filter comparator.", "4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.", "4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.", "4019": "Invalid task filter value.",
"5001": "The namespace does not exist.", "5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.", "5003": "You do not have access to the specified namespace.",

View File

@ -899,7 +899,7 @@
"4015": "Комментарий не существует.", "4015": "Комментарий не существует.",
"4016": "Неверное поле задачи.", "4016": "Неверное поле задачи.",
"4017": "Неверный сравнитель фильтров задач.", "4017": "Неверный сравнитель фильтров задач.",
"4018": "Неверный соединитель фильтров задач.", "4018": "Invalid task filter concatenator.",
"4019": "Неверное значение фильтра задач.", "4019": "Неверное значение фильтра задач.",
"5001": "Пространство имён не существует.", "5001": "Пространство имён не существует.",
"5003": "Нет доступа к указанному пространству имён.", "5003": "Нет доступа к указанному пространству имён.",

View File

@ -899,7 +899,7 @@
"4015": "The task comment does not exist.", "4015": "The task comment does not exist.",
"4016": "Invalid task field.", "4016": "Invalid task field.",
"4017": "Invalid task filter comparator.", "4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.", "4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.", "4019": "Invalid task filter value.",
"5001": "The namespace does not exist.", "5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.", "5003": "You do not have access to the specified namespace.",

View File

@ -899,7 +899,7 @@
"4015": "The task comment does not exist.", "4015": "The task comment does not exist.",
"4016": "Invalid task field.", "4016": "Invalid task field.",
"4017": "Invalid task filter comparator.", "4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.", "4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.", "4019": "Invalid task filter value.",
"5001": "The namespace does not exist.", "5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.", "5003": "You do not have access to the specified namespace.",

View File

@ -899,7 +899,7 @@
"4015": "Bình luận không tồn tại.", "4015": "Bình luận không tồn tại.",
"4016": "Trường công việc không hợp lệ.", "4016": "Trường công việc không hợp lệ.",
"4017": "Bộ so sánh bộ lọc công việc không hợp lệ.", "4017": "Bộ so sánh bộ lọc công việc không hợp lệ.",
"4018": "Bộ lọc kết hợp không hợp lệ.", "4018": "Invalid task filter concatenator.",
"4019": "Giá trị bộ lọc công việc không hợp lệ.", "4019": "Giá trị bộ lọc công việc không hợp lệ.",
"5001": "Góc làm việc không có nữa.", "5001": "Góc làm việc không có nữa.",
"5003": "Bạn chưa được phép bước vào vào góc làm việc được chỉ định.", "5003": "Bạn chưa được phép bước vào vào góc làm việc được chỉ định.",

View File

@ -18,7 +18,7 @@ declare global {
} }
} }
import {formatDate, formatDateShort, formatDateLong, formatDateSince} from '@/helpers/time/formatDate' import {formatDate, formatDateShort, formatDateLong, formatDateSince, formatISO} from '@/helpers/time/formatDate'
// @ts-ignore // @ts-ignore
import {VERSION} from './version.json' import {VERSION} from './version.json'
@ -52,6 +52,7 @@ app.use(Notifications)
// directives // directives
import focus from '@/directives/focus' import focus from '@/directives/focus'
// @ts-ignore The export does exist, ts just doesn't find it.
import { VTooltip } from 'v-tooltip' import { VTooltip } from 'v-tooltip'
import 'v-tooltip/dist/v-tooltip.css' import 'v-tooltip/dist/v-tooltip.css'
import shortcut from '@/directives/shortcut' import shortcut from '@/directives/shortcut'
@ -84,6 +85,7 @@ app.mixin({
format: formatDate, format: formatDate,
formatDate: formatDateLong, formatDate: formatDateLong,
formatDateShort: formatDateShort, formatDateShort: formatDateShort,
formatISO,
getNamespaceTitle, getNamespaceTitle,
getListTitle, getListTitle,
setTitle, setTitle,

View File

@ -226,7 +226,7 @@ export default {
commit('info', info) commit('info', info)
commit('lastUserRefresh') commit('lastUserRefresh')
if (typeof info.settings.language !== 'undefined') { if (typeof info.settings.language === 'undefined' || info.settings.language === '') {
// save current language // save current language
await dispatch('saveUserSettings', { await dispatch('saveUserSettings', {
settings: { settings: {

View File

@ -23,8 +23,6 @@ export default {
return return
} }
// FIXME: direct manipulation of the prop
// might not be a problem since this is happening in the mutation
if (!namespace.lists || namespace.lists.length === 0) { if (!namespace.lists || namespace.lists.length === 0) {
namespace.lists = state.namespaces[namespaceIndex].lists namespace.lists = state.namespaces[namespaceIndex].lists
} }
@ -136,8 +134,8 @@ export default {
}, },
loadNamespacesIfFavoritesDontExist(ctx) { loadNamespacesIfFavoritesDontExist(ctx) {
// The first namespace should be the one holding all favorites // The first or second namespace should be the one holding all favorites
if (ctx.state.namespaces[0].id !== -2) { if (ctx.state.namespaces[0].id !== -2 && ctx.state.namespaces[1]?.id !== -2) {
return ctx.dispatch('loadNamespaces') return ctx.dispatch('loadNamespaces')
} }
}, },

View File

@ -179,9 +179,18 @@ export default {
console.debug('Could not add label to task in kanban, task not found', t) console.debug('Could not add label to task in kanban, task not found', t)
return r return r
} }
// FIXME: direct store manipulation (task)
t.task.labels.push(label) const labels = [...t.task.labels]
ctx.commit('kanban/setTaskInBucketByIndex', t, { root: true }) labels.push(label)
ctx.commit('kanban/setTaskInBucketByIndex', {
task: {
labels,
...t.task,
},
...t,
}, { root: true })
return r return r
}, },
@ -200,15 +209,21 @@ export default {
} }
// Remove the label from the list // Remove the label from the list
for (const l in t.task.labels) { const labels = [...t.task.labels]
if (t.task.labels[l].id === label.id) { for (const l in labels) {
// FIXME: direct store manipulation (task) if (labels[l].id === label.id) {
t.task.labels.splice(l, 1) labels.splice(l, 1)
break break
} }
} }
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true}) ctx.commit('kanban/setTaskInBucketByIndex', {
task: {
labels,
...t.task,
},
...t,
}, {root: true})
return response return response
}, },

View File

@ -60,9 +60,9 @@
--danger: hsla(var(--danger-h), var(--danger-s), var(--danger-l), var(--danger-a)); --danger: hsla(var(--danger-h), var(--danger-s), var(--danger-l), var(--danger-a));
// var(--primary) / $blue is #1973ff // var(--primary) / $blue is #1973ff
--primary-h: 216.5deg; --primary-h: 217deg;
--primary-s: 100%; --primary-s: 98%;
--primary-l: 54.9%; --primary-l: 53%;
--primary-a: 1; --primary-a: 1;
--primary-hsl: var(--primary-h), var(--primary-s), var(--primary-l); --primary-hsl: var(--primary-h), var(--primary-s), var(--primary-l);
--primary: hsla(var(--primary-h), var(--primary-s), var(--primary-l), var(--primary-a)); --primary: hsla(var(--primary-h), var(--primary-s), var(--primary-l), var(--primary-a));
@ -122,5 +122,10 @@
// Custom color variables we need to override // Custom color variables we need to override
--card-border-color: hsla(var(--grey-100-hsl), 0.3); --card-border-color: hsla(var(--grey-100-hsl), 0.3);
--logo-text-color: var(--grey-700); --logo-text-color: var(--grey-700);
// Slightly different primary color to make sure it has a sufficent contrast ratio
--primary-h: 217deg;
--primary-s: 98%;
--primary-l: 58%;
} }
} }

4
src/types/faker.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module '@faker-js/faker' {
import faker from 'faker'
export default faker
}

View File

@ -254,4 +254,13 @@ export default {
background-color: var(--primary-dark); background-color: var(--primary-dark);
} }
} }
@media (prefers-reduced-motion: reduce) {
@keyframes wave {
10% {
transform: translate(0, 0);
background-color: var(--primary);
}
}
}
</style> </style>

View File

@ -22,7 +22,7 @@
</router-link> </router-link>
</p> </p>
<div :key="`n${n.id}`" class="namespace" v-for="n in namespaces"> <section :key="`n${n.id}`" class="namespace" v-for="n in namespaces">
<x-button <x-button
:to="{name: 'list.create', params: {id: n.id}}" :to="{name: 'list.create', params: {id: n.id}}"
class="is-pulled-right" class="is-pulled-right"
@ -64,7 +64,7 @@
:show-archived="showArchived" :show-archived="showArchived"
/> />
</div> </div>
</div> </section>
</div> </div>
</template> </template>

View File

@ -6,7 +6,9 @@
<template #header><span>{{ title }}</span></template> <template #header><span>{{ title }}</span></template>
<template #text> <template #text>
<p>{{ list.isArchived ? $t('namespace.archive.unarchiveText') : $t('namespace.archive.archiveText') }}</p> <p>
{{ namespace.isArchived ? $t('namespace.archive.unarchiveText') : $t('namespace.archive.archiveText')}}
</p>
</template> </template>
</modal> </modal>
</template> </template>
@ -27,17 +29,18 @@ export default {
created() { created() {
this.namespace = this.$store.getters['namespaces/getNamespaceById'](this.$route.params.id) this.namespace = this.$store.getters['namespaces/getNamespaceById'](this.$route.params.id)
this.title = this.namespace.isArchived ? this.title = this.namespace.isArchived ?
this.$t('namespace.archive.titleUnarchive', { namespace: this.namespace.title }) : this.$t('namespace.archive.titleUnarchive', {namespace: this.namespace.title}) :
this.$t('namespace.archive.titleArchive', { namespace: this.namespace.title }) this.$t('namespace.archive.titleArchive', {namespace: this.namespace.title})
this.setTitle(this.title) this.setTitle(this.title)
}, },
methods: { methods: {
async archiveNamespace() { async archiveNamespace() {
this.namespace.isArchived = !this.namespace.isArchived
try { try {
const namespace = await this.namespaceService.update(this.namespace) const namespace = await this.namespaceService.update({
...this.namespace,
isArchived: !this.namespace.isArchived,
})
this.$store.commit('namespaces/setNamespaceById', namespace) this.$store.commit('namespaces/setNamespaceById', namespace)
this.$message.success({message: this.$t('namespace.archive.success')}) this.$message.success({message: this.$t('namespace.archive.success')})
} finally { } finally {

View File

@ -44,7 +44,7 @@ import Message from '@/components/misc/message.vue'
const {t} = useI18n() const {t} = useI18n()
useTitle(t('sharing.authenticating')) useTitle(t('sharing.authenticating'))
async function useAuth() { function useAuth() {
const store = useStore() const store = useStore()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -75,21 +75,21 @@ async function useAuth() {
password: password.value, password: password.value,
}) })
router.push({name: 'list.list', params: {listId}}) router.push({name: 'list.list', params: {listId}})
} catch (e) { } catch (e: any) {
if (e.response?.data?.code === 13001) { if (e.response?.data?.code === 13001) {
authenticateWithPassword.value = true authenticateWithPassword.value = true
return return
} }
// TODO: Put this logic in a global errorMessage handler method which checks all auth codes // TODO: Put this logic in a global errorMessage handler method which checks all auth codes
let errorMessage = t('sharing.error') let err = t('sharing.error')
if (e.response?.data?.message) { if (e.response?.data?.message) {
errorMessage = e.response.data.message err = e.response.data.message
} }
if (e.response?.data?.code === 13002) { if (e.response?.data?.code === 13002) {
errorMessage = t('sharing.invalidPassword') err = t('sharing.invalidPassword')
} }
errorMessage.value = errorMessage errorMessage.value = err
} finally { } finally {
loading.value = false loading.value = false
} }

View File

@ -386,22 +386,28 @@
<!-- Created / Updated [by] --> <!-- Created / Updated [by] -->
<p class="created"> <p class="created">
<i18n-t keypath="task.detail.created"> <time :datetime="formatISO(task.created)" v-tooltip="formatDate(task.created)">
<span v-tooltip="formatDate(task.created)">{{ formatDateSince(task.created) }}</span> <i18n-t keypath="task.detail.created">
{{ task.createdBy.getDisplayName() }} <span>{{ formatDateSince(task.created) }}</span>
</i18n-t> {{ task.createdBy.getDisplayName() }}
</i18n-t>
</time>
<template v-if="+new Date(task.created) !== +new Date(task.updated)"> <template v-if="+new Date(task.created) !== +new Date(task.updated)">
<br/> <br/>
<!-- Computed properties to show the actual date every time it gets updated --> <!-- Computed properties to show the actual date every time it gets updated -->
<i18n-t keypath="task.detail.updated"> <time :datetime="formatISO(task.updated)" v-tooltip="updatedFormatted">
<span v-tooltip="updatedFormatted">{{ updatedSince }}</span> <i18n-t keypath="task.detail.updated">
</i18n-t> <span>{{ updatedSince }}</span>
</i18n-t>
</time>
</template> </template>
<template v-if="task.done"> <template v-if="task.done">
<br/> <br/>
<i18n-t keypath="task.detail.doneAt"> <time :datetime="formatISO(task.doneAt)" v-tooltip="doneFormatted">
<span v-tooltip="doneFormatted">{{ doneSince }}</span> <i18n-t keypath="task.detail.doneAt">
</i18n-t> <span>{{ doneSince }}</span>
</i18n-t>
</time>
</template> </template>
</p> </p>
</div> </div>
@ -589,6 +595,9 @@ export default {
} }
}, },
scrollToHeading() { scrollToHeading() {
if(!this.$refs?.heading?.$el) {
return
}
this.$refs.heading.$el.scrollIntoView({block: 'center'}) this.$refs.heading.$el.scrollIntoView({block: 'center'})
}, },
setActiveFields() { setActiveFields() {
@ -931,4 +940,12 @@ $flash-background-duration: 750ms;
background: transparent; background: transparent;
} }
} }
@media (prefers-reduced-motion: reduce) {
@keyframes flash-background {
0% {
background: transparent;
}
}
}
</style> </style>

1520
yarn.lock

File diff suppressed because it is too large Load Diff