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
@ -81,18 +85,20 @@
<!-- <!--
NOTE: a v-model / computed setter is not possible, since the updateActiveLists function NOTE: a v-model / computed setter is not possible, since the updateActiveLists function
triggered by the change needs to have access to the current namespace triggered by the change needs to have access to the current namespace
--> -->
<draggable <draggable
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;
} }
} }
@ -482,7 +499,7 @@ $vikunja-nav-selected-width: 0.4rem;
height: 1rem; height: 1rem;
vertical-align: middle; vertical-align: middle;
padding-right: 0.5rem; padding-right: 0.5rem;
&.handle { &.handle {
opacity: 0; opacity: 0;
transition: opacity $transition; transition: opacity $transition;
@ -490,7 +507,7 @@ $vikunja-nav-selected-width: 0.4rem;
cursor: grab; cursor: grab;
} }
} }
&:hover .icon.handle { &:hover .icon.handle {
opacity: 1; opacity: 1;
} }
@ -542,7 +559,7 @@ $vikunja-nav-selected-width: 0.4rem;
span.list-menu-link, li > a { span.list-menu-link, li > a {
padding-left: 2rem; padding-left: 2rem;
display: inline-block; display: inline-block;
.icon { .icon {
padding-bottom: .25rem; padding-bottom: .25rem;
} }

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

@ -4,9 +4,11 @@
@submit="archiveNamespace()" @submit="archiveNamespace()"
> >
<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