Compare commits

..

50 commits

Author SHA1 Message Date
Dominik Pschenitschni
1d495b8603
feat: update ganttastic version 2022-10-09 13:29:27 +02:00
kolaente
0f1c5e9394
fix(tests): adjust gantt rows identifier 2022-10-05 15:21:19 +02:00
kolaente
7745f16893
chore: update lockfile 2022-10-05 15:15:55 +02:00
kolaente
97a6b2f3d1
Merge branch 'main' into feature/ganttastic
# Conflicts:
#	package.json
#	pnpm-lock.yaml
#	src/components/tasks/gantt-component.vue
2022-10-05 15:15:03 +02:00
kolaente
62dadc791c
fix: correctly import all components 2022-10-02 14:12:10 +02:00
kolaente
bb280b811c
fix: use base store 2022-10-02 14:09:18 +02:00
kolaente
6db97d1ba1
Merge branch 'main' into feature/ganttastic 2022-10-02 14:01:23 +02:00
kolaente
1bbdd3b117
Merge branch 'main' into feature/ganttastic
# Conflicts:
#	pnpm-lock.yaml
#	src/components/tasks/gantt-component.vue
2022-10-02 00:24:10 +02:00
Dominik Pschenitschni
6cb8d7a3f5
fix imports 2022-09-30 19:17:36 +02:00
Dominik Pschenitschni
c7a3c18972
Merge branch 'main' into feature/ganttastic
# Conflicts:
#	pnpm-lock.yaml
#	src/main.ts
2022-09-30 19:15:16 +02:00
Dominik Pschenitschni
a1e280e47b
Merge branch 'main' into feature/ganttastic
# Conflicts:
#	pnpm-lock.yaml
2022-09-28 18:00:32 +02:00
Dominik Pschenitschni
879dfb74b6
Merge branch 'main' into feature/ganttastic
# Conflicts:
#	src/main.ts
#	yarn.lock
2022-09-27 16:14:12 +02:00
kolaente
bd06f725be
Merge branch 'main' into feature/ganttastic
# Conflicts:
#	package.json
#	src/components/tasks/gantt-component.vue
#	src/main.ts
2022-09-08 13:53:36 +02:00
Dominik Pschenitschni
7ab4ff2d9e
feat: review changes
move TaskForm in separate component, improve types
2022-08-16 23:25:24 +02:00
kolaente
c80e3b57e4
fix: lint 2022-08-16 23:25:24 +02:00
kolaente
daaa7d3864
fix: remove precision setting 2022-08-16 23:25:24 +02:00
kolaente
cf67edb4a6
chore: don't use ref when not nessecary 2022-08-16 23:25:24 +02:00
kolaente
79e332e518
chore: add types for template ref 2022-08-16 23:25:24 +02:00
kolaente
7c9e98fdf6
chore: don't use for..in 2022-08-16 23:25:24 +02:00
kolaente
5dac96a2d5
feat: only use one watcher 2022-08-16 23:25:23 +02:00
kolaente
f5c7b5be82
chore: define types 2022-08-16 23:25:23 +02:00
kolaente
f3bb23cf14
chore: don't set required if there's a default value 2022-08-16 23:25:23 +02:00
kolaente
9431c13a7f
chore: uppercase const 2022-08-16 23:25:23 +02:00
kolaente
c4d5d409d4
chore: use @/models 2022-08-16 23:25:23 +02:00
kolaente
77ed7a5d91
fix: use inherit for font family 2022-08-16 23:25:23 +02:00
kolaente
ede3dec1d6
chore: use Loading component 2022-08-16 23:25:23 +02:00
kolaente
09bdf76aa4
chore: update ganttastic 2022-08-16 23:25:23 +02:00
kolaente
fb56e890e6
feat: increase the default date range 2022-08-16 23:25:23 +02:00
kolaente
3a32501064
feat: create task when pressing the button 2022-08-16 23:25:23 +02:00
kolaente
7d61635182
fix: make tests work again with new selectors 2022-08-16 23:25:23 +02:00
kolaente
0a914e37ed
chore: remove old component and dependencies 2022-08-16 23:25:22 +02:00
kolaente
f142d72da1
feat: loading animation 2022-08-16 23:25:22 +02:00
kolaente
4e0c69d751
feat: handle changing props 2022-08-16 23:25:22 +02:00
kolaente
c39e9d5c62
feat: show done tasks strikethrough 2022-08-16 23:25:22 +02:00
kolaente
af529eef0a
feat: update task in gantt bar after dragging to make sure it changes its color 2022-08-16 23:25:22 +02:00
kolaente
f77f14a91f
fix: make sure the date format is actually valid 2022-08-16 23:25:22 +02:00
kolaente
edaead1d8f
fix: handle bar styling so they can actually be used 2022-08-16 23:25:22 +02:00
kolaente
90a06019ce
chore: cleanup 2022-08-16 23:25:22 +02:00
kolaente
da853a914c
feat: styling 2022-08-16 23:25:22 +02:00
kolaente
ecaa09285b
chore: use width property 2022-08-16 23:25:22 +02:00
kolaente
40f7871f1b
feat: scroll 2022-08-16 23:25:22 +02:00
kolaente
c9c9056baf
feat: add open task detail when double clicking 2022-08-16 23:25:21 +02:00
kolaente
7f3c754389
fix: new task input styling 2022-08-16 23:25:21 +02:00
kolaente
ed338cefcd
chore: use flatpickr range instead of two datepickers 2022-08-16 23:25:21 +02:00
kolaente
b6c776592b
feat: create new tasks 2022-08-16 23:25:21 +02:00
kolaente
e711df758c
feat: dynamically set default date 2022-08-16 23:25:21 +02:00
kolaente
3f4509a6f9
feat: dynamically set default date 2022-08-16 23:25:21 +02:00
kolaente
5e3e79c01b
feat: only load tasks which start in the currently selected range 2022-08-16 23:25:21 +02:00
kolaente
9501592719
feat: allow passing props down to the gantt component 2022-08-16 23:25:21 +02:00
kolaente
a0e6b9643b
feat: add basic implementation of ganttastic 2022-08-16 23:25:19 +02:00
55 changed files with 2710 additions and 3757 deletions

View file

@ -11,7 +11,7 @@ describe('List View Gantt', () => {
const tasks = TaskFactory.create(1) const tasks = TaskFactory.create(1)
cy.visit('/lists/1/gantt') cy.visit('/lists/1/gantt')
cy.get('.gantt-chart .tasks') cy.get('.g-gantt-rows-container')
.should('not.contain', tasks[0].title) .should('not.contain', tasks[0].title)
}) })
@ -25,7 +25,7 @@ describe('List View Gantt', () => {
cy.visit('/lists/1/gantt') cy.visit('/lists/1/gantt')
cy.get('.gantt-chart .months') cy.get('.g-timeunits-container')
.should('contain', format(now, 'MMMM')) .should('contain', format(now, 'MMMM'))
.should('contain', format(nextMonth, 'MMMM')) .should('contain', format(nextMonth, 'MMMM'))
}) })
@ -38,14 +38,13 @@ describe('List View Gantt', () => {
}) })
cy.visit('/lists/1/gantt') cy.visit('/lists/1/gantt')
cy.get('.gantt-chart .tasks') cy.get('.g-gantt-rows-container')
.should('not.be.empty') .should('not.be.empty')
cy.get('.gantt-chart .tasks')
.should('contain', tasks[0].title) .should('contain', tasks[0].title)
}) })
it('Shows tasks with no dates after enabling them', () => { it('Shows tasks with no dates after enabling them', () => {
TaskFactory.create(1, { const tasks = TaskFactory.create(1, {
start_date: null, start_date: null,
end_date: null, end_date: null,
}) })
@ -55,13 +54,15 @@ describe('List View Gantt', () => {
.contains('Show tasks which don\'t have dates set') .contains('Show tasks which don\'t have dates set')
.click() .click()
cy.get('.gantt-chart .tasks') cy.get('.g-gantt-rows-container')
.should('not.be.empty') .should('not.be.empty')
cy.get('.gantt-chart .tasks .task.nodate') .should('contain', tasks[0].title)
.should('exist')
}) })
it('Drags a task around', () => { it('Drags a task around', () => {
cy.intercept('**/api/v1/tasks/*')
.as('taskUpdate')
const now = new Date() const now = new Date()
TaskFactory.create(1, { TaskFactory.create(1, {
start_date: formatISO(now), start_date: formatISO(now),
@ -69,10 +70,11 @@ describe('List View Gantt', () => {
}) })
cy.visit('/lists/1/gantt') cy.visit('/lists/1/gantt')
cy.get('.gantt-chart .tasks .task') cy.get('.g-gantt-rows-container .g-gantt-row .g-gantt-row-bars-container div .g-gantt-bar')
.first() .first()
.trigger('mousedown', {which: 1}) .trigger('mousedown', {which: 1})
.trigger('mousemove', {clientX: 500, clientY: 0}) .trigger('mousemove', {clientX: 500, clientY: 0})
.trigger('mouseup', {force: true}) .trigger('mouseup', {force: true})
cy.wait('@taskUpdate')
}) })
}) })

View file

@ -12,51 +12,15 @@ import {LabelTaskFactory} from '../../factories/label_task'
import {BucketFactory} from '../../factories/bucket' import {BucketFactory} from '../../factories/bucket'
import '../../support/authenticateUser' import '../../support/authenticateUser'
import {TaskAttachmentFactory} from '../../factories/task_attachments'
function addLabelToTaskAndVerify(labelTitle: string) {
cy.get('.task-view .action-buttons .button')
.contains('Add Labels')
.click()
cy.get('.task-view .details.labels-list .multiselect input')
.type(labelTitle)
cy.get('.task-view .details.labels-list .multiselect .search-results')
.children()
.first()
.click()
cy.get('.global-notification', { timeout: 4000 })
.should('contain', 'Success')
cy.get('.task-view .details.labels-list .multiselect .input-wrapper span.tag')
.should('exist')
.should('contain', labelTitle)
}
function uploadAttachmentAndVerify(taskId: number) {
cy.intercept(`${Cypress.env('API_URL')}/tasks/${taskId}/attachments`).as('uploadAttachment')
cy.get('.task-view .action-buttons .button')
.contains('Add Attachments')
.click()
cy.get('input[type=file]', {timeout: 1000})
.selectFile('cypress/fixtures/image.jpg', {force: true}) // The input is not visible, but on purpose
cy.wait('@uploadAttachment')
cy.get('.attachments .attachments .files a.attachment')
.should('exist')
}
describe('Task', () => { describe('Task', () => {
let namespaces let namespaces
let lists let lists
let buckets
beforeEach(() => { beforeEach(() => {
UserFactory.create(1) UserFactory.create(1)
namespaces = NamespaceFactory.create(1) namespaces = NamespaceFactory.create(1)
lists = ListFactory.create(1) lists = ListFactory.create(1)
buckets = BucketFactory.create(1, {
list_id: lists[0].id,
})
TaskFactory.truncate() TaskFactory.truncate()
UserListFactory.truncate() UserListFactory.truncate()
}) })
@ -116,7 +80,6 @@ describe('Task', () => {
describe('Task Detail View', () => { describe('Task Detail View', () => {
beforeEach(() => { beforeEach(() => {
TaskCommentFactory.truncate() TaskCommentFactory.truncate()
LabelTaskFactory.truncate()
}) })
it('Shows all task details', () => { it('Shows all task details', () => {
@ -381,31 +344,21 @@ describe('Task', () => {
cy.visit(`/tasks/${tasks[0].id}`) cy.visit(`/tasks/${tasks[0].id}`)
addLabelToTaskAndVerify(labels[0].title) cy.get('.task-view .action-buttons .button')
}) .contains('Add Labels')
it('Can add a label to a task and it shows up on the kanban board afterwards', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: lists[0].id,
bucket_id: buckets[0].id,
})
const labels = LabelFactory.create(1)
LabelTaskFactory.truncate()
cy.visit(`/lists/${lists[0].id}/kanban`)
cy.get('.bucket .task')
.contains(tasks[0].title)
.click() .click()
cy.get('.task-view .details.labels-list .multiselect input')
addLabelToTaskAndVerify(labels[0].title) .type(labels[0].title)
cy.get('.task-view .details.labels-list .multiselect .search-results')
cy.get('.modal-content .close') .children()
.first()
.click() .click()
cy.get('.bucket .task') cy.get('.global-notification', { timeout: 4000 })
.should('contain.text', labels[0].title) .should('contain', 'Success')
cy.get('.task-view .details.labels-list .multiselect .input-wrapper span.tag')
.should('exist')
.should('contain', labels[0].title)
}) })
it('Can remove a label from a task', () => { it('Can remove a label from a task', () => {
@ -464,87 +417,5 @@ describe('Task', () => {
cy.get('.global-notification') cy.get('.global-notification')
.should('contain', 'Success') .should('contain', 'Success')
}) })
it('Can set a priority for a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Set Priority')
.click()
cy.get('.task-view .columns.details .column')
.contains('Priority')
.get('.select select')
.select('Urgent')
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.task-view .columns.details .column')
.contains('Priority')
.get('.select select')
.should('have.value', '4')
})
it('Can set the progress for a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Set Progress')
.click()
cy.get('.task-view .columns.details .column')
.contains('Progress')
.get('.select select')
.select('50%')
cy.get('.global-notification')
.should('contain', 'Success')
cy.wait(200)
cy.get('.task-view .columns.details .column')
.contains('Progress')
.get('.select select')
.should('be.visible')
.should('have.value', '0.5')
})
it('Can add an attachment to a task', () => {
TaskAttachmentFactory.truncate()
const tasks = TaskFactory.create(1, {
id: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)
uploadAttachmentAndVerify(tasks[0].id)
})
it('Can add an attachment to a task and see it appearing on kanban', () => {
TaskAttachmentFactory.truncate()
const tasks = TaskFactory.create(1, {
id: 1,
list_id: lists[0].id,
bucket_id: buckets[0].id,
})
const labels = LabelFactory.create(1)
LabelTaskFactory.truncate()
cy.visit(`/lists/${lists[0].id}/kanban`)
cy.get('.bucket .task')
.contains(tasks[0].title)
.click()
uploadAttachmentAndVerify(tasks[0].id)
cy.get('.modal-content .close')
.click()
cy.get('.bucket .task .footer .icon svg.fa-paperclip')
.should('exist')
})
}) })
}) })

View file

@ -1,17 +0,0 @@
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'
export class TaskAttachmentFactory extends Factory {
static table = 'task_attachments'
static factory() {
const now = new Date()
return {
id: '{increment}',
task_id: 1,
file_id: 1,
created: formatISO(now),
}
}
}

View file

@ -23,9 +23,10 @@
"@fortawesome/free-solid-svg-icons": "6.2.0", "@fortawesome/free-solid-svg-icons": "6.2.0",
"@fortawesome/vue-fontawesome": "3.0.1", "@fortawesome/vue-fontawesome": "3.0.1",
"@github/hotkey": "2.0.1", "@github/hotkey": "2.0.1",
"@infectoone/vue-ganttastic": "./vendor/infectoone-vue-ganttastic-2.1.1.tgz",
"@kyvg/vue3-notification": "2.4.1", "@kyvg/vue3-notification": "2.4.1",
"@sentry/tracing": "7.15.0", "@sentry/tracing": "7.14.1",
"@sentry/vue": "7.15.0", "@sentry/vue": "7.14.1",
"@types/is-touch-device": "1.0.0", "@types/is-touch-device": "1.0.0",
"@types/lodash.clonedeep": "4.5.7", "@types/lodash.clonedeep": "4.5.7",
"@types/sortablejs": "1.15.0", "@types/sortablejs": "1.15.0",
@ -47,8 +48,8 @@
"lodash.clonedeep": "4.5.0", "lodash.clonedeep": "4.5.0",
"lodash.debounce": "4.0.8", "lodash.debounce": "4.0.8",
"marked": "4.1.1", "marked": "4.1.1",
"minimist": "1.2.7", "minimist": "1.2.6",
"pinia": "2.0.23", "pinia": "2.0.22",
"register-service-worker": "1.7.2", "register-service-worker": "1.7.2",
"snake-case": "3.0.4", "snake-case": "3.0.4",
"sortablejs": "1.15.0", "sortablejs": "1.15.0",
@ -56,7 +57,7 @@
"vue": "3.2.40", "vue": "3.2.40",
"vue-advanced-cropper": "2.8.6", "vue-advanced-cropper": "2.8.6",
"vue-drag-resize": "2.0.3", "vue-drag-resize": "2.0.3",
"vue-flatpickr-component": "9.0.8", "vue-flatpickr-component": "9.0.6",
"vue-i18n": "9.2.2", "vue-i18n": "9.2.2",
"vue-router": "4.1.5", "vue-router": "4.1.5",
"workbox-precaching": "6.5.4", "workbox-precaching": "6.5.4",
@ -64,7 +65,7 @@
}, },
"devDependencies": { "devDependencies": {
"@4tw/cypress-drag-drop": "2.2.1", "@4tw/cypress-drag-drop": "2.2.1",
"@cypress/vite-dev-server": "3.3.1", "@cypress/vite-dev-server": "3.2.0",
"@cypress/vue": "4.2.0", "@cypress/vue": "4.2.0",
"@faker-js/faker": "7.5.0", "@faker-js/faker": "7.5.0",
"@rushstack/eslint-patch": "1.2.0", "@rushstack/eslint-patch": "1.2.0",
@ -72,9 +73,9 @@
"@types/flexsearch": "0.7.3", "@types/flexsearch": "0.7.3",
"@types/lodash.debounce": "4.0.7", "@types/lodash.debounce": "4.0.7",
"@types/marked": "4.0.7", "@types/marked": "4.0.7",
"@types/node": "16.11.65", "@types/node": "16.11.64",
"@typescript-eslint/eslint-plugin": "5.40.0", "@typescript-eslint/eslint-plugin": "5.39.0",
"@typescript-eslint/parser": "5.40.0", "@typescript-eslint/parser": "5.39.0",
"@vitejs/plugin-legacy": "2.2.0", "@vitejs/plugin-legacy": "2.2.0",
"@vitejs/plugin-vue": "3.1.2", "@vitejs/plugin-vue": "3.1.2",
"@vue/eslint-config-typescript": "11.0.2", "@vue/eslint-config-typescript": "11.0.2",
@ -82,25 +83,25 @@
"@vue/tsconfig": "0.1.3", "@vue/tsconfig": "0.1.3",
"autoprefixer": "10.4.12", "autoprefixer": "10.4.12",
"browserslist": "4.21.4", "browserslist": "4.21.4",
"caniuse-lite": "1.0.30001418", "caniuse-lite": "1.0.30001414",
"cypress": "10.10.0", "cypress": "10.9.0",
"esbuild": "0.15.10", "esbuild": "0.15.10",
"eslint": "8.25.0", "eslint": "8.24.0",
"eslint-plugin-vue": "9.6.0", "eslint-plugin-vue": "9.6.0",
"express": "4.18.2", "express": "4.18.1",
"happy-dom": "7.4.0", "happy-dom": "6.0.4",
"netlify-cli": "12.0.7", "netlify-cli": "12.0.2",
"postcss": "8.4.17", "postcss": "8.4.17",
"postcss-preset-env": "7.8.2", "postcss-preset-env": "7.8.2",
"rollup": "3.0.0", "rollup": "2.79.1",
"rollup-plugin-visualizer": "5.8.2", "rollup-plugin-visualizer": "5.8.2",
"sass": "1.55.0", "sass": "1.55.0",
"typescript": "4.8.4", "typescript": "4.8.4",
"vite": "3.1.7", "vite": "3.1.4",
"vite-plugin-pwa": "0.13.1", "vite-plugin-pwa": "0.13.1",
"vite-svg-loader": "3.6.0", "vite-svg-loader": "3.6.0",
"vitest": "0.24.1", "vitest": "0.23.4",
"vue-tsc": "1.0.5", "vue-tsc": "0.40.13",
"wait-on": "6.0.1", "wait-on": "6.0.1",
"workbox-cli": "6.5.4" "workbox-cli": "6.5.4"
}, },
@ -110,5 +111,5 @@
} }
}, },
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"packageManager": "pnpm@7.13.4" "packageManager": "pnpm@7.13.1"
} }

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@
], ],
"packageRules": [ "packageRules": [
{ {
"matchPackageNames": ["netlify-cli", "happy-dom"], "matchPackageNames": ["netlify-cli"],
"extends": ["schedule:weekly"] "extends": ["schedule:weekly"]
}, },
{ {

View file

@ -55,13 +55,13 @@
> >
{{ $t('menu.archive') }} {{ $t('menu.archive') }}
</dropdown-item> </dropdown-item>
<Subscription <task-subscription
class="has-no-shadow" class="has-no-shadow"
:is-button="false" :is-button="false"
entity="list" entity="list"
:entity-id="list.id" :entity-id="list.id"
:model-value="list.subscription" :model-value="list.subscription"
@update:model-value="setSubscriptionInStore" @update:model-value="sub => subscription = sub"
type="dropdown" type="dropdown"
/> />
<dropdown-item <dropdown-item
@ -81,12 +81,10 @@ import {ref, computed, watchEffect, type PropType} from 'vue'
import {isSavedFilter} from '@/helpers/savedFilter' import {isSavedFilter} from '@/helpers/savedFilter'
import Dropdown from '@/components/misc/dropdown.vue' import Dropdown from '@/components/misc/dropdown.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue' import DropdownItem from '@/components/misc/dropdown-item.vue'
import Subscription from '@/components/misc/subscription.vue' import TaskSubscription from '@/components/misc/subscription.vue'
import type {IList} from '@/modelTypes/IList' import type {IList} from '@/modelTypes/IList'
import type {ISubscription} from '@/modelTypes/ISubscription' import type {ISubscription} from '@/modelTypes/ISubscription'
import {useConfigStore} from '@/stores/config' import {useConfigStore} from '@/stores/config'
import {useListStore} from '@/stores/lists'
import {useNamespaceStore} from '@/stores/namespaces'
const props = defineProps({ const props = defineProps({
list: { list: {
@ -95,8 +93,6 @@ const props = defineProps({
}, },
}) })
const listStore = useListStore()
const namespaceStore = useNamespaceStore()
const subscription = ref<ISubscription | null>(null) const subscription = ref<ISubscription | null>(null)
watchEffect(() => { watchEffect(() => {
subscription.value = props.list.subscription ?? null subscription.value = props.list.subscription ?? null
@ -104,14 +100,4 @@ watchEffect(() => {
const configStore = useConfigStore() const configStore = useConfigStore()
const backgroundsEnabled = computed(() => configStore.enabledBackgroundProviders?.length > 0) const backgroundsEnabled = computed(() => configStore.enabledBackgroundProviders?.length > 0)
function setSubscriptionInStore(sub: ISubscription) {
subscription.value = sub
const updatedList = {
...props.list,
subscription: sub,
}
listStore.setList(updatedList)
namespaceStore.setListInNamespaceById(updatedList)
}
</script> </script>

View file

@ -210,7 +210,6 @@ import ListService from '@/services/list'
import NamespaceService from '@/services/namespace' import NamespaceService from '@/services/namespace'
import EditLabels from '@/components/tasks/partials/editLabels.vue' import EditLabels from '@/components/tasks/partials/editLabels.vue'
import {dateIsValid, formatISO} from '@/helpers/time/formatDate'
import {objectToSnakeCase} from '@/helpers/case' import {objectToSnakeCase} from '@/helpers/case'
import {getDefaultParams} from '@/composables/taskList' import {getDefaultParams} from '@/composables/taskList'
import {camelCase} from 'camel-case' import {camelCase} from 'camel-case'
@ -392,14 +391,7 @@ export default defineComponent({
this.params.filter_value.push(dateTo) this.params.filter_value.push(dateTo)
} }
this.filters[camelCase(filterName)] = { this.filters[camelCase(filterName)] = {dateFrom, dateTo}
// Passing the dates as string values avoids an endless loop between values changing
// in the datepicker (bubbling up to here) and changing here and bubbling down to the
// datepicker (because there's a new date instance every time this function gets called).
// See https://kolaente.dev/vikunja/frontend/issues/2384
dateFrom: dateIsValid(dateFrom) ? formatISO(dateFrom) : dateFrom,
dateTo: dateIsValid(dateTo) ? formatISO(dateTo) : dateTo,
}
this.change() this.change()
return return
} }
@ -519,12 +511,12 @@ export default defineComponent({
if (typeof this.filters[filterName] === 'undefined' || this.filters[filterName] === '') { if (typeof this.filters[filterName] === 'undefined' || this.filters[filterName] === '') {
return return
} }
// Don't load things if we already have something loaded. // Don't load things if we already have something loaded.
// This is not the most ideal solution because it prevents a re-population when filters are changed // This is not the most ideal solution because it prevents a re-population when filters are changed
// from the outside. It is still fine because we're not changing them from the outside, other than // from the outside. It is still fine because we're not changing them from the outside, other than
// loading them initially. // loading them initially.
if (this[kind].length > 0) { if(this[kind].length > 0) {
return return
} }

View file

@ -69,38 +69,17 @@ const emit = defineEmits(['update:modelValue'])
const subscriptionService = shallowRef(new SubscriptionService()) const subscriptionService = shallowRef(new SubscriptionService())
const {t} = useI18n({useScope: 'global'}) const {t} = useI18n({useScope: 'global'})
const tooltipText = computed(() => { const tooltipText = computed(() => {
if (disabled.value) { if (disabled.value) {
if (props.entity === 'list' && subscriptionEntity.value === 'namespace') { return t('task.subscription.subscribedThroughParent', {
return t('task.subscription.subscribedListThroughParentNamespace') entity: props.entity,
} parent: subscriptionEntity.value,
if (props.entity === 'task' && subscriptionEntity.value === 'namespace') { })
return t('task.subscription.subscribedTaskThroughParentNamespace')
}
if (props.entity === 'task' && subscriptionEntity.value === 'list') {
return t('task.subscription.subscribedTaskThroughParentList')
}
return ''
} }
switch (props.entity) { return props.modelValue !== null ?
case 'namespace': t('task.subscription.subscribed', {entity: props.entity}) :
return props.modelValue !== null ? t('task.subscription.notSubscribed', {entity: props.entity})
t('task.subscription.subscribedNamespace') :
t('task.subscription.notSubscribedNamespace')
case 'list':
return props.modelValue !== null ?
t('task.subscription.subscribedList') :
t('task.subscription.notSubscribedList')
case 'task':
return props.modelValue !== null ?
t('task.subscription.subscribedTask') :
t('task.subscription.notSubscribedTask')
}
return ''
}) })
const buttonText = computed(() => props.modelValue ? t('task.subscription.unsubscribe') : t('task.subscription.subscribe')) const buttonText = computed(() => props.modelValue ? t('task.subscription.unsubscribe') : t('task.subscription.subscribe'))
@ -126,20 +105,7 @@ async function subscribe() {
}) })
await subscriptionService.value.create(subscription) await subscriptionService.value.create(subscription)
emit('update:modelValue', subscription) emit('update:modelValue', subscription)
success({message: t('task.subscription.subscribeSuccess', {entity: props.entity})})
let message = ''
switch (props.entity) {
case 'namespace':
message = t('task.subscription.subscribeSuccessNamespace')
break
case 'list':
message = t('task.subscription.subscribeSuccessList')
break
case 'task':
message = t('task.subscription.subscribeSuccessTask')
break
}
success({message})
} }
async function unsubscribe() { async function unsubscribe() {
@ -149,19 +115,6 @@ async function unsubscribe() {
}) })
await subscriptionService.value.delete(subscription) await subscriptionService.value.delete(subscription)
emit('update:modelValue', null) emit('update:modelValue', null)
success({message: t('task.subscription.unsubscribeSuccess', {entity: props.entity})})
let message = ''
switch (props.entity) {
case 'namespace':
message = t('task.subscription.unsubscribeSuccessNamespace')
break
case 'list':
message = t('task.subscription.unsubscribeSuccessList')
break
case 'task':
message = t('task.subscription.unsubscribeSuccessTask')
break
}
success({message})
} }
</script> </script>

View file

@ -33,13 +33,14 @@
> >
{{ $t('menu.archive') }} {{ $t('menu.archive') }}
</dropdown-item> </dropdown-item>
<Subscription <task-subscription
v-if="subscription"
class="has-no-shadow" class="has-no-shadow"
:is-button="false" :is-button="false"
entity="namespace" entity="namespace"
:entity-id="namespace.id" :entity-id="namespace.id"
:model-value="subscription" :model-value="subscription"
@update:model-value="setSubscriptionInStore" @update:model-value="sub => subscription = sub"
type="dropdown" type="dropdown"
/> />
<dropdown-item <dropdown-item
@ -58,10 +59,9 @@ import {ref, onMounted, type PropType} from 'vue'
import Dropdown from '@/components/misc/dropdown.vue' import Dropdown from '@/components/misc/dropdown.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue' import DropdownItem from '@/components/misc/dropdown-item.vue'
import Subscription from '@/components/misc/subscription.vue' import TaskSubscription from '@/components/misc/subscription.vue'
import type {INamespace} from '@/modelTypes/INamespace' import type {INamespace} from '@/modelTypes/INamespace'
import type {ISubscription} from '@/modelTypes/ISubscription' import type {ISubscription} from '@/modelTypes/ISubscription'
import {useNamespaceStore} from '@/stores/namespaces'
const props = defineProps({ const props = defineProps({
namespace: { namespace: {
@ -70,20 +70,8 @@ const props = defineProps({
}, },
}) })
const namespaceStore = useNamespaceStore()
const subscription = ref<ISubscription | null>(null) const subscription = ref<ISubscription | null>(null)
onMounted(() => { onMounted(() => {
subscription.value = props.namespace.subscription subscription.value = props.namespace.subscription
}) })
function setSubscriptionInStore(sub: ISubscription) {
subscription.value = sub
namespaceStore.setNamespaces([
{
...props.namespace,
subscription: sub,
},
])
}
</script> </script>

View file

@ -0,0 +1,78 @@
<template>
<form
@submit.prevent="createTask"
class="add-new-task"
>
<transition name="width">
<input
v-if="newTaskFieldActive"
v-model="newTaskTitle"
@blur="hideCreateNewTask"
@keyup.esc="newTaskFieldActive = false"
class="input"
ref="newTaskTitleField"
type="text"
/>
</transition>
<x-button @click="showCreateTaskOrCreate" :shadow="false" icon="plus">
{{ $t('task.new') }}
</x-button>
</form>
</template>
<script setup lang="ts">
import {nextTick, ref} from 'vue'
import type {ITask} from '@/models/task'
const emit = defineEmits<{
(e: 'create-task', title: string): Promise<ITask>
}>()
const newTaskFieldActive = ref(false)
const newTaskTitleField = ref()
const newTaskTitle = ref('')
function showCreateTaskOrCreate() {
if (!newTaskFieldActive.value) {
// Timeout to not send the form if the field isn't even shown
setTimeout(() => {
newTaskFieldActive.value = true
nextTick(() => newTaskTitleField.value.focus())
}, 100)
} else {
createTask()
}
}
function hideCreateNewTask() {
if (newTaskTitle.value === '') {
nextTick(() => (newTaskFieldActive.value = false))
}
}
async function createTask() {
if (!newTaskFieldActive.value) {
return
}
await emit('create-task', newTaskTitle.value)
newTaskTitle.value = ''
hideCreateNewTask()
}
</script>
<style scoped lang="scss">
.add-new-task {
padding: 1rem .7rem .4rem .7rem;
display: flex;
max-width: 450px;
.input {
margin-right: .7rem;
font-size: .8rem;
}
.button {
font-size: .68rem;
}
}
</style>

View file

@ -0,0 +1,319 @@
<template>
<Loading class="gantt-container" v-if="taskService.loading || taskCollectionService.loading"/>
<div class="gantt-container" v-else>
<GGanttChart
:chart-start="`${dateFrom} 00:00`"
:chart-end="`${dateTo} 23:59`"
:precision="PRECISION"
bar-start="startDate"
bar-end="endDate"
:grid="true"
@dragend-bar="updateTask"
@dblclick-bar="openTask"
:width="ganttChartWidth + 'px'"
>
<template #timeunit="{label, value}">
<div
class="timeunit-wrapper"
:class="{'today': dayIsToday(label)}">
<span>{{ value }}</span>
<span class="weekday">
{{ weekdayFromTimeLabel(label) }}
</span>
</div>
</template>
<GGanttRow
v-for="(bar, k) in ganttBars"
:key="k"
label=""
:bars="bar"
/>
</GGanttChart>
</div>
<TaskForm v-if="canWrite" @create-task="createTask" />
</template>
<script setup lang="ts">
import {computed, ref, watch, watchEffect, shallowReactive, type PropType} from 'vue'
import {useRouter} from 'vue-router'
import {format, parse} from 'date-fns'
import TaskCollectionService from '@/services/taskCollection'
import TaskService from '@/services/task'
import TaskModel, { getHexColor } from '@/models/task'
import type ListModel from '@/models/list'
import {colorIsDark} from '@/helpers/color/colorIsDark'
import {RIGHTS} from '@/constants/rights'
import {
extendDayjs,
GGanttChart,
GGanttRow,
type GanttBarObject,
} from '@infectoone/vue-ganttastic'
import Loading from '@/components/misc/loading.vue'
import TaskForm from '@/components/tasks/TaskForm.vue'
import {useBaseStore} from '@/stores/base'
extendDayjs()
const PRECISION = 'day' as const
const DATE_FORMAT = 'yyyy-LL-dd HH:mm'
const baseStore = useBaseStore()
const router = useRouter()
const props = defineProps({
listId: {
type: Number as PropType<ListModel['id']>,
required: true,
},
dateFrom: {
type: String as PropType<any>,
required: true,
},
dateTo: {
type: String as PropType<any>,
required: true,
},
showTasksWithoutDates: {
type: Boolean,
default: false,
},
})
const taskCollectionService = shallowReactive(new TaskCollectionService())
const taskService = shallowReactive(new TaskService())
const dateFromDate = computed(() => parse(props.dateFrom, 'yyyy-LL-dd', new Date()))
const dateToDate = computed(() => parse(props.dateTo, 'yyyy-LL-dd', new Date()))
const DAY_WIDTH_PIXELS = 30
const ganttChartWidth = computed(() => {
const dateDiff = Math.floor((dateToDate.value - dateFromDate.value) / (1000 * 60 * 60 * 24))
return dateDiff * DAY_WIDTH_PIXELS
})
const canWrite = computed(() => baseStore.currentList.maxRight > RIGHTS.READ)
const tasks = ref<Map<TaskModel['id'], TaskModel>>(new Map())
const ganttBars = ref<GanttBarObject[][]>([])
watch(
tasks,
// We need a "real" ref object for the gantt bars to instantly update the tasks when they are dragged on the chart.
// A computed won't work directly.
// function mapGanttBars() {
() => {
ganttBars.value = []
tasks.value.forEach(t => ganttBars.value.push(transformTaskToGanttBar(t)))
},
{deep: true}
)
const defaultStartDate = format(new Date(), DATE_FORMAT)
const defaultEndDate = format(new Date((new Date()).setDate((new Date()).getDate() + 7)), DATE_FORMAT)
function transformTaskToGanttBar(t: TaskModel) {
const black = 'var(--grey-800)'
return [{
startDate: t.startDate ? format(t.startDate, DATE_FORMAT) : defaultStartDate,
endDate: t.endDate ? format(t.endDate, DATE_FORMAT) : defaultEndDate,
ganttBarConfig: {
id: String(t.id),
label: t.title,
hasHandles: true,
style: {
color: t.startDate ? (colorIsDark(getHexColor(t.hexColor)) ? black : 'white') : black,
backgroundColor: t.startDate ? getHexColor(t.hexColor) : 'var(--grey-100)',
border: t.startDate ? '' : '2px dashed var(--grey-300)',
'text-decoration': t.done ? 'line-through' : null,
},
},
} as GanttBarObject]
}
// FIXME: unite with other filter params types
interface GetAllTasksParams {
sort_by: ('start_date' | 'done' | 'id')[],
order_by: ('asc' | 'asc' | 'desc')[],
filter_by: 'start_date'[],
filter_comparator: ('greater_equals' | 'less_equals')[],
filter_value: [string, string] // [dateFrom, dateTo],
filter_concat: 'and',
filter_include_nulls: boolean,
}
async function getAllTasks(params: GetAllTasksParams, page = 1): Promise<TaskModel[]> {
const tasks = await taskCollectionService.getAll({listId: props.listId}, params, page) as TaskModel[]
if (page < taskCollectionService.totalPages) {
const nextTasks = await getAllTasks(params, page + 1)
return tasks.concat(nextTasks)
}
return tasks
}
async function loadTasks({
dateTo,
dateFrom,
showTasksWithoutDates,
}: {
dateTo: string;
dateFrom: string;
showTasksWithoutDates: boolean;
}) {
tasks.value = new Map()
const params = {
sort_by: ['start_date', 'done', 'id'],
order_by: ['asc', 'asc', 'desc'],
filter_by: ['start_date', 'start_date'],
filter_comparator: ['greater_equals', 'less_equals'],
filter_value: [dateFrom, dateTo],
filter_concat: 'and',
filter_include_nulls: showTasksWithoutDates,
}
const loadedTasks = await getAllTasks(params)
loadedTasks.forEach(t => tasks.value.set(t.id, t))
}
watchEffect(() => loadTasks({
dateTo: props.dateTo,
dateFrom: props.dateFrom,
showTasksWithoutDates: props.showTasksWithoutDates,
}))
async function createTask(title: TaskModel['title']) {
const newTask = await taskService.create(new TaskModel({
title,
listId: props.listId,
startDate: defaultStartDate,
endDate: defaultEndDate,
}))
tasks.value.set(newTask.id, newTask)
return newTask
}
async function updateTask(e) {
const task = tasks.value.get(e.bar.ganttBarConfig.id)
if (!task) return
task.startDate = e.bar.startDate
task.endDate = e.bar.endDate
const updatedTask = await taskService.update(task)
ganttBars.value.map(gantBar => {
return gantBar[0].ganttBarConfig.id === task.id
? transformTaskToGanttBar(updatedTask)
: gantBar
})
}
function openTask(e) {
router.push({
name: 'task.detail',
params: {id: e.bar.ganttBarConfig.id},
state: {backdropView: router.currentRoute.value.fullPath},
})
}
function weekdayFromTimeLabel(label: string): string {
const parsed = parse(label, 'dd.MMM', dateFromDate.value)
return format(parsed, 'E')
}
function dayIsToday(label: string): boolean {
const parsed = parse(label, 'dd.MMM', dateFromDate.value)
const today = new Date()
return parsed.getDate() === today.getDate() &&
parsed.getMonth() === today.getMonth() &&
parsed.getFullYear() === today.getFullYear()
}
</script>
<style scoped lang="scss">
.gantt-container {
overflow-x: auto;
}
</style>
<style lang="scss">
// Not scoped because we need to style the elements inside the gantt chart component
.g-gantt-chart {
width: 2000px;
}
.g-gantt-row-label {
display: none;
}
.g-upper-timeunit, .g-timeunit {
background: var(--white);
font-family: $vikunja-font;
}
.g-upper-timeunit {
font-weight: bold;
border-right: 1px solid var(--grey-200);
padding: .5rem 0;
}
.g-timeunit .timeunit-wrapper {
padding: 0.5rem 0;
font-size: 1rem;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
&.today {
background: var(--primary);
color: var(--white);
border-radius: 5px 5px 0 0;
font-weight: bold;
}
.weekday {
font-size: 0.8rem;
}
}
.g-timeaxis {
height: auto;
box-shadow: none;
}
.g-gantt-row > .g-gantt-row-bars-container {
border-bottom: none;
border-top: none;
}
.g-gantt-row:nth-child(odd) {
background: hsla(var(--grey-100-hsl), .5);
}
.g-gantt-bar {
border-radius: $radius * 1.5;
overflow: visible;
font-size: .85rem;
&-handle-left,
&-handle-right {
width: 6px;
height: 75%;
opacity: .75;
border-radius: $radius;
margin-top: 4px;
}
}
</style>

View file

@ -1,642 +0,0 @@
<template>
<div class="gantt-chart">
<div class="filter-container">
<div class="items">
<filter-popup
v-model="params"
@update:modelValue="loadTasks()"
/>
</div>
</div>
<div class="dates">
<template v-for="(y, yk) in days" :key="yk + 'year'">
<div class="months">
<div
:key="mk + 'month'"
class="month"
v-for="(m, mk) in days[yk]"
>
{{ formatMonthAndYear(yk, parseInt(mk) + 1) }}
<div class="days">
<div
:class="{ today: d.toDateString() === now.toDateString() }"
:key="dk + 'day'"
:style="{ width: dayWidth + 'px' }"
class="day"
v-for="(d, dk) in days[yk][mk]"
>
<span class="theday" v-if="dayWidth > 25">
{{ d.getDate() }}
</span>
<span class="weekday" v-if="dayWidth > 25">
{{
d.toLocaleString('en-us', {
weekday: 'short',
})
}}
</span>
</div>
</div>
</div>
</div>
</template>
</div>
<div :style="{ width: fullWidth + 'px' }" class="tasks">
<div
v-for="(t, k) in theTasks"
:key="t ? t.id : 0"
:style="{
background:
'repeating-linear-gradient(90deg, #ededed, #ededed 1px, ' +
(k % 2 === 0
? '#fafafa 1px, #fafafa '
: '#fff 1px, #fff ') +
dayWidth +
'px)',
}"
class="row"
>
<VueDragResize
:class="{
done: t ? t.done : false,
'is-current-edit': taskToEdit !== null && taskToEdit.id === t.id,
'has-light-text': !colorIsDark(t.getHexColor()),
'has-dark-text': colorIsDark(t.getHexColor()),
}"
:gridX="dayWidth"
:h="31"
:isActive="canWrite"
:minw="dayWidth"
:parentLimitation="true"
:parentW="fullWidth"
:snapToGrid="true"
:sticks="['mr', 'ml']"
:style="{
'border-color': t.getHexColor(),
'background-color': t.getHexColor(),
}"
:w="t.durationDays * dayWidth"
:x="t.offsetDays * dayWidth - 6"
:y="0"
@dragstop="(e) => resizeTask(t, e)"
@resizestop="(e) => resizeTask(t, e)"
axis="x"
class="task"
>
<span
:class="{
'has-high-priority': t.priority >= priorities.HIGH,
'has-not-so-high-priority':
t.priority === priorities.HIGH,
'has-super-high-priority':
t.priority === priorities.DO_NOW,
}"
>
{{ t.title }}
</span>
<priority-label :priority="t.priority" :done="t.done"/>
<!-- using the key here forces vue to use the updated version model and not the response returned by the api -->
<!-- FIXME: add label -->
<BaseButton @click="editTask(theTasks[k])" class="edit-toggle">
<icon icon="pen"/>
</BaseButton>
</VueDragResize>
</div>
<template v-if="showTaskswithoutDates">
<div
:key="t.id"
:style="{
background:
'repeating-linear-gradient(90deg, #ededed, #ededed 1px, ' +
(k % 2 === 0
? '#fafafa 1px, #fafafa '
: '#fff 1px, #fff ') +
dayWidth +
'px)',
}"
class="row"
v-for="(t, k) in tasksWithoutDates"
>
<VueDragResize
:gridX="dayWidth"
:h="31"
:isActive="canWrite"
:minw="dayWidth"
:parentLimitation="true"
:parentW="fullWidth"
:snapToGrid="true"
:sticks="['mr', 'ml']"
:x="dayOffsetUntilToday * dayWidth - 6"
:y="0"
@dragstop="(e) => resizeTask(t, e)"
@resizestop="(e) => resizeTask(t, e)"
axis="x"
class="task nodate"
v-tooltip="$t('list.gantt.noDates')"
>
<span>{{ t.title }}</span>
</VueDragResize>
</div>
</template>
</div>
<form
@submit.prevent="addNewTask()"
class="add-new-task"
v-if="canWrite"
>
<transition name="width">
<input
@blur="hideCrateNewTask"
@keyup.esc="newTaskFieldActive = false"
class="input"
ref="newTaskTitleField"
type="text"
v-if="newTaskFieldActive"
v-model="newTaskTitle"
/>
</transition>
<x-button @click="showCreateNewTask" :shadow="false" icon="plus">
{{ $t('list.list.newTaskCta') }}
</x-button>
</form>
<transition name="fade">
<edit-task
v-if="isTaskEdit"
class="taskedit"
:title="$t('list.list.editTask')"
@close="() => {isTaskEdit = false;taskToEdit = null}"
:task="taskToEdit"
/>
</transition>
</div>
</template>
<script lang="ts">
import {defineComponent} from 'vue'
import {mapState} from 'pinia'
import VueDragResize from 'vue-drag-resize'
import EditTask from './edit-task.vue'
import TaskService from '../../services/task'
import TaskModel from '../../models/task'
import {PRIORITIES as priorities} from '@/constants/priorities'
import PriorityLabel from './partials/priorityLabel.vue'
import TaskCollectionService from '../../services/taskCollection'
import {RIGHTS as Rights} from '@/constants/rights'
import FilterPopup from '@/components/list/partials/filter-popup.vue'
import BaseButton from '@/components/base/BaseButton.vue'
import {colorIsDark} from '@/helpers/color/colorIsDark'
import {formatDate} from '@/helpers/time/formatDate'
import {useBaseStore} from '@/stores/base'
export default defineComponent({
name: 'GanttChart',
components: {
BaseButton,
FilterPopup,
PriorityLabel,
EditTask,
VueDragResize,
},
props: {
listId: {
type: Number,
required: true,
},
showTaskswithoutDates: {
type: Boolean,
default: false,
},
dateFrom: {
default: () => new Date(new Date().setDate(new Date().getDate() - 15)),
},
dateTo: {
default: () => new Date(new Date().setDate(new Date().getDate() + 30)),
},
// The width of a day in pixels, used to calculate all sorts of things.
dayWidth: {
type: Number,
default: 35,
},
},
data() {
return {
days: [],
startDate: null,
endDate: null,
theTasks: [], // Pretty much a copy of the prop, since we cant mutate the prop directly
tasksWithoutDates: [],
taskService: new TaskService(),
fullWidth: 0,
now: new Date(),
dayOffsetUntilToday: 0,
isTaskEdit: false,
taskToEdit: null,
newTaskTitle: '',
newTaskFieldActive: false,
priorities: priorities,
taskCollectionService: new TaskCollectionService(),
params: {
sort_by: ['done', 'id'],
order_by: ['asc', 'desc'],
filter_by: ['done'],
filter_value: ['false'],
filter_comparator: ['equals'],
filter_concat: 'and',
},
}
},
watch: {
dateFrom: 'buildTheGanttChart',
dateTo: 'buildTheGanttChart',
listId: 'parseTasks',
},
mounted() {
this.buildTheGanttChart()
},
computed: mapState(useBaseStore, {
canWrite: (state) => state.currentList.maxRight > Rights.READ,
}),
methods: {
colorIsDark,
buildTheGanttChart() {
this.setDates()
this.prepareGanttDays()
this.parseTasks()
},
setDates() {
this.startDate = new Date(this.dateFrom)
this.endDate = new Date(this.dateTo)
console.debug('setDates; start date: ', this.startDate, 'end date:', this.endDate, 'date from:', this.dateFrom, 'date to:', this.dateTo)
this.dayOffsetUntilToday = Math.floor((this.now - this.startDate) / 1000 / 60 / 60 / 24) + 1
},
prepareGanttDays() {
console.debug('prepareGanttDays; start date: ', this.startDate, 'end date:', this.endDate)
// Layout: years => [months => [days]]
const years = {}
for (
let d = this.startDate;
d <= this.endDate;
d.setDate(d.getDate() + 1)
) {
const date = new Date(d)
if (years[date.getFullYear() + ''] === undefined) {
years[date.getFullYear() + ''] = {}
}
if (years[date.getFullYear() + ''][date.getMonth() + ''] === undefined) {
years[date.getFullYear() + ''][date.getMonth() + ''] = []
}
years[date.getFullYear() + ''][date.getMonth() + ''].push(date)
this.fullWidth += this.dayWidth
}
console.debug('prepareGanttDays; years:', years)
this.days = years
},
parseTasks() {
this.setDates()
this.loadTasks()
},
async loadTasks() {
this.theTasks = []
this.tasksWithoutDates = []
const getAllTasks = async (page = 1) => {
const tasks = await this.taskCollectionService.getAll({listId: this.listId}, this.params, page)
if (page < this.taskCollectionService.totalPages) {
const nextTasks = await getAllTasks(page + 1)
return tasks.concat(nextTasks)
}
return tasks
}
const tasks = await getAllTasks()
this.theTasks = tasks
.filter((t) => {
if (t.startDate === null && !t.done) {
this.tasksWithoutDates.push(t)
}
return (
t.startDate >= this.startDate &&
t.endDate <= this.endDate
)
})
.map((t) => this.addGantAttributes(t))
.sort(function (a, b) {
if (a.startDate < b.startDate) return -1
if (a.startDate > b.startDate) return 1
return 0
})
},
addGantAttributes(t) {
if (typeof t.durationDays !== 'undefined' && typeof t.offsetDays !== 'undefined') {
return t
}
t.endDate === null ? this.endDate : t.endDate
t.durationDays = Math.floor((t.endDate - t.startDate) / 1000 / 60 / 60 / 24)
t.offsetDays = Math.floor((t.startDate - this.startDate) / 1000 / 60 / 60 / 24)
return t
},
async resizeTask(taskDragged, newRect) {
if (this.isTaskEdit) {
return
}
let newTask = {...taskDragged}
const didntHaveDates = newTask.startDate === null ? true : false
const startDate = new Date(this.startDate)
startDate.setDate(
startDate.getDate() + newRect.left / this.dayWidth,
)
startDate.setUTCHours(0)
startDate.setUTCMinutes(0)
startDate.setUTCSeconds(0)
startDate.setUTCMilliseconds(0)
newTask.startDate = startDate
const endDate = new Date(startDate)
endDate.setDate(
startDate.getDate() + newRect.width / this.dayWidth,
)
newTask.startDate = startDate
newTask.endDate = endDate
// We take the task from the overall tasks array because the one in it has bad data after it was updated once.
// FIXME: This is a workaround. We should use a better mechanism to get the task or, even better,
// prevent it from containing outdated Data in the first place.
for (const tt in this.theTasks) {
if (this.theTasks[tt].id === newTask.id) {
newTask = this.theTasks[tt]
break
}
}
const ganttData = {
endDate: newTask.endDate,
durationDays: newTask.durationDays,
offsetDays: newTask.offsetDays,
}
const r = await this.taskService.update(newTask)
r.endDate = ganttData.endDate
r.durationDays = ganttData.durationDays
r.offsetDays = ganttData.offsetDays
// If the task didn't have dates before, we'll update the list
if (didntHaveDates) {
for (const t in this.tasksWithoutDates) {
if (this.tasksWithoutDates[t].id === r.id) {
this.tasksWithoutDates.splice(t, 1)
break
}
}
this.theTasks.push(this.addGantAttributes(r))
} else {
for (const tt in this.theTasks) {
if (this.theTasks[tt].id === r.id) {
this.theTasks[tt] = this.addGantAttributes(r)
break
}
}
}
},
editTask(task) {
this.taskToEdit = task
this.isTaskEdit = true
},
showCreateNewTask() {
if (!this.newTaskFieldActive) {
// Timeout to not send the form if the field isn't even shown
setTimeout(() => {
this.newTaskFieldActive = true
this.$nextTick(() => this.$refs.newTaskTitleField.focus())
}, 100)
}
},
hideCrateNewTask() {
if (this.newTaskTitle === '') {
this.$nextTick(() => (this.newTaskFieldActive = false))
}
},
async addNewTask() {
if (!this.newTaskFieldActive) {
return
}
const task = new TaskModel({
title: this.newTaskTitle,
listId: this.listId,
})
const r = await this.taskService.create(task)
this.tasksWithoutDates.push(this.addGantAttributes(r))
this.newTaskTitle = ''
this.hideCrateNewTask()
},
formatMonthAndYear(year, month) {
month = month < 10 ? '0' + month : month
const date = new Date(`${year}-${month}-01`)
return formatDate(date, 'MMMM, yyyy')
},
},
})
</script>
<style lang="scss" scoped>
$gantt-border: 1px solid var(--grey-200);
$gantt-vertical-border-color: var(--grey-100);
.gantt-chart {
overflow-x: auto;
border-top: 1px solid var(--grey-200);
.dates {
display: flex;
text-align: center;
.months {
display: flex;
.month {
padding: 0.5rem 0 0;
border-right: $gantt-border;
font-family: $vikunja-font;
font-weight: bold;
&:last-child {
border-right: none;
}
.days {
display: flex;
.day {
padding: 0.5rem 0;
font-weight: normal;
&.today {
background: var(--primary);
color: var(--white);
border-radius: 5px 5px 0 0;
font-weight: bold;
}
.theday {
padding: 0 .5rem;
width: 100%;
display: block;
}
.weekday {
font-size: 0.8rem;
}
}
}
}
}
}
.tasks {
max-width: unset !important;
border-top: $gantt-border;
.row {
height: 45px;
.task {
display: inline-block;
border: 2px solid var(--primary);
font-size: 0.85rem;
margin: 0.5rem;
border-radius: 6px;
padding: 0.25rem 0.5rem;
cursor: grab;
position: relative;
height: 31px !important;
-webkit-touch-callout: none; // iOS Safari
user-select: none; // Non-prefixed version
&.is-current-edit {
border-color: var(--warning) !important;
}
&.has-light-text {
color: var(--grey-100);
&.done span:after {
border-top: 1px solid var(--grey-100);
}
.edit-toggle {
color: var(--grey-100);
}
}
&.has-dark-text {
color: var(--text);
&.done span:after {
border-top: 1px solid var(--dark);
}
.edit-toggle {
color: var(--text);
}
}
&.done span {
position: relative;
&::after {
content: '';
position: absolute;
right: 0;
left: 0;
top: 57%;
}
}
span:not(.high-priority) {
max-width: calc(100% - 20px);
display: inline-block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
&.has-high-priority {
max-width: calc(100% - 90px);
}
&.has-not-so-high-priority {
max-width: calc(100% - 70px);
}
&.has-super-high-priority {
max-width: calc(100% - 111px);
}
&.icon {
width: 10px;
text-align: center;
}
}
.high-priority {
margin: 0 0 0 .5rem;
vertical-align: bottom;
}
.edit-toggle {
float: right;
cursor: pointer;
margin-right: 4px;
}
&.nodate {
border: 2px dashed var(--grey-300);
background: var(--grey-100);
}
&:active {
cursor: grabbing;
}
}
}
}
.taskedit {
position: fixed;
top: 10vh;
right: 10vw;
z-index: 5;
// FIXME: should be an option of the card, e.g. overflow
:deep(.card-content) {
max-height: 60vh;
overflow-y: auto;
}
}
.add-new-task {
padding: 1rem .7rem .4rem .7rem;
display: flex;
max-width: 450px;
.input {
margin-right: .7rem;
font-size: .8rem;
}
.button {
font-size: .68rem;
}
}
}
</style>

View file

@ -9,7 +9,7 @@
<input <input
v-if="editEnabled" v-if="editEnabled"
:disabled="loading || undefined" :disabled="attachmentService.loading || undefined"
@change="uploadNewAttachment()" @change="uploadNewAttachment()"
id="files" id="files"
multiple multiple
@ -35,15 +35,7 @@
:key="a.id" :key="a.id"
@click="viewOrDownload(a)" @click="viewOrDownload(a)"
> >
<div class="filename"> <div class="filename">{{ a.file.name }}</div>
{{ a.file.name }}
<span
v-if="task.coverImageAttachmentId === a.id"
class="is-task-cover"
>
{{ $t('task.attachment.usedAsCover') }}
</span>
</div>
<div class="info"> <div class="info">
<p class="attachment-info-meta"> <p class="attachment-info-meta">
<i18n-t keypath="task.attachment.createdBy" scope="global"> <i18n-t keypath="task.attachment.createdBy" scope="global">
@ -86,17 +78,6 @@
> >
{{ $t('misc.delete') }} {{ $t('misc.delete') }}
</BaseButton> </BaseButton>
<BaseButton
v-if="editEnabled"
class="attachment-info-meta-button"
@click.prevent.stop="setCoverImage(task.coverImageAttachmentId === a.id ? null : a)"
>
{{
task.coverImageAttachmentId === a.id
? $t('task.attachment.unsetAsCover')
: $t('task.attachment.setAsCover')
}}
</BaseButton>
</p> </p>
</div> </div>
</a> </a>
@ -104,7 +85,7 @@
<x-button <x-button
v-if="editEnabled" v-if="editEnabled"
:disabled="loading" :disabled="attachmentService.loading"
@click="filesRef?.click()" @click="filesRef?.click()"
class="mb-4" class="mb-4"
icon="cloud-upload-alt" icon="cloud-upload-alt"
@ -137,7 +118,7 @@
<template #header> <template #header>
<span>{{ $t('task.attachment.delete') }}</span> <span>{{ $t('task.attachment.delete') }}</span>
</template> </template>
<template #text> <template #text>
<p> <p>
{{ $t('task.attachment.deleteText1', {filename: attachmentToDelete.file.name}) }}<br/> {{ $t('task.attachment.deleteText1', {filename: attachmentToDelete.file.name}) }}<br/>
@ -157,14 +138,13 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {ref, shallowReactive, computed} from 'vue' import {ref, shallowReactive, computed, type PropType} from 'vue'
import {useDropZone} from '@vueuse/core' import {useDropZone} from '@vueuse/core'
import User from '@/components/misc/user.vue' import User from '@/components/misc/user.vue'
import BaseButton from '@/components/base/BaseButton.vue' import BaseButton from '@/components/base/BaseButton.vue'
import AttachmentService from '@/services/attachment' import AttachmentService from '@/services/attachment'
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
import type AttachmentModel from '@/models/attachment' import type AttachmentModel from '@/models/attachment'
import type {IAttachment} from '@/modelTypes/IAttachment' import type {IAttachment} from '@/modelTypes/IAttachment'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
@ -175,44 +155,38 @@ import {uploadFiles, generateAttachmentUrl} from '@/helpers/attachments'
import {getHumanSize} from '@/helpers/getHumanSize' import {getHumanSize} from '@/helpers/getHumanSize'
import {useCopyToClipboard} from '@/composables/useCopyToClipboard' import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
import {error, success} from '@/message' import {error, success} from '@/message'
import {useTaskStore} from '@/stores/tasks'
import {useI18n} from 'vue-i18n'
const taskStore = useTaskStore() const props = defineProps({
const {t} = useI18n({useScope: 'global'}) taskId: {
type: Number as PropType<ITask['id']>,
const props = withDefaults(defineProps<{ required: true,
task: ITask, },
initialAttachments?: IAttachment[], initialAttachments: {
editEnabled: boolean, type: Array,
}>(), { },
editEnabled: true, editEnabled: {
default: true,
},
}) })
// FIXME: this should go through the store
const emit = defineEmits(['task-changed'])
const attachmentService = shallowReactive(new AttachmentService()) const attachmentService = shallowReactive(new AttachmentService())
const attachmentStore = useAttachmentStore() const attachmentStore = useAttachmentStore()
const attachments = computed(() => attachmentStore.attachments) const attachments = computed(() => attachmentStore.attachments)
const loading = computed(() => attachmentService.loading || taskStore.isLoading)
function onDrop(files: File[] | null) { function onDrop(files: File[] | null) {
if (files && files.length !== 0) { if (files && files.length !== 0) {
uploadFilesToTask(files) uploadFilesToTask(files)
} }
} }
const {isOverDropZone} = useDropZone(document, onDrop) const { isOverDropZone } = useDropZone(document, onDrop)
function downloadAttachment(attachment: IAttachment) { function downloadAttachment(attachment: IAttachment) {
attachmentService.download(attachment) attachmentService.download(attachment)
} }
const filesRef = ref<HTMLInputElement | null>(null) const filesRef = ref<HTMLInputElement | null>(null)
function uploadNewAttachment() { function uploadNewAttachment() {
const files = filesRef.value?.files const files = filesRef.value?.files
@ -224,7 +198,7 @@ function uploadNewAttachment() {
} }
function uploadFilesToTask(files: File[] | FileList) { function uploadFilesToTask(files: File[] | FileList) {
uploadFiles(attachmentService, props.task.id, files) uploadFiles(attachmentService, props.taskId, files)
} }
const attachmentToDelete = ref<AttachmentModel | null>(null) const attachmentToDelete = ref<AttachmentModel | null>(null)
@ -243,15 +217,16 @@ async function deleteAttachment() {
attachmentStore.removeById(attachmentToDelete.value.id) attachmentStore.removeById(attachmentToDelete.value.id)
success(r) success(r)
setAttachmentToDelete(null) setAttachmentToDelete(null)
} catch (e) { } catch(e) {
error(e) error(e)
} }
} }
const attachmentImageBlobUrl = ref<string | null>(null) const attachmentImageBlobUrl = ref<string | null>(null)
const SUPPORTED_SUFFIX = ['.jpg', '.png', '.bmp', '.gif']
async function viewOrDownload(attachment: AttachmentModel) { async function viewOrDownload(attachment: AttachmentModel) {
if (SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.endsWith(suffix))) { if (SUPPORTED_SUFFIX.some((suffix) => attachment.file.name.endsWith(suffix)) ) {
attachmentImageBlobUrl.value = await attachmentService.getBlobUrl(attachment) attachmentImageBlobUrl.value = await attachmentService.getBlobUrl(attachment)
} else { } else {
downloadAttachment(attachment) downloadAttachment(attachment)
@ -259,15 +234,8 @@ async function viewOrDownload(attachment: AttachmentModel) {
} }
const copy = useCopyToClipboard() const copy = useCopyToClipboard()
function copyUrl(attachment: IAttachment) { function copyUrl(attachment: IAttachment) {
copy(generateAttachmentUrl(props.task.id, attachment.id)) copy(generateAttachmentUrl(props.taskId, attachment.id))
}
async function setCoverImage(attachment: IAttachment | null) {
const task = await taskStore.setCoverImage(props.task, attachment)
emit('task-changed', task)
success({message: t('task.attachment.successfullyChangedCoverImage')})
} }
</script> </script>
@ -348,7 +316,7 @@ async function setCoverImage(attachment: IAttachment | null) {
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) { @media (prefers-reduced-motion: reduce) {
animation: none; animation: none;
} }
@ -370,7 +338,7 @@ async function setCoverImage(attachment: IAttachment | null) {
.attachment-info-meta { .attachment-info-meta {
display: flex; display: flex;
align-items: center; align-items: center;
:deep(.user) { :deep(.user) {
display: flex !important; display: flex !important;
align-items: center; align-items: center;
@ -380,7 +348,7 @@ async function setCoverImage(attachment: IAttachment | null) {
@media screen and (max-width: $mobile) { @media screen and (max-width: $mobile) {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
:deep(.user) { :deep(.user) {
margin: .5rem 0; margin: .5rem 0;
} }
@ -426,13 +394,5 @@ async function setCoverImage(attachment: IAttachment | null) {
} }
} }
.is-task-cover {
background: var(--primary);
color: var(--white);
padding: .25rem .35rem;
border-radius: 4px;
font-size: .75rem;
}
@include modal-transition(); @include modal-transition();
</style> </style>

View file

@ -28,7 +28,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {ref, shallowReactive, watch, nextTick, type PropType} from 'vue' import {ref, shallowReactive, watch, type PropType} from 'vue'
import {useI18n} from 'vue-i18n' import {useI18n} from 'vue-i18n'
import User from '@/components/misc/user.vue' import User from '@/components/misc/user.vue'
@ -43,22 +43,22 @@ import {useTaskStore} from '@/stores/tasks'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
const props = defineProps({ const props = defineProps({
taskId: { taskId: {
type: Number, type: Number,
required: true, required: true,
}, },
listId: { listId: {
type: Number, type: Number,
required: true, required: true,
}, },
disabled: { disabled: {
default: false, default: false,
}, },
modelValue: { modelValue: {
type: Array as PropType<IUser[]>, type: Array as PropType<IUser[]>,
default: () => [], default: () => [],
}, },
}) })
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const taskStore = useTaskStore() const taskStore = useTaskStore()
@ -67,7 +67,6 @@ const {t} = useI18n({useScope: 'global'})
const listUserService = shallowReactive(new ListUserService()) const listUserService = shallowReactive(new ListUserService())
const foundUsers = ref([]) const foundUsers = ref([])
const assignees = ref<IUser[]>([]) const assignees = ref<IUser[]>([])
let isAdding = false
watch( watch(
() => props.modelValue, () => props.modelValue,
@ -81,19 +80,9 @@ watch(
) )
async function addAssignee(user: IUser) { async function addAssignee(user: IUser) {
if (isAdding) { await taskStore.addAssignee({user: user, taskId: props.taskId})
return emit('update:modelValue', assignees.value)
} success({message: t('task.assignee.assignSuccess')})
try {
nextTick(() => isAdding = true)
await taskStore.addAssignee({user: user, taskId: props.taskId})
emit('update:modelValue', assignees.value)
success({message: t('task.assignee.assignSuccess')})
} finally {
nextTick(() => isAdding = false)
}
} }
async function removeAssignee(user: IUser) { async function removeAssignee(user: IUser) {
@ -130,7 +119,6 @@ function clearAllFoundUsers() {
} }
const multiselect = ref() const multiselect = ref()
function focus() { function focus() {
multiselect.value.focus() multiselect.value.focus()
} }

View file

@ -11,70 +11,62 @@
@click.ctrl="() => toggleTaskDone(task)" @click.ctrl="() => toggleTaskDone(task)"
@click.meta="() => toggleTaskDone(task)" @click.meta="() => toggleTaskDone(task)"
> >
<img <span class="task-id">
v-if="coverImageBlobUrl" <Done class="kanban-card__done" :is-done="task.done" variant="small"/>
:src="coverImageBlobUrl" <template v-if="task.identifier === ''">
alt="" #{{ task.index }}
class="cover-image" </template>
/> <template v-else>
<div class="p-2"> {{ task.identifier }}
<span class="task-id"> </template>
<Done class="kanban-card__done" :is-done="task.done" variant="small"/> </span>
<template v-if="task.identifier === ''"> <span
#{{ task.index }} :class="{'overdue': task.dueDate <= new Date() && !task.done}"
</template> class="due-date"
<template v-else> v-if="task.dueDate > 0"
{{ task.identifier }} v-tooltip="formatDateLong(task.dueDate)">
</template> <span class="icon">
<icon :icon="['far', 'calendar-alt']"/>
</span> </span>
<span <time :datetime="formatISO(task.dueDate)">
:class="{'overdue': task.dueDate <= new Date() && !task.done}" {{ formatDateSince(task.dueDate) }}
class="due-date" </time>
v-if="task.dueDate > 0" </span>
v-tooltip="formatDateLong(task.dueDate)"> <h3>{{ task.title }}</h3>
<span class="icon"> <progress
<icon :icon="['far', 'calendar-alt']"/> class="progress is-small"
</span> v-if="task.percentDone > 0"
<time :datetime="formatISO(task.dueDate)"> :value="task.percentDone * 100" max="100">
{{ formatDateSince(task.dueDate) }} {{ task.percentDone * 100 }}%
</time> </progress>
</span> <div class="footer">
<h3>{{ task.title }}</h3> <labels :labels="task.labels"/>
<progress <priority-label :priority="task.priority" :done="task.done"/>
class="progress is-small" <div class="assignees" v-if="task.assignees.length > 0">
v-if="task.percentDone > 0" <user
:value="task.percentDone * 100" max="100"> v-for="u in task.assignees"
{{ task.percentDone * 100 }}% :avatar-size="24"
</progress> :key="task.id + 'assignee' + u.id"
<div class="footer"> :show-username="false"
<labels :labels="task.labels"/> :user="u"
<priority-label :priority="task.priority" :done="task.done"/> />
<div class="assignees" v-if="task.assignees.length > 0">
<user
v-for="u in task.assignees"
:avatar-size="24"
:key="task.id + 'assignee' + u.id"
:show-username="false"
:user="u"
/>
</div>
<checklist-summary :task="task"/>
<span class="icon" v-if="task.attachments.length > 0">
<icon icon="paperclip"/>
</span>
<span v-if="task.description" class="icon">
<icon icon="align-left"/>
</span>
<span class="icon" v-if="task.repeatAfter.amount > 0">
<icon icon="history"/>
</span>
</div> </div>
<checklist-summary :task="task"/>
<span class="icon" v-if="task.attachments.length > 0">
<icon icon="paperclip"/>
</span>
<span v-if="task.description" class="icon">
<icon icon="align-left"/>
</span>
<span class="icon" v-if="task.repeatAfter.amount > 0">
<icon icon="history"/>
</span>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {ref, computed, watch} from 'vue' import {ref, computed} from 'vue'
import {useRouter} from 'vue-router' import {useRouter} from 'vue-router'
import PriorityLabel from '@/components/tasks/partials/priorityLabel.vue' import PriorityLabel from '@/components/tasks/partials/priorityLabel.vue'
@ -85,8 +77,6 @@ import ChecklistSummary from './checklist-summary.vue'
import {TASK_DEFAULT_COLOR, getHexColor} from '@/models/task' import {TASK_DEFAULT_COLOR, getHexColor} from '@/models/task'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
import AttachmentService from '@/services/attachment'
import {formatDateLong, formatISO, formatDateSince} from '@/helpers/time/formatDate' import {formatDateLong, formatISO, formatDateSince} from '@/helpers/time/formatDate'
import {colorIsDark} from '@/helpers/color/colorIsDark' import {colorIsDark} from '@/helpers/color/colorIsDark'
@ -124,29 +114,6 @@ function openTaskDetail() {
state: {backdropView: router.currentRoute.value.fullPath}, state: {backdropView: router.currentRoute.value.fullPath},
}) })
} }
const coverImageBlobUrl = ref<string | null>(null)
async function maybeDownloadCoverImage() {
if (!props.task.coverImageAttachmentId) {
coverImageBlobUrl.value = null
return
}
const attachment = props.task.attachments.find(a => a.id === props.task.coverImageAttachmentId)
if (!attachment || !SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.endsWith(suffix))) {
return
}
const attachmentService = new AttachmentService()
coverImageBlobUrl.value = await attachmentService.getBlobUrl(attachment)
}
watch(
() => props.task.coverImageAttachmentId,
maybeDownloadCoverImage,
{immediate: true},
)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -158,11 +125,12 @@ $task-background: var(--white);
cursor: pointer; cursor: pointer;
box-shadow: var(--shadow-xs); box-shadow: var(--shadow-xs);
display: block; display: block;
border: 3px solid transparent;
font-size: .9rem; font-size: .9rem;
padding: .4rem;
border-radius: $radius; border-radius: $radius;
background: $task-background; background: $task-background;
overflow: hidden;
&.loader-container.is-loading::after { &.loader-container.is-loading::after {
width: 1.5rem; width: 1.5rem;

View file

@ -37,11 +37,7 @@
@create="createAndRelateTask" @create="createAndRelateTask"
> >
<template #searchResult="{option: task}"> <template #searchResult="{option: task}">
<span <span v-if="typeof task !== 'string'" class="search-result">
v-if="typeof task !== 'string'"
class="search-result"
:class="{'is-strikethrough': task.done}"
>
<span <span
class="different-list" class="different-list"
v-if="task.listId !== listId" v-if="task.listId !== listId"

View file

@ -4,7 +4,7 @@
* @param color * @param color
* @returns {string} * @returns {string}
*/ */
export function colorFromHex(color) { export function colorFromHex(color: string) {
if (color.substring(0, 1) === '#') { if (color.substring(0, 1) === '#') {
color = color.substring(1, 7) color = color.substring(1, 7)
} }

View file

@ -6,7 +6,7 @@ import {i18n} from '@/i18n'
const locales = {en: enGB, de, ch: de, fr, ru} const locales = {en: enGB, de, ch: de, fr, ru}
export function dateIsValid(date) { const dateIsValid = date => {
if (date === null) { if (date === null) {
return false return false
} }

View file

@ -169,14 +169,6 @@
"title": "List Title", "title": "List Title",
"color": "Color", "color": "Color",
"lists": "Lists", "lists": "Lists",
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"search": "Type to search for a list…", "search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list", "searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists", "shared": "Shared Lists",
@ -278,6 +270,14 @@
"delete": "Delete" "delete": "Delete"
} }
}, },
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Show tasks which don't have dates set", "showTasksWithoutDates": "Show tasks which don't have dates set",
@ -672,23 +672,13 @@
"updated": "Updated" "updated": "Updated"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Subscribe", "subscribe": "Subscribe",
"unsubscribe": "Unsubscribe", "unsubscribe": "Unsubscribe",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "You are now unsubscribed to this {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Attachments", "title": "Attachments",
@ -700,11 +690,7 @@
"deleteTooltip": "Delete this attachment", "deleteTooltip": "Delete this attachment",
"deleteText1": "Are you sure you want to delete the attachment {filename}?", "deleteText1": "Are you sure you want to delete the attachment {filename}?",
"copyUrl": "Copy URL", "copyUrl": "Copy URL",
"copyUrlTooltip": "Copy the url of this attachment for usage in text", "copyUrlTooltip": "Copy the url of this attachment for usage in text"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Comments", "title": "Comments",
@ -853,12 +839,6 @@
"text1": "Are you sure you want to remove this user from the team?", "text1": "Are you sure you want to remove this user from the team?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!", "text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team." "success": "The user was successfully deleted from the team."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "Název seznamu", "title": "Název seznamu",
"color": "Barva", "color": "Barva",
"lists": "Seznamy", "lists": "Seznamy",
"list": {
"title": "Seznam",
"add": "Přidat",
"addPlaceholder": "Přidat nový úkol…",
"empty": "Tento seznam je nyní prázdný.",
"newTaskCta": "Vytvořit nový úkol.",
"editTask": "Upravit úkol"
},
"search": "Začni psát pro vyhledání seznamu…", "search": "Začni psát pro vyhledání seznamu…",
"searchSelect": "Klikněte nebo stiskněte Enter pro výběr tohoto seznamu", "searchSelect": "Klikněte nebo stiskněte Enter pro výběr tohoto seznamu",
"shared": "Sdílené seznamy", "shared": "Sdílené seznamy",
@ -278,6 +270,14 @@
"delete": "Smazat" "delete": "Smazat"
} }
}, },
"list": {
"title": "Seznam",
"add": "Přidat",
"addPlaceholder": "Přidat nový úkol…",
"empty": "Tento seznam je nyní prázdný.",
"newTaskCta": "Vytvořit nový úkol.",
"editTask": "Upravit úkol"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Zobrazit úkoly, které nemají nastavené datum", "showTasksWithoutDates": "Zobrazit úkoly, které nemají nastavené datum",
@ -672,23 +672,13 @@
"updated": "Aktualizováno" "updated": "Aktualizováno"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "Zde se nemůžete odhlásit, protože jste přihlášeni k odběru {entity} prostřednictvím jeho {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "Aktuálně jste přihlášeni k odběru {entity} a budete dostávat oznámení o změnách.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "Nejste přihlášeni k odběru {entity} a nebudete dostávat upozornění na změny.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Odebírat", "subscribe": "Odebírat",
"unsubscribe": "Odhlásit odběr", "unsubscribe": "Odhlásit odběr",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "Nyní jste přihlášeni k odběru {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "Nyní jste odhlášeni od odběru {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Přílohy", "title": "Přílohy",
@ -700,11 +690,7 @@
"deleteTooltip": "Smazat tuto přílohu", "deleteTooltip": "Smazat tuto přílohu",
"deleteText1": "Opravdu chcete odstranit přílohu {filename}?", "deleteText1": "Opravdu chcete odstranit přílohu {filename}?",
"copyUrl": "Kopírovat URL", "copyUrl": "Kopírovat URL",
"copyUrlTooltip": "Kopírovat URL této přílohy pro použití v textu", "copyUrlTooltip": "Kopírovat URL této přílohy pro použití v textu"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Komentáře", "title": "Komentáře",
@ -853,12 +839,6 @@
"text1": "Opravdu chcete odebrat tohoto uživatele z týmu?", "text1": "Opravdu chcete odebrat tohoto uživatele z týmu?",
"text2": "Ztratí přístup ke všem seznamům a prostorům, k nimž má tento tým přístup. To NEMŮŽE BÝT VZATO ZPĚT!", "text2": "Ztratí přístup ke všem seznamům a prostorům, k nimž má tento tým přístup. To NEMŮŽE BÝT VZATO ZPĚT!",
"success": "Uživatel byl úspěšně odstraněn z týmu." "success": "Uživatel byl úspěšně odstraněn z týmu."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "Listentitel", "title": "Listentitel",
"color": "Farbe", "color": "Farbe",
"lists": "Listen", "lists": "Listen",
"list": {
"title": "Liste",
"add": "Hinzufügen",
"addPlaceholder": "Eine neue Aufgabe hinzufügen …",
"empty": "Diese Liste ist derzeit leer.",
"newTaskCta": "Eine neue Aufgabe erstellen.",
"editTask": "Aufgabe bearbeiten"
},
"search": "Tippe, um nach einer Liste zu suchen…", "search": "Tippe, um nach einer Liste zu suchen…",
"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",
@ -278,6 +270,14 @@
"delete": "Löschen" "delete": "Löschen"
} }
}, },
"list": {
"title": "Liste",
"add": "Hinzufügen",
"addPlaceholder": "Eine neue Aufgabe hinzufügen …",
"empty": "Diese Liste ist derzeit leer.",
"newTaskCta": "Eine neue Aufgabe erstellen.",
"editTask": "Aufgabe bearbeiten"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Aufgaben anzeigen, für die keine Daten festgelegt sind", "showTasksWithoutDates": "Aufgaben anzeigen, für die keine Daten festgelegt sind",
@ -672,23 +672,13 @@
"updated": "Aktualisiert" "updated": "Aktualisiert"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "Du kannst hier nicht de-abonnieren, da du diese Liste über ihren Namespace abonniert hast.", "subscribedThroughParent": "Du kannst hier nicht de-abonnieren, da du diese {entity} durch {parent} abonniert hast.",
"subscribedTaskThroughParentNamespace": "Du kannst hier nicht de-abonnieren, da du diese Aufgabe über ihren Namespace abonniert hast.", "subscribed": "Du erhältst Benachrichtigungen zu dieser {entity}.",
"subscribedTaskThroughParentList": "Du kannst hier nicht de-abonnieren, da du diese Aufgabe über ihre Liste abonniert hast.", "notSubscribed": "Du hast diese {entity} nicht abonniert und erhältst keine Benachrichtigungen über Änderungen.",
"subscribedNamespace": "Du hast diesen Namespace abonniert und erhältst Benachrichtigungen über Änderungen.",
"notSubscribedNamespace": "Du hast diesen Namespace nicht abonniert und erhältst keine Benachrichtigungen über Änderungen.",
"subscribedList": "Du hast diese Liste abonniert und erhältst Benachrichtigungen über Änderungen.",
"notSubscribedList": "Du hast diese Liste nicht abonniert und erhältst keine Benachrichtigungen über Änderungen.",
"subscribedTask": "Du hast diese Aufgabe abonniert und erhältst Benachrichtigungen über Änderungen.",
"notSubscribedTask": "Du hast diese Aufgabe nicht abonniert und erhältst keine Benachrichtigungen über Änderungen.",
"subscribe": "Abonnieren", "subscribe": "Abonnieren",
"unsubscribe": "Abbestellen", "unsubscribe": "Abbestellen",
"subscribeSuccessNamespace": "Du hast diesen Namespace jetzt abonniert", "subscribeSuccess": "Du hast jetzt diese {entity} abonniert",
"unsubscribeSuccessNamespace": "Du hast diesen Namespace jetzt nicht mehr abonniert", "unsubscribeSuccess": "Du hast diese {entity} jetzt abbestellt"
"subscribeSuccessList": "Du hast diese Liste jetzt abonniert",
"unsubscribeSuccessList": "Du hast diese Liste jetzt nicht mehr abonniert",
"subscribeSuccessTask": "Du hast diese Aufgabe jetzt abonniert",
"unsubscribeSuccessTask": "Du hast diese Aufgabe jetzt nicht mehr abonniert"
}, },
"attachment": { "attachment": {
"title": "Anhänge", "title": "Anhänge",
@ -700,11 +690,7 @@
"deleteTooltip": "Diesen Anhang löschen", "deleteTooltip": "Diesen Anhang löschen",
"deleteText1": "Soll der Anhang {filename} gelöscht werden?", "deleteText1": "Soll der Anhang {filename} gelöscht werden?",
"copyUrl": "URL kopieren", "copyUrl": "URL kopieren",
"copyUrlTooltip": "Die URL dieses Anhangs zur Verwendung im Text kopieren", "copyUrlTooltip": "Die URL dieses Anhangs zur Verwendung im Text kopieren"
"setAsCover": "Als Titelbild setzen",
"unsetAsCover": "Titelbild entfernen",
"successfullyChangedCoverImage": "Das Titelbild wurde erfolgreich geändert.",
"usedAsCover": "Titelbild"
}, },
"comment": { "comment": {
"title": "Kommentare", "title": "Kommentare",
@ -853,12 +839,6 @@
"text1": "Bist du sicher, dass du diese:n Benutzer:in aus dem Team entfernen willst?", "text1": "Bist du sicher, dass du diese:n Benutzer:in aus dem Team entfernen willst?",
"text2": "Diese:r Benutzer:in verliert den Zugriff auf alle Listen und Namespaces auf die dieses Team Zugriff hat. Dies kann nicht rückgängig gemacht werden!", "text2": "Diese:r Benutzer:in verliert den Zugriff auf alle Listen und Namespaces auf die dieses Team Zugriff hat. Dies kann nicht rückgängig gemacht werden!",
"success": "Der:die Benutzer:in wurde erfolgreich aus dem Team gelöscht." "success": "Der:die Benutzer:in wurde erfolgreich aus dem Team gelöscht."
},
"leave": {
"title": "Team verlassen",
"text1": "Bist du sicher, dass du dieses Team verlassen willst?",
"text2": "Du wirst Zugriff auf alle Listen und Namespaces verlieren, auf die dieses Team Zugriff hat. Wenn du deine Meinung änderst, musst du durch einen Team-Admin wieder hinzugefügt werden.",
"success": "Du hast das Team erfolgreich verlassen."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "Liste Titl", "title": "Liste Titl",
"color": "Farb", "color": "Farb",
"lists": "Listene", "lists": "Listene",
"list": {
"title": "Liste",
"add": "Hinzuefüege",
"addPlaceholder": "E neui Uufgab erstelle…",
"empty": "D'Liste isch momentan leer.",
"newTaskCta": "Neui Uufgab erstelle.",
"editTask": "Uufgab bearbeite"
},
"search": "Schriib, um nachere Liste z'sueche…", "search": "Schriib, um nachere Liste z'sueche…",
"searchSelect": "Druck uf Enter um die Liste uuszwähle", "searchSelect": "Druck uf Enter um die Liste uuszwähle",
"shared": "Teilti Liste", "shared": "Teilti Liste",
@ -278,6 +270,14 @@
"delete": "Chüble" "delete": "Chüble"
} }
}, },
"list": {
"title": "Liste",
"add": "Hinzuefüege",
"addPlaceholder": "E neui Uufgab erstelle…",
"empty": "D'Liste isch momentan leer.",
"newTaskCta": "Neui Uufgab erstelle.",
"editTask": "Uufgab bearbeite"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Zeig Uufgabe, wo kei Date hend", "showTasksWithoutDates": "Zeig Uufgabe, wo kei Date hend",
@ -672,23 +672,13 @@
"updated": "Aktualisiert" "updated": "Aktualisiert"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "Du kannst hier nicht de-abonnieren, da du diese Liste über ihren Namespace abonniert hast.", "subscribedThroughParent": "Du chasch da nid deabonnierä, weil du zu dere {entity} durch {parent} dezue abonniert bisch.",
"subscribedTaskThroughParentNamespace": "Du kannst hier nicht de-abonnieren, da du diese Aufgabe über ihren Namespace abonniert hast.", "subscribed": "Du bisch momentan zu dere {entity} abonniert und bechunsch Benachrichtigunge für Änderige.",
"subscribedTaskThroughParentList": "Du kannst hier nicht de-abonnieren, da du diese Aufgabe über ihre Liste abonniert hast.", "notSubscribed": "Du bisch momentan nid zu dere {entity} abonniert und bechunsch kei Benachrichtigunge für Änderige.",
"subscribedNamespace": "Du hast diesen Namespace abonniert und erhältst Benachrichtigungen über Änderungen.",
"notSubscribedNamespace": "Du hast diesen Namespace nicht abonniert und erhältst keine Benachrichtigungen über Änderungen.",
"subscribedList": "Du hast diese Liste abonniert und erhältst Benachrichtigungen über Änderungen.",
"notSubscribedList": "Du hast diese Liste nicht abonniert und erhältst keine Benachrichtigungen über Änderungen.",
"subscribedTask": "Du hast diese Aufgabe abonniert und erhältst Benachrichtigungen über Änderungen.",
"notSubscribedTask": "Du hast diese Aufgabe nicht abonniert und erhältst keine Benachrichtigungen über Änderungen.",
"subscribe": "Abooniere", "subscribe": "Abooniere",
"unsubscribe": "Deabonniere", "unsubscribe": "Deabonniere",
"subscribeSuccessNamespace": "Du hast diesen Namespace jetzt abonniert", "subscribeSuccess": "Du hesch die {entity} abonniert",
"unsubscribeSuccessNamespace": "Du hast diesen Namespace jetzt nicht mehr abonniert", "unsubscribeSuccess": "Du hesch die {entity} deabonniert"
"subscribeSuccessList": "Du hast diese Liste jetzt abonniert",
"unsubscribeSuccessList": "Du hast diese Liste jetzt nicht mehr abonniert",
"subscribeSuccessTask": "Du hast diese Aufgabe jetzt abonniert",
"unsubscribeSuccessTask": "Du hast diese Aufgabe jetzt nicht mehr abonniert"
}, },
"attachment": { "attachment": {
"title": "Aahhäng", "title": "Aahhäng",
@ -700,11 +690,7 @@
"deleteTooltip": "De Aahhang lösche", "deleteTooltip": "De Aahhang lösche",
"deleteText1": "Bisch du dir sicher, dass du de Aahang {filename} lösche wetsch?", "deleteText1": "Bisch du dir sicher, dass du de Aahang {filename} lösche wetsch?",
"copyUrl": "URL Kopierä", "copyUrl": "URL Kopierä",
"copyUrlTooltip": "D'Url vo dem Aahang kopiere, um sie im Text zbruuche", "copyUrlTooltip": "D'Url vo dem Aahang kopiere, um sie im Text zbruuche"
"setAsCover": "Als Titelbild setzen",
"unsetAsCover": "Titelbild entfernen",
"successfullyChangedCoverImage": "Das Titelbild wurde erfolgreich geändert.",
"usedAsCover": "Titelbild"
}, },
"comment": { "comment": {
"title": "Kommentär", "title": "Kommentär",
@ -853,12 +839,6 @@
"text1": "Bisch du dir sicher, dass du de Benutzer usm Team werfe wetsch?", "text1": "Bisch du dir sicher, dass du de Benutzer usm Team werfe wetsch?",
"text2": "Diese:r Benutzer:in verliert den Zugriff auf alle Listen und Namespaces auf die dieses Team Zugriff hat. Dies kann nicht rückgängig gemacht werden!", "text2": "Diese:r Benutzer:in verliert den Zugriff auf alle Listen und Namespaces auf die dieses Team Zugriff hat. Dies kann nicht rückgängig gemacht werden!",
"success": "Benutzer erfolgriich usegworfe." "success": "Benutzer erfolgriich usegworfe."
},
"leave": {
"title": "Team verlassen",
"text1": "Bist du sicher, dass du dieses Team verlassen willst?",
"text2": "Du wirst Zugriff auf alle Listen und Namespaces verlieren, auf die dieses Team Zugriff hat. Wenn du deine Meinung änderst, musst du durch einen Team-Admin wieder hinzugefügt werden.",
"success": "Du hast das Team erfolgreich verlassen."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,7 +169,6 @@
"title": "List Title", "title": "List Title",
"color": "Color", "color": "Color",
"lists": "Lists", "lists": "Lists",
"list": "List",
"search": "Type to search for a list…", "search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list", "searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists", "shared": "Shared Lists",
@ -286,8 +285,8 @@
"default": "Default", "default": "Default",
"month": "Month", "month": "Month",
"day": "Day", "day": "Day",
"from": "From", "hour": "Hour",
"to": "To", "range": "Range",
"noDates": "This task has no dates set." "noDates": "This task has no dates set."
}, },
"table": { "table": {
@ -676,23 +675,13 @@
"updated": "Updated" "updated": "Updated"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Subscribe", "subscribe": "Subscribe",
"unsubscribe": "Unsubscribe", "unsubscribe": "Unsubscribe",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "You are now unsubscribed to this {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Attachments", "title": "Attachments",
@ -704,11 +693,7 @@
"deleteTooltip": "Delete this attachment", "deleteTooltip": "Delete this attachment",
"deleteText1": "Are you sure you want to delete the attachment {filename}?", "deleteText1": "Are you sure you want to delete the attachment {filename}?",
"copyUrl": "Copy URL", "copyUrl": "Copy URL",
"copyUrlTooltip": "Copy the url of this attachment for usage in text", "copyUrlTooltip": "Copy the url of this attachment for usage in text"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Comments", "title": "Comments",
@ -857,12 +842,6 @@
"text1": "Are you sure you want to remove this user from the team?", "text1": "Are you sure you want to remove this user from the team?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!", "text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team." "success": "The user was successfully deleted from the team."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "List Title", "title": "List Title",
"color": "Color", "color": "Color",
"lists": "Lists", "lists": "Lists",
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"search": "Type to search for a list…", "search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list", "searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists", "shared": "Shared Lists",
@ -278,6 +270,14 @@
"delete": "Delete" "delete": "Delete"
} }
}, },
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Show tasks which don't have dates set", "showTasksWithoutDates": "Show tasks which don't have dates set",
@ -672,23 +672,13 @@
"updated": "Updated" "updated": "Updated"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Subscribe", "subscribe": "Subscribe",
"unsubscribe": "Unsubscribe", "unsubscribe": "Unsubscribe",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "You are now unsubscribed to this {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Attachments", "title": "Attachments",
@ -700,11 +690,7 @@
"deleteTooltip": "Delete this attachment", "deleteTooltip": "Delete this attachment",
"deleteText1": "Are you sure you want to delete the attachment {filename}?", "deleteText1": "Are you sure you want to delete the attachment {filename}?",
"copyUrl": "Copy URL", "copyUrl": "Copy URL",
"copyUrlTooltip": "Copy the url of this attachment for usage in text", "copyUrlTooltip": "Copy the url of this attachment for usage in text"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Comments", "title": "Comments",
@ -853,12 +839,6 @@
"text1": "Are you sure you want to remove this user from the team?", "text1": "Are you sure you want to remove this user from the team?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!", "text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team." "success": "The user was successfully deleted from the team."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "Nom de la liste", "title": "Nom de la liste",
"color": "Couleur", "color": "Couleur",
"lists": "Listes", "lists": "Listes",
"list": {
"title": "Liste",
"add": "Ajouter",
"addPlaceholder": "Ajouter une nouvelle tâche…",
"empty": "Cette liste est actuellement vide.",
"newTaskCta": "Créer une nouvelle tâche.",
"editTask": "Modifier la tâche"
},
"search": "Écris pour rechercher une liste…", "search": "Écris pour rechercher une liste…",
"searchSelect": "Clique ou appuie sur la touche Entrée pour sélectionner cette liste", "searchSelect": "Clique ou appuie sur la touche Entrée pour sélectionner cette liste",
"shared": "Listes partagées", "shared": "Listes partagées",
@ -278,6 +270,14 @@
"delete": "Supprimer" "delete": "Supprimer"
} }
}, },
"list": {
"title": "Liste",
"add": "Ajouter",
"addPlaceholder": "Ajouter une nouvelle tâche…",
"empty": "Cette liste est actuellement vide.",
"newTaskCta": "Créer une nouvelle tâche.",
"editTask": "Modifier la tâche"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Afficher les tâches pour lesquelles aucune date na été fixée", "showTasksWithoutDates": "Afficher les tâches pour lesquelles aucune date na été fixée",
@ -672,23 +672,13 @@
"updated": "Mis à jour" "updated": "Mis à jour"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "Tu ne peux pas te désabonner ici car tu es abonné·e à cette {entity} par le biais de son {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "Tu es actuellement abonné·e à cette {entity} et recevras des notifications pour les changements.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "Tu nes pas abonné·e à cette {entity} et ne recevras pas de notifications pour les changements.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Sabonner", "subscribe": "Sabonner",
"unsubscribe": "Se désabonner", "unsubscribe": "Se désabonner",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "Tu es maintenant abonné·e à cette {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "Tu es maintenant désabonné·e de cette {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Pièces jointes", "title": "Pièces jointes",
@ -700,11 +690,7 @@
"deleteTooltip": "Supprimer cette pièce jointe", "deleteTooltip": "Supprimer cette pièce jointe",
"deleteText1": "Supprimer la pièce jointe {filename} ?", "deleteText1": "Supprimer la pièce jointe {filename} ?",
"copyUrl": "Copier lURL", "copyUrl": "Copier lURL",
"copyUrlTooltip": "Copier lURL de cette pièce jointe pour lutiliser dans le texte", "copyUrlTooltip": "Copier lURL de cette pièce jointe pour lutiliser dans le texte"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Commentaires", "title": "Commentaires",
@ -853,12 +839,6 @@
"text1": "Retirer cette personne de léquipe ?", "text1": "Retirer cette personne de léquipe ?",
"text2": "Ils perdront l'accès à toutes les listes et espaces de noms auxquels cette équipe a accès. Ceci NE PEUT PAS ÊTRE ANNULÉ !", "text2": "Ils perdront l'accès à toutes les listes et espaces de noms auxquels cette équipe a accès. Ceci NE PEUT PAS ÊTRE ANNULÉ !",
"success": "Utilisateur·rice retiré·e de léquipe." "success": "Utilisateur·rice retiré·e de léquipe."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "Titolo della Lista", "title": "Titolo della Lista",
"color": "Colore", "color": "Colore",
"lists": "Liste", "lists": "Liste",
"list": {
"title": "Lista",
"add": "Aggiungi",
"addPlaceholder": "Aggiungi una nuova attività…",
"empty": "Questa lista è attualmente vuota.",
"newTaskCta": "Crea una nuova attività.",
"editTask": "Modifica Attività"
},
"search": "Digita per cercare una lista…", "search": "Digita per cercare una lista…",
"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",
@ -278,6 +270,14 @@
"delete": "Elimina" "delete": "Elimina"
} }
}, },
"list": {
"title": "Lista",
"add": "Aggiungi",
"addPlaceholder": "Aggiungi una nuova attività…",
"empty": "Questa lista è attualmente vuota.",
"newTaskCta": "Crea una nuova attività.",
"editTask": "Modifica Attività"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Mostra attività che non hanno date impostate", "showTasksWithoutDates": "Mostra attività che non hanno date impostate",
@ -672,23 +672,13 @@
"updated": "Aggiornato" "updated": "Aggiornato"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "Non puoi annullare l'iscrizione qui perché sei iscritto a questo {entity} attraverso il suo {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "Sei attualmente iscritto a questo {entity} e riceverai notifiche per le modifiche.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "Non sei iscritto a questo {entity} e non riceverai notifiche per le modifiche.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Iscriviti", "subscribe": "Iscriviti",
"unsubscribe": "Disiscriviti", "unsubscribe": "Disiscriviti",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "Ti sei iscritto a questo {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "Ti sei disiscritto a questo {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Allegati", "title": "Allegati",
@ -700,11 +690,7 @@
"deleteTooltip": "Elimina questo allegato", "deleteTooltip": "Elimina questo allegato",
"deleteText1": "Sei sicuro di voler eliminare l'allegato {filename}?", "deleteText1": "Sei sicuro di voler eliminare l'allegato {filename}?",
"copyUrl": "Copia URL", "copyUrl": "Copia URL",
"copyUrlTooltip": "Copia l'URL di questo allegato per usarlo nel testo", "copyUrlTooltip": "Copia l'URL di questo allegato per usarlo nel testo"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Commenti", "title": "Commenti",
@ -853,12 +839,6 @@
"text1": "Confermi di voler rimuovere questo utente dal gruppo?", "text1": "Confermi di voler rimuovere questo utente dal gruppo?",
"text2": "Perderanno l'accesso a tutte le liste e i namespace a cui questo gruppo ha accesso. NON PUÒ ESSERE RIPRISTINATO!", "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."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "Lijst titel", "title": "Lijst titel",
"color": "Kleur", "color": "Kleur",
"lists": "Lijsten", "lists": "Lijsten",
"list": {
"title": "Lijst",
"add": "Toevoegen",
"addPlaceholder": "Voeg een nieuwe taak toe…",
"empty": "Deze lijst is momenteel leeg.",
"newTaskCta": "Creëer een nieuwe taak.",
"editTask": "Taak bewerken"
},
"search": "Typ om naar een lijst te zoeken…", "search": "Typ om naar een lijst te zoeken…",
"searchSelect": "Klik of druk op enter om deze lijst te selecteren", "searchSelect": "Klik of druk op enter om deze lijst te selecteren",
"shared": "Gedeelde lijsten", "shared": "Gedeelde lijsten",
@ -278,6 +270,14 @@
"delete": "Verwijderen" "delete": "Verwijderen"
} }
}, },
"list": {
"title": "Lijst",
"add": "Toevoegen",
"addPlaceholder": "Voeg een nieuwe taak toe…",
"empty": "Deze lijst is momenteel leeg.",
"newTaskCta": "Creëer een nieuwe taak.",
"editTask": "Taak bewerken"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Toon taken waarvoor geen datums zijn ingesteld", "showTasksWithoutDates": "Toon taken waarvoor geen datums zijn ingesteld",
@ -672,23 +672,13 @@
"updated": "Bijgewerkt" "updated": "Bijgewerkt"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Subscribe", "subscribe": "Subscribe",
"unsubscribe": "Unsubscribe", "unsubscribe": "Unsubscribe",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "You are now unsubscribed to this {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Bijlagen", "title": "Bijlagen",
@ -700,11 +690,7 @@
"deleteTooltip": "Verwijder deze bijlage", "deleteTooltip": "Verwijder deze bijlage",
"deleteText1": "Weet je zeker dat je de bijlage {filename} wilt verwijderen?", "deleteText1": "Weet je zeker dat je de bijlage {filename} wilt verwijderen?",
"copyUrl": "URL kopiëren", "copyUrl": "URL kopiëren",
"copyUrlTooltip": "Copy the url of this attachment for usage in text", "copyUrlTooltip": "Copy the url of this attachment for usage in text"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Reacties", "title": "Reacties",
@ -853,12 +839,6 @@
"text1": "Weet je zeker dat je deze gebruiker wilt verwijderen uit het team?", "text1": "Weet je zeker dat je deze gebruiker wilt verwijderen uit het team?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!", "text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team." "success": "The user was successfully deleted from the team."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "Tytuł listy", "title": "Tytuł listy",
"color": "Kolor", "color": "Kolor",
"lists": "Listy", "lists": "Listy",
"list": {
"title": "Lista",
"add": "Dodaj",
"addPlaceholder": "Dodaj nowe zadanie…",
"empty": "Ta lista jest obecnie pusta.",
"newTaskCta": "Utwórz nowe zadanie.",
"editTask": "Edytuj zadanie"
},
"search": "Wpisz, aby wyszukać listę…", "search": "Wpisz, aby wyszukać listę…",
"searchSelect": "Kliknij lub naciśnij Enter, aby wybrać tę listę", "searchSelect": "Kliknij lub naciśnij Enter, aby wybrać tę listę",
"shared": "Współdzielone listy", "shared": "Współdzielone listy",
@ -278,6 +270,14 @@
"delete": "Usuń" "delete": "Usuń"
} }
}, },
"list": {
"title": "Lista",
"add": "Dodaj",
"addPlaceholder": "Dodaj nowe zadanie…",
"empty": "Ta lista jest obecnie pusta.",
"newTaskCta": "Utwórz nowe zadanie.",
"editTask": "Edytuj zadanie"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Pokaż zadania, które nie mają ustawionych dat", "showTasksWithoutDates": "Pokaż zadania, które nie mają ustawionych dat",
@ -672,23 +672,13 @@
"updated": "Zaktualizowano" "updated": "Zaktualizowano"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "Nie możesz zrezygnować z subskrypcji, ponieważ subskrybujesz {entity} za pośrednictwem {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "Obecnie subskrybujesz {entity} i będziesz otrzymywać powiadomienia o zmianach.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "Nie subskrybujesz {entity} i nie będziesz otrzymywać powiadomień o zmianach.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Subskrybuj", "subscribe": "Subskrybuj",
"unsubscribe": "Anuluj subskrypcję", "unsubscribe": "Anuluj subskrypcję",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "Od teraz subskrybujesz {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "Już nie subskrybujesz {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Załączniki", "title": "Załączniki",
@ -700,11 +690,7 @@
"deleteTooltip": "Usuń ten załącznik", "deleteTooltip": "Usuń ten załącznik",
"deleteText1": "Czy na pewno chcesz usunąć załącznik {filename}?", "deleteText1": "Czy na pewno chcesz usunąć załącznik {filename}?",
"copyUrl": "Kopiuj URL", "copyUrl": "Kopiuj URL",
"copyUrlTooltip": "Skopiuj adres URL tego załącznika do użycia w tekście", "copyUrlTooltip": "Skopiuj adres URL tego załącznika do użycia w tekście"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Komentarze", "title": "Komentarze",
@ -853,12 +839,6 @@
"text1": "Czy na pewno chcesz usunąć tego użytkownika z zespołu?", "text1": "Czy na pewno chcesz usunąć tego użytkownika z zespołu?",
"text2": "Utraci on dostęp do wszystkich list i sekcji, do których ma dostęp ten zespół. Tego NIE DA SIĘ COFNĄĆ!", "text2": "Utraci on dostęp do wszystkich list i sekcji, do których ma dostęp ten zespół. Tego NIE DA SIĘ COFNĄĆ!",
"success": "Użytkownik został pomyślnie usunięty z zespołu." "success": "Użytkownik został pomyślnie usunięty z zespołu."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "List Title", "title": "List Title",
"color": "Color", "color": "Color",
"lists": "Lists", "lists": "Lists",
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"search": "Type to search for a list…", "search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list", "searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists", "shared": "Shared Lists",
@ -278,6 +270,14 @@
"delete": "Delete" "delete": "Delete"
} }
}, },
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Show tasks which don't have dates set", "showTasksWithoutDates": "Show tasks which don't have dates set",
@ -672,23 +672,13 @@
"updated": "Updated" "updated": "Updated"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Subscribe", "subscribe": "Subscribe",
"unsubscribe": "Unsubscribe", "unsubscribe": "Unsubscribe",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "You are now unsubscribed to this {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Attachments", "title": "Attachments",
@ -700,11 +690,7 @@
"deleteTooltip": "Delete this attachment", "deleteTooltip": "Delete this attachment",
"deleteText1": "Are you sure you want to delete the attachment {filename}?", "deleteText1": "Are you sure you want to delete the attachment {filename}?",
"copyUrl": "Copy URL", "copyUrl": "Copy URL",
"copyUrlTooltip": "Copy the url of this attachment for usage in text", "copyUrlTooltip": "Copy the url of this attachment for usage in text"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Comments", "title": "Comments",
@ -853,12 +839,6 @@
"text1": "Are you sure you want to remove this user from the team?", "text1": "Are you sure you want to remove this user from the team?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!", "text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team." "success": "The user was successfully deleted from the team."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "Título da Lista", "title": "Título da Lista",
"color": "Cor", "color": "Cor",
"lists": "Listas", "lists": "Listas",
"list": {
"title": "Lista",
"add": "Adicionar",
"addPlaceholder": "Adicionar uma nova tarefa…",
"empty": "Esta lista está atualmente vazia.",
"newTaskCta": "Cria uma nova tarefa.",
"editTask": "Editar Tarefa"
},
"search": "Escreve para pesquisar por uma lista…", "search": "Escreve para pesquisar por uma lista…",
"searchSelect": "Clica ou pressiona Enter para selecionar esta lista", "searchSelect": "Clica ou pressiona Enter para selecionar esta lista",
"shared": "Listas Partilhadas", "shared": "Listas Partilhadas",
@ -278,6 +270,14 @@
"delete": "Eliminar" "delete": "Eliminar"
} }
}, },
"list": {
"title": "Lista",
"add": "Adicionar",
"addPlaceholder": "Adicionar uma nova tarefa…",
"empty": "Esta lista está atualmente vazia.",
"newTaskCta": "Cria uma nova tarefa.",
"editTask": "Editar Tarefa"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Mostrar tarefas que não têm datas atríbuidas", "showTasksWithoutDates": "Mostrar tarefas que não têm datas atríbuidas",
@ -672,23 +672,13 @@
"updated": "Atualizado" "updated": "Atualizado"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "Não podes cancelar a tua subscrição aqui porque estás subscrito nesta lista através do seu espaço.", "subscribedThroughParent": "Não podes cancelar a tua subscrição aqui porque estás subscrito nesta {entity} através de {parent}.",
"subscribedTaskThroughParentNamespace": "Não podes cancelar a tua subscrição aqui porque estás subscrito nesta tarefa através do seu espaço.", "subscribed": "Estás atualmente subscrito a esta {entity} e serás notificado de alterações.",
"subscribedTaskThroughParentList": "Não podes cancelar a tua subscrição aqui porque estás subscrito nesta tarefa através da sua lista.", "notSubscribed": "Não estás subscrito a esta {entity} e não serás notificado de alterações.",
"subscribedNamespace": "Estás atualmente subscrito a este espaço e serás notificado de alterações.",
"notSubscribedNamespace": "Não estás subscrito a este espaço e não serás notificado de alterações.",
"subscribedList": "Estás atualmente subscrito a esta lista e serás notificado de alterações.",
"notSubscribedList": "Não estás subscrito a esta lista e não serás notificado de alterações.",
"subscribedTask": "Estás atualmente subscrito a esta tarefa e serás notificado de alterações.",
"notSubscribedTask": "Não estás subscrito a esta tarefa e não serás notificado de alterações.",
"subscribe": "Subscrever", "subscribe": "Subscrever",
"unsubscribe": "Remover Subscrição", "unsubscribe": "Remover Subscrição",
"subscribeSuccessNamespace": "Estás agora subscrito a este espaço", "subscribeSuccess": "Estás agora subscrito a esta {entity}",
"unsubscribeSuccessNamespace": "Não estás mais subcrito a este espaço", "unsubscribeSuccess": "Não estás mais subcrito a esta {entity}"
"subscribeSuccessList": "Estás agora subscrito a esta lista",
"unsubscribeSuccessList": "Não estás mais subcrito a esta lista",
"subscribeSuccessTask": "Estás agora subscrito a esta tarefa",
"unsubscribeSuccessTask": "Não estás mais subcrito a esta tarefa"
}, },
"attachment": { "attachment": {
"title": "Anexos", "title": "Anexos",
@ -700,11 +690,7 @@
"deleteTooltip": "Eliminar este anexo", "deleteTooltip": "Eliminar este anexo",
"deleteText1": "Tens a certeza que pretendes eliminar o anexo {filename}?", "deleteText1": "Tens a certeza que pretendes eliminar o anexo {filename}?",
"copyUrl": "Copiar URL", "copyUrl": "Copiar URL",
"copyUrlTooltip": "Copia o url deste anexo para o utilizar no texto", "copyUrlTooltip": "Copia o url deste anexo para o utilizar no texto"
"setAsCover": "Criar capa",
"unsetAsCover": "Remover capa",
"successfullyChangedCoverImage": "A imagem de capa foi alterada com sucesso.",
"usedAsCover": "Imagem de capa"
}, },
"comment": { "comment": {
"title": "Comentários", "title": "Comentários",
@ -853,12 +839,6 @@
"text1": "Tens a certeza que pretendes remover este utilizador da equipa?", "text1": "Tens a certeza que pretendes remover este utilizador da equipa?",
"text2": "Eles perderão o acesso a todas as listas e espaços a que esta equipa tem acesso. Isto NÃO PODER SER REVERTIDO!", "text2": "Eles perderão o acesso a todas as listas e espaços a que esta equipa tem acesso. Isto NÃO PODER SER REVERTIDO!",
"success": "O utilizador foi removido da equipa com sucesso." "success": "O utilizador foi removido da equipa com sucesso."
},
"leave": {
"title": "Sair da equipa",
"text1": "Tens a certeza de que queres sair desta equipa?",
"text2": "Vais perder acesso a todas as listas e espaços a que esta equipa tem acesso. Se mudares de ideias, vais necessitar que um administrador da equipa te adicione novamente.",
"success": "Saíste da equipa com sucesso."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "List Title", "title": "List Title",
"color": "Color", "color": "Color",
"lists": "Lists", "lists": "Lists",
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"search": "Type to search for a list…", "search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list", "searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists", "shared": "Shared Lists",
@ -278,6 +270,14 @@
"delete": "Delete" "delete": "Delete"
} }
}, },
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Show tasks which don't have dates set", "showTasksWithoutDates": "Show tasks which don't have dates set",
@ -672,23 +672,13 @@
"updated": "Updated" "updated": "Updated"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Subscribe", "subscribe": "Subscribe",
"unsubscribe": "Unsubscribe", "unsubscribe": "Unsubscribe",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "You are now unsubscribed to this {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Attachments", "title": "Attachments",
@ -700,11 +690,7 @@
"deleteTooltip": "Delete this attachment", "deleteTooltip": "Delete this attachment",
"deleteText1": "Are you sure you want to delete the attachment {filename}?", "deleteText1": "Are you sure you want to delete the attachment {filename}?",
"copyUrl": "Copy URL", "copyUrl": "Copy URL",
"copyUrlTooltip": "Copy the url of this attachment for usage in text", "copyUrlTooltip": "Copy the url of this attachment for usage in text"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Comments", "title": "Comments",
@ -853,12 +839,6 @@
"text1": "Are you sure you want to remove this user from the team?", "text1": "Are you sure you want to remove this user from the team?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!", "text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team." "success": "The user was successfully deleted from the team."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "Название списка", "title": "Название списка",
"color": "Цвет", "color": "Цвет",
"lists": "Списки", "lists": "Списки",
"list": {
"title": "Список",
"add": "Добавить",
"addPlaceholder": "Добавить новую задачу…",
"empty": "Список сейчас пуст.",
"newTaskCta": "Создать новую задачу.",
"editTask": "Изменить задачу"
},
"search": "Введи запрос для поиска списка…", "search": "Введи запрос для поиска списка…",
"searchSelect": "Кликни или нажми Enter для выбора этого списка", "searchSelect": "Кликни или нажми Enter для выбора этого списка",
"shared": "Общие списки", "shared": "Общие списки",
@ -278,6 +270,14 @@
"delete": "Удалить" "delete": "Удалить"
} }
}, },
"list": {
"title": "Список",
"add": "Добавить",
"addPlaceholder": "Добавить новую задачу…",
"empty": "Список сейчас пуст.",
"newTaskCta": "Создать новую задачу.",
"editTask": "Изменить задачу"
},
"gantt": { "gantt": {
"title": "Гант", "title": "Гант",
"showTasksWithoutDates": "Показать задачи без установленной даты", "showTasksWithoutDates": "Показать задачи без установленной даты",
@ -672,23 +672,13 @@
"updated": "Дата изменения" "updated": "Дата изменения"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "Ты не можешь отписаться здесь, потому что ты подписан на {entity} через {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "Ты подписан на {entity} и будешь получать уведомления об изменениях.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "Ты не подписан на {entity} и не будешь получать уведомления об изменениях.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Подписаться", "subscribe": "Подписаться",
"unsubscribe": "Отписаться", "unsubscribe": "Отписаться",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "Ты подписался на {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "Ты отписался от {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Вложения", "title": "Вложения",
@ -700,11 +690,7 @@
"deleteTooltip": "Удалить это вложение", "deleteTooltip": "Удалить это вложение",
"deleteText1": "Удалить вложение {filename}?", "deleteText1": "Удалить вложение {filename}?",
"copyUrl": "Скопировать URL", "copyUrl": "Скопировать URL",
"copyUrlTooltip": "Скопировать ссылку на это вложение для использования в тексте", "copyUrlTooltip": "Скопировать ссылку на это вложение для использования в тексте"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Комментарии", "title": "Комментарии",
@ -853,12 +839,6 @@
"text1": "Удалить этого пользователя из команды?", "text1": "Удалить этого пользователя из команды?",
"text2": "Пользователь потеряет доступ ко всем спискам и пространствам имён, к котором есть доступ у команды. Это действие отменить нельзя!", "text2": "Пользователь потеряет доступ ко всем спискам и пространствам имён, к котором есть доступ у команды. Это действие отменить нельзя!",
"success": "Пользователь удалён из команды." "success": "Пользователь удалён из команды."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "List Title", "title": "List Title",
"color": "Color", "color": "Color",
"lists": "Lists", "lists": "Lists",
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"search": "Type to search for a list…", "search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list", "searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists", "shared": "Shared Lists",
@ -278,6 +270,14 @@
"delete": "Delete" "delete": "Delete"
} }
}, },
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Show tasks which don't have dates set", "showTasksWithoutDates": "Show tasks which don't have dates set",
@ -672,23 +672,13 @@
"updated": "Updated" "updated": "Updated"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Subscribe", "subscribe": "Subscribe",
"unsubscribe": "Unsubscribe", "unsubscribe": "Unsubscribe",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "You are now unsubscribed to this {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Attachments", "title": "Attachments",
@ -700,11 +690,7 @@
"deleteTooltip": "Delete this attachment", "deleteTooltip": "Delete this attachment",
"deleteText1": "Are you sure you want to delete the attachment {filename}?", "deleteText1": "Are you sure you want to delete the attachment {filename}?",
"copyUrl": "Copy URL", "copyUrl": "Copy URL",
"copyUrlTooltip": "Copy the url of this attachment for usage in text", "copyUrlTooltip": "Copy the url of this attachment for usage in text"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Comments", "title": "Comments",
@ -853,12 +839,6 @@
"text1": "Are you sure you want to remove this user from the team?", "text1": "Are you sure you want to remove this user from the team?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!", "text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team." "success": "The user was successfully deleted from the team."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "List Title", "title": "List Title",
"color": "Color", "color": "Color",
"lists": "Lists", "lists": "Lists",
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"search": "Type to search for a list…", "search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list", "searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists", "shared": "Shared Lists",
@ -278,6 +270,14 @@
"delete": "Delete" "delete": "Delete"
} }
}, },
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Show tasks which don't have dates set", "showTasksWithoutDates": "Show tasks which don't have dates set",
@ -672,23 +672,13 @@
"updated": "Updated" "updated": "Updated"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Subscribe", "subscribe": "Subscribe",
"unsubscribe": "Unsubscribe", "unsubscribe": "Unsubscribe",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "You are now unsubscribed to this {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Attachments", "title": "Attachments",
@ -700,11 +690,7 @@
"deleteTooltip": "Delete this attachment", "deleteTooltip": "Delete this attachment",
"deleteText1": "Are you sure you want to delete the attachment {filename}?", "deleteText1": "Are you sure you want to delete the attachment {filename}?",
"copyUrl": "Copy URL", "copyUrl": "Copy URL",
"copyUrlTooltip": "Copy the url of this attachment for usage in text", "copyUrlTooltip": "Copy the url of this attachment for usage in text"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Comments", "title": "Comments",
@ -853,12 +839,6 @@
"text1": "Are you sure you want to remove this user from the team?", "text1": "Are you sure you want to remove this user from the team?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!", "text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team." "success": "The user was successfully deleted from the team."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "List Title", "title": "List Title",
"color": "Color", "color": "Color",
"lists": "Lists", "lists": "Lists",
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"search": "Type to search for a list…", "search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list", "searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists", "shared": "Shared Lists",
@ -278,6 +270,14 @@
"delete": "Delete" "delete": "Delete"
} }
}, },
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Show tasks which don't have dates set", "showTasksWithoutDates": "Show tasks which don't have dates set",
@ -672,23 +672,13 @@
"updated": "Updated" "updated": "Updated"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Subscribe", "subscribe": "Subscribe",
"unsubscribe": "Unsubscribe", "unsubscribe": "Unsubscribe",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "You are now unsubscribed to this {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Attachments", "title": "Attachments",
@ -700,11 +690,7 @@
"deleteTooltip": "Delete this attachment", "deleteTooltip": "Delete this attachment",
"deleteText1": "Are you sure you want to delete the attachment {filename}?", "deleteText1": "Are you sure you want to delete the attachment {filename}?",
"copyUrl": "Copy URL", "copyUrl": "Copy URL",
"copyUrlTooltip": "Copy the url of this attachment for usage in text", "copyUrlTooltip": "Copy the url of this attachment for usage in text"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Comments", "title": "Comments",
@ -853,12 +839,6 @@
"text1": "Are you sure you want to remove this user from the team?", "text1": "Are you sure you want to remove this user from the team?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!", "text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team." "success": "The user was successfully deleted from the team."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "Tên Danh sách", "title": "Tên Danh sách",
"color": "Màu sắc", "color": "Màu sắc",
"lists": "Danh sách", "lists": "Danh sách",
"list": {
"title": "Danh sách",
"add": "Thêm",
"addPlaceholder": "Thêm việc cần làm…",
"empty": "Danh sách này đang trống trơn.",
"newTaskCta": "Thêm một công việc mới.",
"editTask": "Chỉnh sửa Công việc"
},
"search": "Gõ để tìm kiếm danh sách…", "search": "Gõ để tìm kiếm danh sách…",
"searchSelect": "Nhấp hoặc nhấn enter để chọn danh sách này", "searchSelect": "Nhấp hoặc nhấn enter để chọn danh sách này",
"shared": "Đang tham gia", "shared": "Đang tham gia",
@ -278,6 +270,14 @@
"delete": "Xóa" "delete": "Xóa"
} }
}, },
"list": {
"title": "Danh sách",
"add": "Thêm",
"addPlaceholder": "Thêm việc cần làm…",
"empty": "Danh sách này đang trống trơn.",
"newTaskCta": "Thêm một công việc mới.",
"editTask": "Chỉnh sửa Công việc"
},
"gantt": { "gantt": {
"title": "Biểu đồ Gantt", "title": "Biểu đồ Gantt",
"showTasksWithoutDates": "Hiển thị các nhiệm vụ không cài đặt ngày", "showTasksWithoutDates": "Hiển thị các nhiệm vụ không cài đặt ngày",
@ -672,23 +672,13 @@
"updated": "Đã cập nhật" "updated": "Đã cập nhật"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "Bạn không thể hủy theo dõi ở đây vì bạn đã theo dõi {entity} này thông qua {parent} của nó.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "Bạn đang theo dõi {entity} này và sẽ nhận được thông báo về các thay đổi.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "Bạn chưa theo dõi {entity} này và sẽ không nhận được thông báo về các thay đổi.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Theo dõi", "subscribe": "Theo dõi",
"unsubscribe": "Bỏ theo dõi", "unsubscribe": "Bỏ theo dõi",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "Bạn hiện đã theo dõi {entity} này",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "Bạn đã bỏ theo dõi {entity} này"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Tệp đính kèm", "title": "Tệp đính kèm",
@ -700,11 +690,7 @@
"deleteTooltip": "Xóa tệp đính kèm này", "deleteTooltip": "Xóa tệp đính kèm này",
"deleteText1": "Bạn có chắc chắn muốn xóa tệp đính kèm {filename} không?", "deleteText1": "Bạn có chắc chắn muốn xóa tệp đính kèm {filename} không?",
"copyUrl": "Sao chép URL", "copyUrl": "Sao chép URL",
"copyUrlTooltip": "Sao chép url của tệp đính kèm này để sử dụng trong văn bản", "copyUrlTooltip": "Sao chép url của tệp đính kèm này để sử dụng trong văn bản"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Bình luận", "title": "Bình luận",
@ -853,12 +839,6 @@
"text1": "Bạn có chắc muốn đưa thành viên này ra khỏi Team không?", "text1": "Bạn có chắc muốn đưa thành viên này ra khỏi Team không?",
"text2": "Họ sẽ mất quyền truy cập vào tất cả danh sách và góc làm việc mà Team này có quyền truy cập. Điều đó KHÔNG THỂ HOÀN TÁC!", "text2": "Họ sẽ mất quyền truy cập vào tất cả danh sách và góc làm việc mà Team này có quyền truy cập. Điều đó KHÔNG THỂ HOÀN TÁC!",
"success": "Thành viên đã rời khỏi Team." "success": "Thành viên đã rời khỏi Team."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -169,14 +169,6 @@
"title": "List Title", "title": "List Title",
"color": "Color", "color": "Color",
"lists": "Lists", "lists": "Lists",
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"search": "Type to search for a list…", "search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list", "searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists", "shared": "Shared Lists",
@ -278,6 +270,14 @@
"delete": "Delete" "delete": "Delete"
} }
}, },
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": { "gantt": {
"title": "Gantt", "title": "Gantt",
"showTasksWithoutDates": "Show tasks which don't have dates set", "showTasksWithoutDates": "Show tasks which don't have dates set",
@ -672,23 +672,13 @@
"updated": "Updated" "updated": "Updated"
}, },
"subscription": { "subscription": {
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.", "subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.", "subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.", "notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribedNamespace": "You are currently subscribed to this namespace and will receive notifications for changes.",
"notSubscribedNamespace": "You are not subscribed to this namespace and won't receive notifications for changes.",
"subscribedList": "You are currently subscribed to this list and will receive notifications for changes.",
"notSubscribedList": "You are not subscribed to this list and won't receive notifications for changes.",
"subscribedTask": "You are currently subscribed to this task and will receive notifications for changes.",
"notSubscribedTask": "You are not subscribed to this task and won't receive notifications for changes.",
"subscribe": "Subscribe", "subscribe": "Subscribe",
"unsubscribe": "Unsubscribe", "unsubscribe": "Unsubscribe",
"subscribeSuccessNamespace": "You are now subscribed to this namespace", "subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace", "unsubscribeSuccess": "You are now unsubscribed to this {entity}"
"subscribeSuccessList": "You are now subscribed to this list",
"unsubscribeSuccessList": "You are now unsubscribed to this list",
"subscribeSuccessTask": "You are now subscribed to this task",
"unsubscribeSuccessTask": "You are now unsubscribed to this task"
}, },
"attachment": { "attachment": {
"title": "Attachments", "title": "Attachments",
@ -700,11 +690,7 @@
"deleteTooltip": "Delete this attachment", "deleteTooltip": "Delete this attachment",
"deleteText1": "Are you sure you want to delete the attachment {filename}?", "deleteText1": "Are you sure you want to delete the attachment {filename}?",
"copyUrl": "Copy URL", "copyUrl": "Copy URL",
"copyUrlTooltip": "Copy the url of this attachment for usage in text", "copyUrlTooltip": "Copy the url of this attachment for usage in text"
"setAsCover": "Make cover",
"unsetAsCover": "Remove cover",
"successfullyChangedCoverImage": "The cover image was successfully changed.",
"usedAsCover": "Cover image"
}, },
"comment": { "comment": {
"title": "Comments", "title": "Comments",
@ -853,12 +839,6 @@
"text1": "Are you sure you want to remove this user from the team?", "text1": "Are you sure you want to remove this user from the team?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!", "text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team." "success": "The user was successfully deleted from the team."
},
"leave": {
"title": "Leave team",
"text1": "Are you sure you want to leave this team?",
"text2": "You will loose access to all lists and namespaces this team has access to. If you change your mind you'll need a team admin to add you again.",
"success": "You have successfully left the team."
} }
}, },
"attributes": { "attributes": {

View file

@ -1,4 +1,4 @@
import type {Priority} from '@/constants/priorities' import type { Priority } from '@/constants/priorities'
import type {IAbstract} from './IAbstract' import type {IAbstract} from './IAbstract'
import type {IUser} from './IUser' import type {IUser} from './IUser'
@ -11,7 +11,6 @@ import type {IBucket} from './IBucket'
import type {IRelationKind} from '@/types/IRelationKind' import type {IRelationKind} from '@/types/IRelationKind'
import type {IRepeatAfter} from '@/types/IRepeatAfter' import type {IRepeatAfter} from '@/types/IRepeatAfter'
import type {IRepeatMode} from '@/types/IRepeatMode' import type {IRepeatMode} from '@/types/IRepeatMode'
export interface ITask extends IAbstract { export interface ITask extends IAbstract {
id: number id: number
title: string title: string
@ -32,9 +31,8 @@ export interface ITask extends IAbstract {
parentTaskId: ITask['id'] parentTaskId: ITask['id']
hexColor: string hexColor: string
percentDone: number percentDone: number
relatedTasks: Partial<Record<IRelationKind, ITask[]>> relatedTasks: Partial<Record<IRelationKind, ITask[]>>,
attachments: IAttachment[] attachments: IAttachment[]
coverImageAttachmentId: IAttachment['id']
identifier: string identifier: string
index: number index: number
isFavorite: boolean isFavorite: boolean

View file

@ -5,8 +5,6 @@ import type { IUser } from '@/modelTypes/IUser'
import type { IFile } from '@/modelTypes/IFile' import type { IFile } from '@/modelTypes/IFile'
import type { IAttachment } from '@/modelTypes/IAttachment' import type { IAttachment } from '@/modelTypes/IAttachment'
export const SUPPORTED_IMAGE_SUFFIX = ['.jpg', '.png', '.bmp', '.gif']
export default class AttachmentModel extends AbstractModel<IAttachment> implements IAttachment { export default class AttachmentModel extends AbstractModel<IAttachment> implements IAttachment {
id = 0 id = 0
taskId = 0 taskId = 0

View file

@ -488,8 +488,4 @@ export function getAuthForRoute(route: RouteLocation) {
} }
} }
router.beforeEach((to) => {
return getAuthForRoute(to)
})
export default router export default router

View file

@ -22,6 +22,7 @@ export default class AbstractMigrationFileService extends AbstractService {
} }
migrate(file: IFile) { migrate(file: IFile) {
console.log(file)
return this.uploadFile( return this.uploadFile(
this.paths.create, this.paths.create,
file, file,

View file

@ -75,11 +75,12 @@ export const useKanbanStore = defineStore('kanban', {
getTaskById(state) { getTaskById(state) {
return (id: ITask['id']) => { return (id: ITask['id']) => {
const { bucketIndex, taskIndex } = getTaskIndicesById(state, id) const { bucketIndex, taskIndex } = getTaskIndicesById(state, id)
return { return {
bucketIndex, bucketIndex,
taskIndex, taskIndex,
task: bucketIndex !== null && taskIndex !== null && state.buckets[bucketIndex]?.tasks?.[taskIndex] || null, task: bucketIndex && taskIndex && state.buckets[bucketIndex]?.tasks?.[taskIndex] || null,
} }
} }
}, },

View file

@ -43,7 +43,7 @@ export const useNamespaceStore = defineStore('namespace', {
getNamespaceById: state => (namespaceId: INamespace['id']) => { getNamespaceById: state => (namespaceId: INamespace['id']) => {
return state.namespaces.find(({id}) => id == namespaceId) || null return state.namespaces.find(({id}) => id == namespaceId) || null
}, },
searchNamespace() { searchNamespace() {
return (query: string) => ( return (query: string) => (
search(query) search(query)
@ -64,13 +64,6 @@ export const useNamespaceStore = defineStore('namespace', {
this.namespaces = namespaces this.namespaces = namespaces
namespaces.forEach(n => { namespaces.forEach(n => {
add(n) add(n)
// Check for each list in that namespace if it has a subscription and set it if not
n.lists.forEach(l => {
if (l.subscription === null || l.subscription.entity !== 'list') {
l.subscription = n.subscription
}
})
}) })
}, },
@ -210,5 +203,5 @@ export const useNamespaceStore = defineStore('namespace', {
// support hot reloading // support hot reloading
if (import.meta.hot) { if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useNamespaceStore, import.meta.hot)) import.meta.hot.accept(acceptHMRUpdate(useNamespaceStore, import.meta.hot))
} }

View file

@ -171,39 +171,32 @@ export const useTaskStore = defineStore('task', {
user: IUser, user: IUser,
taskId: ITask['id'] taskId: ITask['id']
}) { }) {
const cancel = setModuleLoading(this) const kanbanStore = useKanbanStore()
const taskAssigneeService = new TaskAssigneeService()
try { const r = await taskAssigneeService.create(new TaskAssigneeModel({
const kanbanStore = useKanbanStore() userId: user.id,
const taskAssigneeService = new TaskAssigneeService() taskId: taskId,
const r = await taskAssigneeService.create(new TaskAssigneeModel({ }))
userId: user.id, const t = kanbanStore.getTaskById(taskId)
taskId: taskId, if (t.task === null) {
})) // Don't try further adding a label if the task is not in kanban
const t = kanbanStore.getTaskById(taskId) // Usually this means the kanban board hasn't been accessed until now.
if (t.task === null) { // Vuex seems to have its difficulties with that, so we just log the error and fail silently.
// Don't try further adding a label if the task is not in kanban console.debug('Could not add assignee to task in kanban, task not found', t)
// Usually this means the kanban board hasn't been accessed until now.
// Vuex seems to have its difficulties with that, so we just log the error and fail silently.
console.debug('Could not add assignee to task in kanban, task not found', t)
return r
}
kanbanStore.setTaskInBucketByIndex({
...t,
task: {
...t.task,
assignees: [
...t.task.assignees,
user,
],
},
})
return r return r
} finally {
cancel()
} }
kanbanStore.setTaskInBucketByIndex({
...t,
task: {
...t.task,
assignees: [
...t.task.assignees,
user,
],
},
})
return r
}, },
async removeAssignee({ async removeAssignee({
@ -259,7 +252,7 @@ export const useTaskStore = defineStore('task', {
// Don't try further adding a label if the task is not in kanban // Don't try further adding a label if the task is not in kanban
// Usually this means the kanban board hasn't been accessed until now. // Usually this means the kanban board hasn't been accessed until now.
// Vuex seems to have its difficulties with that, so we just log the error and fail silently. // Vuex seems to have its difficulties with that, so we just log the error and fail silently.
console.debug('Could not add label to task in kanban, task not found', {taskId, t}) console.debug('Could not add label to task in kanban, task not found', t)
return r return r
} }
@ -416,13 +409,6 @@ export const useTaskStore = defineStore('task', {
cancel() cancel()
} }
}, },
async setCoverImage(task: ITask, attachment: IAttachment | null) {
return this.update({
...task,
coverImageAttachmentId: attachment ? attachment.id : 0,
})
},
}, },
}) })

View file

@ -1,6 +1,5 @@
// FIXME: should be a component <FilterContainer> // FIXME: should be a component <FilterContainer>
// used in // used in
// - gantt-component.vue
// - Kanban.vue // - Kanban.vue
// - List.vue // - List.vue
// - Table.vue // - Table.vue

View file

@ -46,7 +46,6 @@
} }
// FIXME: is only used where <edit-task> is used aswell: // FIXME: is only used where <edit-task> is used aswell:
// - gantt-component.vue
// - List.vue // - List.vue
// -> Move the <card> wrapper including this class definition inside <edit-task> // -> Move the <card> wrapper including this class definition inside <edit-task>
.is-max-width-desktop .tasks .task { .is-max-width-desktop .tasks .task {

View file

@ -0,0 +1 @@
declare module 'vue-flatpickr-component';

View file

@ -1,47 +1,23 @@
<template> <template>
<ListWrapper class="list-gantt" :list-id="props.listId" viewName="gantt"> <ListWrapper class="list-gantt" :list-id="props.listId" viewName="gantt">
<template #header> <template #header>
<card class="gantt-options"> <card>
<fancycheckbox class="is-block" v-model="showTaskswithoutDates"> <div class="gantt-options">
{{ $t('list.gantt.showTasksWithoutDates') }}
</fancycheckbox>
<div class="range-picker">
<div class="field"> <div class="field">
<label class="label" for="dayWidth">{{ $t('list.gantt.size') }}</label> <label class="label" for="range">{{ $t('list.gantt.range') }}</label>
<div class="control">
<div class="select">
<select id="dayWidth" v-model.number="dayWidth">
<option value="35">{{ $t('list.gantt.default') }}</option>
<option value="10">{{ $t('list.gantt.month') }}</option>
<option value="80">{{ $t('list.gantt.day') }}</option>
</select>
</div>
</div>
</div>
<div class="field">
<label class="label" for="fromDate">{{ $t('list.gantt.from') }}</label>
<div class="control"> <div class="control">
<flat-pickr <flat-pickr
:config="flatPickerConfig" :config="flatPickerConfig"
class="input" class="input"
id="fromDate" id="range"
:placeholder="$t('list.gantt.from')" :placeholder="$t('list.gantt.range')"
v-model="dateFrom" v-model="range"
/>
</div>
</div>
<div class="field">
<label class="label" for="toDate">{{ $t('list.gantt.to') }}</label>
<div class="control">
<flat-pickr
:config="flatPickerConfig"
class="input"
id="toDate"
:placeholder="$t('list.gantt.to')"
v-model="dateTo"
/> />
</div> </div>
</div> </div>
<fancycheckbox class="is-block" v-model="showTasksWithoutDates">
{{ $t('list.gantt.showTasksWithoutDates') }}
</fancycheckbox>
</div> </div>
</card> </card>
</template> </template>
@ -53,9 +29,8 @@
<gantt-chart <gantt-chart
:date-from="dateFrom" :date-from="dateFrom"
:date-to="dateTo" :date-to="dateTo"
:day-width="dayWidth"
:list-id="props.listId" :list-id="props.listId"
:show-taskswithout-dates="showTaskswithoutDates" :show-tasks-without-dates="showTasksWithoutDates"
/> />
</card> </card>
@ -72,8 +47,9 @@ import {useI18n} from 'vue-i18n'
import {useAuthStore} from '@/stores/auth' import {useAuthStore} from '@/stores/auth'
import ListWrapper from './ListWrapper.vue' import ListWrapper from './ListWrapper.vue'
import GanttChart from '@/components/tasks/gantt-component.vue' import GanttChart from '@/components/tasks/gantt-chart.vue'
import Fancycheckbox from '@/components/input/fancycheckbox.vue' import Fancycheckbox from '@/components/input/fancycheckbox.vue'
import {format} from 'date-fns'
const props = defineProps({ const props = defineProps({
listId: { listId: {
@ -82,14 +58,16 @@ const props = defineProps({
}, },
}) })
const DEFAULT_DAY_COUNT = 35 const showTasksWithoutDates = ref(false)
const showTaskswithoutDates = ref(false) const now = new Date()
const dayWidth = ref(DEFAULT_DAY_COUNT) const defaultFrom = format(new Date((new Date()).setDate(now.getDate() - 15)), 'yyyy-LL-dd')
const defaultTo = format(new Date((new Date()).setDate(now.getDate() + 55)), 'yyyy-LL-dd')
const range = ref(`${defaultFrom} to ${defaultTo}`)
const now = ref(new Date()) // TODO: only update once both dates are available (maybe use a watcher + refs instead?)
const dateFrom = ref(new Date((new Date()).setDate(now.value.getDate() - 15))) const dateFrom = computed(() => range.value?.split(' to ')[0] ?? defaultFrom)
const dateTo = ref(new Date((new Date()).setDate(now.value.getDate() + 30))) const dateTo = computed(() => range.value?.split(' to ')[1] ?? defaultTo)
const {t} = useI18n({useScope: 'global'}) const {t} = useI18n({useScope: 'global'})
const authStore = useAuthStore() const authStore = useAuthStore()
@ -98,6 +76,7 @@ const flatPickerConfig = computed(() => ({
altInput: true, altInput: true,
dateFormat: 'Y-m-d', dateFormat: 'Y-m-d',
enableTime: false, enableTime: false,
mode: 'range',
locale: { locale: {
firstDayOfWeek: authStore.settings.weekStart, firstDayOfWeek: authStore.settings.weekStart,
}, },
@ -119,46 +98,35 @@ const flatPickerConfig = computed(() => ({
flex-direction: column; flex-direction: column;
} }
.range-picker { .field {
display: flex; margin-bottom: 0;
margin-bottom: 1rem; width: 33%;
width: 50%;
@media screen and (max-width: $tablet) { &:not(:last-child) {
flex-direction: column; padding-right: .5rem;
width: 100%;
} }
.field { @media screen and (max-width: $tablet) {
margin-bottom: 0; width: 100%;
width: 33%; max-width: 100%;
margin-top: .5rem;
padding-right: 0 !important;
}
&:not(:last-child) { &, .input {
padding-right: .5rem; font-size: .8rem;
} }
@media screen and (max-width: $tablet) { .select, .select select {
width: 100%; height: auto;
max-width: 100%; width: 100%;
margin-top: .5rem; font-size: .8rem;
padding-right: 0 !important; }
}
&, .input {
font-size: .8rem;
}
.select, .select select {
height: auto;
width: 100%;
font-size: .8rem;
}
.label { .label {
font-size: .9rem; font-size: .9rem;
padding-left: .4rem; padding-left: .4rem;
}
} }
} }
} }

View file

@ -1 +0,0 @@
<svg viewBox="0 0 88 88" xmlns="http://www.w3.org/2000/svg" class="logo_1OKcB"><g fill="none" fill-rule="evenodd"><rect></rect><path d="M30.755 33.292l-7.34 8.935L40.798 56.48a5.782 5.782 0 008.182-.854l31.179-38.93-9.026-7.228L43.614 43.83l-12.86-10.538z" fill="#FFB000"></path><path d="M44 78.1C25.197 78.1 9.9 62.803 9.9 44S25.197 9.9 44 9.9V0C19.738 0 0 19.738 0 44s19.738 44 44 44 44-19.738 44-44h-9.9c0 18.803-15.297 34.1-34.1 34.1" fill="#4772FA"></path></g></svg>

Before

Width:  |  Height:  |  Size: 471 B

View file

@ -3,7 +3,6 @@ import todoistIcon from './icons/todoist.svg?url'
import trelloIcon from './icons/trello.svg?url' import trelloIcon from './icons/trello.svg?url'
import microsoftTodoIcon from './icons/microsoft-todo.svg?url' import microsoftTodoIcon from './icons/microsoft-todo.svg?url'
import vikunjaFileIcon from './icons/vikunja-file.png?url' import vikunjaFileIcon from './icons/vikunja-file.png?url'
import tickTickIcon from './icons/ticktick.svg?url'
export interface Migrator { export interface Migrator {
id: string id: string
@ -43,10 +42,4 @@ export const MIGRATORS: IMigratorRecord = {
icon: vikunjaFileIcon, icon: vikunjaFileIcon,
isFileMigrator: true, isFileMigrator: true,
}, },
ticktick: {
id: 'ticktick',
name: 'TickTick',
icon: tickTickIcon as string,
isFileMigrator: true,
},
} }

View file

@ -39,7 +39,7 @@
</div> </div>
<priority-select <priority-select
:disabled="!canWrite" :disabled="!canWrite"
@update:model-value="setPriority" @update:model-value="saveTask"
ref="priority" ref="priority"
v-model="task.priority"/> v-model="task.priority"/>
</div> </div>
@ -79,7 +79,7 @@
</div> </div>
<percent-done-select <percent-done-select
:disabled="!canWrite" :disabled="!canWrite"
@update:model-value="setPercentDone" @update:model-value="saveTask"
ref="percentDone" ref="percentDone"
v-model="task.percentDone"/> v-model="task.percentDone"/>
</div> </div>
@ -218,8 +218,7 @@
<div class="content attachments" v-if="activeFields.attachments || hasAttachments"> <div class="content attachments" v-if="activeFields.attachments || hasAttachments">
<attachments <attachments
:edit-enabled="canWrite" :edit-enabled="canWrite"
:task="task" :task-id="taskId"
@task-changed="({coverImageAttachmentId}) => task.coverImageAttachmentId = coverImageAttachmentId"
ref="attachments" ref="attachments"
/> />
</div> </div>
@ -443,7 +442,7 @@ import TaskModel, {TASK_DEFAULT_COLOR} from '@/models/task'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
import type {IList} from '@/modelTypes/IList' import type {IList} from '@/modelTypes/IList'
import {PRIORITIES, type Priority} from '@/constants/priorities' import {PRIORITIES} from '@/constants/priorities'
import {RIGHTS} from '@/constants/rights' import {RIGHTS} from '@/constants/rights'
import BaseButton from '@/components/base/BaseButton.vue' import BaseButton from '@/components/base/BaseButton.vue'
@ -501,7 +500,7 @@ const attachmentStore = useAttachmentStore()
const taskStore = useTaskStore() const taskStore = useTaskStore()
const kanbanStore = useKanbanStore() const kanbanStore = useKanbanStore()
const task = reactive<ITask>(new TaskModel()) const task = reactive(new TaskModel())
useTitle(toRef(task, 'title')) useTitle(toRef(task, 'title'))
// We doubled the task color property here because verte does not have a real change property, leading // We doubled the task color property here because verte does not have a real change property, leading
@ -676,19 +675,21 @@ function setFieldActive(fieldName: keyof typeof activeFields) {
} }
async function saveTask(args?: { async function saveTask(args?: {
task: ITask, task: ITask,
undoCallback?: () => void, showNotification?: boolean,
}) { undoCallback?: () => void,
const { }) {
task: currentTask, const {
undoCallback, task: currentTask,
} = { showNotification,
...{ undoCallback,
task: cloneDeep(task), } = {
}, ...{
...args, task: cloneDeep(task),
} showNotification: true,
},
...args,
}
if (!canWrite.value) { if (!canWrite.value) {
return return
} }
@ -709,6 +710,10 @@ async function saveTask(args?: {
Object.assign(task, newTask) Object.assign(task, newTask)
setActiveFields() setActiveFields()
if (!showNotification) {
return
}
let actions = [] let actions = []
if (undoCallback !== null) { if (undoCallback !== null) {
actions = [{ actions = [{
@ -754,28 +759,6 @@ async function toggleFavorite() {
Object.assign(task, newTask) Object.assign(task, newTask)
await namespaceStore.loadNamespacesIfFavoritesDontExist() await namespaceStore.loadNamespacesIfFavoritesDontExist()
} }
async function setPriority(priority: Priority) {
const newTask: ITask = {
...task,
priority,
}
return saveTask({
task: newTask,
})
}
async function setPercentDone(percentDone: number) {
const newTask: ITask = {
...task,
percentDone,
}
return saveTask({
task: newTask,
})
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -127,24 +127,6 @@
</table> </table>
</card> </card>
<x-button class="is-fullwidth is-danger" @click="showLeaveModal = true">
{{ $t('team.edit.leave.title') }}
</x-button>
<!-- Leave team modal -->
<modal
v-if="showLeaveModal"
@close="showLeaveModal = false"
@submit="leave()"
>
<template #header><span>{{ $t('team.edit.leave.title') }}</span></template>
<template #text>
<p>{{ $t('team.edit.leave.text1') }}<br/>
{{ $t('team.edit.leave.text2') }}</p>
</template>
</modal>
<!-- Team delete modal --> <!-- Team delete modal -->
<transition name="modal"> <transition name="modal">
<modal <modal
@ -220,14 +202,13 @@ const teamMemberService = ref<TeamMemberService>(new TeamMemberService())
const userService = ref<UserService>(new UserService()) const userService = ref<UserService>(new UserService())
const team = ref<ITeam>() const team = ref<ITeam>()
const teamId = computed(() => Number(route.params.id)) const teamId = computed(() => route.params.id)
const memberToDelete = ref<ITeamMember>() const memberToDelete = ref<ITeamMember>()
const newMember = ref<IUser>() const newMember = ref<IUser>()
const foundUsers = ref<IUser[]>() const foundUsers = ref<IUser[]>()
const showDeleteModal = ref(false) const showDeleteModal = ref(false)
const showUserDeleteModal = ref(false) const showUserDeleteModal = ref(false)
const showLeaveModal = ref(false)
const showError = ref(false) const showError = ref(false)
const title = ref('') const title = ref('')
@ -306,19 +287,6 @@ async function findUser(query: string) {
const users = await userService.value.getAll({}, {s: query}) const users = await userService.value.getAll({}, {s: query})
foundUsers.value = users.filter((u: IUser) => u.id !== userInfo.value.id) foundUsers.value = users.filter((u: IUser) => u.id !== userInfo.value.id)
} }
async function leave() {
try {
await teamMemberService.value.delete({
teamId: teamId.value,
username: userInfo.value.username,
})
success({message: t('team.edit.leave.success')})
await router.push({name: 'home'})
} finally {
showUserDeleteModal.value = false
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -3,9 +3,6 @@
<message variant="danger" v-if="errorMessage"> <message variant="danger" v-if="errorMessage">
{{ errorMessage }} {{ errorMessage }}
</message> </message>
<message variant="danger" v-if="errorMessageFromQuery" class="mt-2">
{{ errorMessageFromQuery }}
</message>
<message v-if="loading"> <message v-if="loading">
{{ $t('user.auth.authenticating') }} {{ $t('user.auth.authenticating') }}
</message> </message>
@ -36,7 +33,6 @@ const authStore = useAuthStore()
const loading = computed(() => authStore.isLoading) const loading = computed(() => authStore.isLoading)
const errorMessage = ref('') const errorMessage = ref('')
const errorMessageFromQuery = computed(() => route.query.error)
async function authenticateWithCode() { async function authenticateWithCode() {
// This component gets mounted twice: The first time when the actual auth request hits the frontend, // This component gets mounted twice: The first time when the actual auth request hits the frontend,

Binary file not shown.