Compare commits
66 commits
feature/ga
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
90ecea74c7 | ||
|
9bde803ef9 | ||
|
82ac8aeb6c | ||
|
b73d285c88 | ||
|
dd252273d7 | ||
|
47cc400417 | ||
|
5826e2ebcc | ||
|
e5b60b7cd3 | ||
|
a47167197d | ||
|
e811695cb0 | ||
|
87ad58b4b7 | ||
|
3e8e5bb554 | ||
|
322724c2a6 | ||
|
37956b5933 | ||
|
be64831035 | ||
|
8458e8f687 | ||
|
c6d6da3171 | ||
|
1af4f7811a | ||
|
35155034e0 | ||
|
feeaca2c02 | ||
|
4784e3a22f | ||
|
ba28617464 | ||
|
766f2b7461 | ||
|
6c1cd9f911 | ||
|
48cb3defc4 | ||
|
59cb0c82f9 | ||
|
6d587fad6e | ||
|
458df80443 | ||
|
96a8308d16 | ||
|
0a29197715 | ||
|
172d353df7 | ||
|
a895bde661 | ||
|
4ebe17f4f3 | ||
|
59b0f12424 | ||
|
44549bb441 | ||
|
c8aa3db205 | ||
|
74a9b9ab1b | ||
|
5b733ffa8a | ||
|
b62ec79939 | ||
|
17b31e0b95 | ||
|
bc1e366750 | ||
|
d4c179c862 | ||
|
d77addd266 | ||
|
5be75f3c54 | ||
|
8e905de41b | ||
|
07d38a4aa3 | ||
|
ab75e3ab50 | ||
|
b806a01e95 | ||
|
820db3e96d | ||
|
f405b2105b | ||
|
3af20b6220 | ||
|
38fc157f24 | ||
|
01f648c20c | ||
|
1be516a905 | ||
|
fd71de4b5d | ||
|
31e39aa6c8 | ||
|
f01107fd73 | ||
|
a7731370a0 | ||
|
84a1abf347 | ||
|
ee3965eae9 | ||
|
fad72e091b | ||
|
eb80bfa00d | ||
|
43258ab74e | ||
|
877e425055 | ||
|
054d70cbe5 | ||
|
3d88fdaadd |
55 changed files with 3760 additions and 2713 deletions
|
@ -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('.g-gantt-rows-container')
|
cy.get('.gantt-chart .tasks')
|
||||||
.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('.g-timeunits-container')
|
cy.get('.gantt-chart .months')
|
||||||
.should('contain', format(now, 'MMMM'))
|
.should('contain', format(now, 'MMMM'))
|
||||||
.should('contain', format(nextMonth, 'MMMM'))
|
.should('contain', format(nextMonth, 'MMMM'))
|
||||||
})
|
})
|
||||||
|
@ -38,13 +38,14 @@ describe('List View Gantt', () => {
|
||||||
})
|
})
|
||||||
cy.visit('/lists/1/gantt')
|
cy.visit('/lists/1/gantt')
|
||||||
|
|
||||||
cy.get('.g-gantt-rows-container')
|
cy.get('.gantt-chart .tasks')
|
||||||
.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', () => {
|
||||||
const tasks = TaskFactory.create(1, {
|
TaskFactory.create(1, {
|
||||||
start_date: null,
|
start_date: null,
|
||||||
end_date: null,
|
end_date: null,
|
||||||
})
|
})
|
||||||
|
@ -54,15 +55,13 @@ 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('.g-gantt-rows-container')
|
cy.get('.gantt-chart .tasks')
|
||||||
.should('not.be.empty')
|
.should('not.be.empty')
|
||||||
.should('contain', tasks[0].title)
|
cy.get('.gantt-chart .tasks .task.nodate')
|
||||||
|
.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),
|
||||||
|
@ -70,11 +69,10 @@ describe('List View Gantt', () => {
|
||||||
})
|
})
|
||||||
cy.visit('/lists/1/gantt')
|
cy.visit('/lists/1/gantt')
|
||||||
|
|
||||||
cy.get('.g-gantt-rows-container .g-gantt-row .g-gantt-row-bars-container div .g-gantt-bar')
|
cy.get('.gantt-chart .tasks .task')
|
||||||
.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')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
|
@ -12,15 +12,51 @@ 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()
|
||||||
})
|
})
|
||||||
|
@ -80,6 +116,7 @@ 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', () => {
|
||||||
|
@ -344,21 +381,31 @@ describe('Task', () => {
|
||||||
|
|
||||||
cy.visit(`/tasks/${tasks[0].id}`)
|
cy.visit(`/tasks/${tasks[0].id}`)
|
||||||
|
|
||||||
cy.get('.task-view .action-buttons .button')
|
addLabelToTaskAndVerify(labels[0].title)
|
||||||
.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')
|
|
||||||
.type(labels[0].title)
|
addLabelToTaskAndVerify(labels[0].title)
|
||||||
cy.get('.task-view .details.labels-list .multiselect .search-results')
|
|
||||||
.children()
|
cy.get('.modal-content .close')
|
||||||
.first()
|
|
||||||
.click()
|
.click()
|
||||||
|
|
||||||
cy.get('.global-notification', { timeout: 4000 })
|
cy.get('.bucket .task')
|
||||||
.should('contain', 'Success')
|
.should('contain.text', labels[0].title)
|
||||||
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', () => {
|
||||||
|
@ -417,5 +464,87 @@ 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')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
17
cypress/factories/task_attachments.ts
Normal file
17
cypress/factories/task_attachments.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
package.json
41
package.json
|
@ -23,10 +23,9 @@
|
||||||
"@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.14.1",
|
"@sentry/tracing": "7.15.0",
|
||||||
"@sentry/vue": "7.14.1",
|
"@sentry/vue": "7.15.0",
|
||||||
"@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",
|
||||||
|
@ -48,8 +47,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.6",
|
"minimist": "1.2.7",
|
||||||
"pinia": "2.0.22",
|
"pinia": "2.0.23",
|
||||||
"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",
|
||||||
|
@ -57,7 +56,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.6",
|
"vue-flatpickr-component": "9.0.8",
|
||||||
"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",
|
||||||
|
@ -65,7 +64,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@4tw/cypress-drag-drop": "2.2.1",
|
"@4tw/cypress-drag-drop": "2.2.1",
|
||||||
"@cypress/vite-dev-server": "3.2.0",
|
"@cypress/vite-dev-server": "3.3.1",
|
||||||
"@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",
|
||||||
|
@ -73,9 +72,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.64",
|
"@types/node": "16.11.65",
|
||||||
"@typescript-eslint/eslint-plugin": "5.39.0",
|
"@typescript-eslint/eslint-plugin": "5.40.0",
|
||||||
"@typescript-eslint/parser": "5.39.0",
|
"@typescript-eslint/parser": "5.40.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",
|
||||||
|
@ -83,25 +82,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.30001414",
|
"caniuse-lite": "1.0.30001418",
|
||||||
"cypress": "10.9.0",
|
"cypress": "10.10.0",
|
||||||
"esbuild": "0.15.10",
|
"esbuild": "0.15.10",
|
||||||
"eslint": "8.24.0",
|
"eslint": "8.25.0",
|
||||||
"eslint-plugin-vue": "9.6.0",
|
"eslint-plugin-vue": "9.6.0",
|
||||||
"express": "4.18.1",
|
"express": "4.18.2",
|
||||||
"happy-dom": "6.0.4",
|
"happy-dom": "7.4.0",
|
||||||
"netlify-cli": "12.0.2",
|
"netlify-cli": "12.0.7",
|
||||||
"postcss": "8.4.17",
|
"postcss": "8.4.17",
|
||||||
"postcss-preset-env": "7.8.2",
|
"postcss-preset-env": "7.8.2",
|
||||||
"rollup": "2.79.1",
|
"rollup": "3.0.0",
|
||||||
"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.4",
|
"vite": "3.1.7",
|
||||||
"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.23.4",
|
"vitest": "0.24.1",
|
||||||
"vue-tsc": "0.40.13",
|
"vue-tsc": "1.0.5",
|
||||||
"wait-on": "6.0.1",
|
"wait-on": "6.0.1",
|
||||||
"workbox-cli": "6.5.4"
|
"workbox-cli": "6.5.4"
|
||||||
},
|
},
|
||||||
|
@ -111,5 +110,5 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"packageManager": "pnpm@7.13.1"
|
"packageManager": "pnpm@7.13.4"
|
||||||
}
|
}
|
||||||
|
|
3570
pnpm-lock.yaml
3570
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -6,7 +6,7 @@
|
||||||
],
|
],
|
||||||
"packageRules": [
|
"packageRules": [
|
||||||
{
|
{
|
||||||
"matchPackageNames": ["netlify-cli"],
|
"matchPackageNames": ["netlify-cli", "happy-dom"],
|
||||||
"extends": ["schedule:weekly"]
|
"extends": ["schedule:weekly"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -55,13 +55,13 @@
|
||||||
>
|
>
|
||||||
{{ $t('menu.archive') }}
|
{{ $t('menu.archive') }}
|
||||||
</dropdown-item>
|
</dropdown-item>
|
||||||
<task-subscription
|
<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="sub => subscription = sub"
|
@update:model-value="setSubscriptionInStore"
|
||||||
type="dropdown"
|
type="dropdown"
|
||||||
/>
|
/>
|
||||||
<dropdown-item
|
<dropdown-item
|
||||||
|
@ -81,10 +81,12 @@ 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 TaskSubscription from '@/components/misc/subscription.vue'
|
import Subscription 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: {
|
||||||
|
@ -93,6 +95,8 @@ 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
|
||||||
|
@ -100,4 +104,14 @@ 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>
|
||||||
|
|
|
@ -210,6 +210,7 @@ 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'
|
||||||
|
@ -391,7 +392,14 @@ export default defineComponent({
|
||||||
this.params.filter_value.push(dateTo)
|
this.params.filter_value.push(dateTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.filters[camelCase(filterName)] = {dateFrom, dateTo}
|
this.filters[camelCase(filterName)] = {
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
@ -511,12 +519,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,17 +69,38 @@ 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) {
|
||||||
return t('task.subscription.subscribedThroughParent', {
|
if (props.entity === 'list' && subscriptionEntity.value === 'namespace') {
|
||||||
entity: props.entity,
|
return t('task.subscription.subscribedListThroughParentNamespace')
|
||||||
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 ''
|
||||||
}
|
}
|
||||||
|
|
||||||
return props.modelValue !== null ?
|
switch (props.entity) {
|
||||||
t('task.subscription.subscribed', {entity: props.entity}) :
|
case 'namespace':
|
||||||
t('task.subscription.notSubscribed', {entity: props.entity})
|
return props.modelValue !== null ?
|
||||||
|
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'))
|
||||||
|
@ -105,7 +126,20 @@ 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() {
|
||||||
|
@ -115,6 +149,19 @@ 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>
|
||||||
|
|
|
@ -33,14 +33,13 @@
|
||||||
>
|
>
|
||||||
{{ $t('menu.archive') }}
|
{{ $t('menu.archive') }}
|
||||||
</dropdown-item>
|
</dropdown-item>
|
||||||
<task-subscription
|
<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="sub => subscription = sub"
|
@update:model-value="setSubscriptionInStore"
|
||||||
type="dropdown"
|
type="dropdown"
|
||||||
/>
|
/>
|
||||||
<dropdown-item
|
<dropdown-item
|
||||||
|
@ -59,9 +58,10 @@ 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 TaskSubscription from '@/components/misc/subscription.vue'
|
import Subscription 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,8 +70,20 @@ 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>
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,319 +0,0 @@
|
||||||
<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>
|
|
642
src/components/tasks/gantt-component.vue
Normal file
642
src/components/tasks/gantt-component.vue
Normal file
|
@ -0,0 +1,642 @@
|
||||||
|
<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>
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<input
|
<input
|
||||||
v-if="editEnabled"
|
v-if="editEnabled"
|
||||||
:disabled="attachmentService.loading || undefined"
|
:disabled="loading || undefined"
|
||||||
@change="uploadNewAttachment()"
|
@change="uploadNewAttachment()"
|
||||||
id="files"
|
id="files"
|
||||||
multiple
|
multiple
|
||||||
|
@ -35,7 +35,15 @@
|
||||||
:key="a.id"
|
:key="a.id"
|
||||||
@click="viewOrDownload(a)"
|
@click="viewOrDownload(a)"
|
||||||
>
|
>
|
||||||
<div class="filename">{{ a.file.name }}</div>
|
<div class="filename">
|
||||||
|
{{ 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">
|
||||||
|
@ -78,6 +86,17 @@
|
||||||
>
|
>
|
||||||
{{ $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>
|
||||||
|
@ -85,7 +104,7 @@
|
||||||
|
|
||||||
<x-button
|
<x-button
|
||||||
v-if="editEnabled"
|
v-if="editEnabled"
|
||||||
:disabled="attachmentService.loading"
|
:disabled="loading"
|
||||||
@click="filesRef?.click()"
|
@click="filesRef?.click()"
|
||||||
class="mb-4"
|
class="mb-4"
|
||||||
icon="cloud-upload-alt"
|
icon="cloud-upload-alt"
|
||||||
|
@ -118,7 +137,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/>
|
||||||
|
@ -138,13 +157,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, shallowReactive, computed, type PropType} from 'vue'
|
import {ref, shallowReactive, computed} 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'
|
||||||
|
@ -155,38 +175,44 @@ 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 props = defineProps({
|
const taskStore = useTaskStore()
|
||||||
taskId: {
|
const {t} = useI18n({useScope: 'global'})
|
||||||
type: Number as PropType<ITask['id']>,
|
|
||||||
required: true,
|
const props = withDefaults(defineProps<{
|
||||||
},
|
task: ITask,
|
||||||
initialAttachments: {
|
initialAttachments?: IAttachment[],
|
||||||
type: Array,
|
editEnabled: boolean,
|
||||||
},
|
}>(), {
|
||||||
editEnabled: {
|
editEnabled: true,
|
||||||
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
|
||||||
|
|
||||||
|
@ -198,7 +224,7 @@ function uploadNewAttachment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadFilesToTask(files: File[] | FileList) {
|
function uploadFilesToTask(files: File[] | FileList) {
|
||||||
uploadFiles(attachmentService, props.taskId, files)
|
uploadFiles(attachmentService, props.task.id, files)
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachmentToDelete = ref<AttachmentModel | null>(null)
|
const attachmentToDelete = ref<AttachmentModel | null>(null)
|
||||||
|
@ -217,16 +243,15 @@ 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_SUFFIX.some((suffix) => attachment.file.name.endsWith(suffix)) ) {
|
if (SUPPORTED_IMAGE_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)
|
||||||
|
@ -234,8 +259,15 @@ async function viewOrDownload(attachment: AttachmentModel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const copy = useCopyToClipboard()
|
const copy = useCopyToClipboard()
|
||||||
|
|
||||||
function copyUrl(attachment: IAttachment) {
|
function copyUrl(attachment: IAttachment) {
|
||||||
copy(generateAttachmentUrl(props.taskId, attachment.id))
|
copy(generateAttachmentUrl(props.task.id, 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>
|
||||||
|
|
||||||
|
@ -316,7 +348,7 @@ function copyUrl(attachment: IAttachment) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -338,7 +370,7 @@ function copyUrl(attachment: IAttachment) {
|
||||||
.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;
|
||||||
|
@ -348,7 +380,7 @@ function copyUrl(attachment: IAttachment) {
|
||||||
@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;
|
||||||
}
|
}
|
||||||
|
@ -394,5 +426,13 @@ function copyUrl(attachment: IAttachment) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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>
|
|
@ -28,7 +28,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, shallowReactive, watch, type PropType} from 'vue'
|
import {ref, shallowReactive, watch, nextTick, 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,6 +67,7 @@ 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,
|
||||||
|
@ -80,9 +81,19 @@ watch(
|
||||||
)
|
)
|
||||||
|
|
||||||
async function addAssignee(user: IUser) {
|
async function addAssignee(user: IUser) {
|
||||||
await taskStore.addAssignee({user: user, taskId: props.taskId})
|
if (isAdding) {
|
||||||
emit('update:modelValue', assignees.value)
|
return
|
||||||
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) {
|
||||||
|
@ -119,6 +130,7 @@ function clearAllFoundUsers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const multiselect = ref()
|
const multiselect = ref()
|
||||||
|
|
||||||
function focus() {
|
function focus() {
|
||||||
multiselect.value.focus()
|
multiselect.value.focus()
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,62 +11,70 @@
|
||||||
@click.ctrl="() => toggleTaskDone(task)"
|
@click.ctrl="() => toggleTaskDone(task)"
|
||||||
@click.meta="() => toggleTaskDone(task)"
|
@click.meta="() => toggleTaskDone(task)"
|
||||||
>
|
>
|
||||||
<span class="task-id">
|
<img
|
||||||
<Done class="kanban-card__done" :is-done="task.done" variant="small"/>
|
v-if="coverImageBlobUrl"
|
||||||
<template v-if="task.identifier === ''">
|
:src="coverImageBlobUrl"
|
||||||
#{{ task.index }}
|
alt=""
|
||||||
</template>
|
class="cover-image"
|
||||||
<template v-else>
|
/>
|
||||||
{{ task.identifier }}
|
<div class="p-2">
|
||||||
</template>
|
<span class="task-id">
|
||||||
</span>
|
<Done class="kanban-card__done" :is-done="task.done" variant="small"/>
|
||||||
<span
|
<template v-if="task.identifier === ''">
|
||||||
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
#{{ task.index }}
|
||||||
class="due-date"
|
</template>
|
||||||
v-if="task.dueDate > 0"
|
<template v-else>
|
||||||
v-tooltip="formatDateLong(task.dueDate)">
|
{{ task.identifier }}
|
||||||
<span class="icon">
|
</template>
|
||||||
<icon :icon="['far', 'calendar-alt']"/>
|
|
||||||
</span>
|
</span>
|
||||||
<time :datetime="formatISO(task.dueDate)">
|
<span
|
||||||
{{ formatDateSince(task.dueDate) }}
|
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
||||||
</time>
|
class="due-date"
|
||||||
</span>
|
v-if="task.dueDate > 0"
|
||||||
<h3>{{ task.title }}</h3>
|
v-tooltip="formatDateLong(task.dueDate)">
|
||||||
<progress
|
<span class="icon">
|
||||||
class="progress is-small"
|
<icon :icon="['far', 'calendar-alt']"/>
|
||||||
v-if="task.percentDone > 0"
|
</span>
|
||||||
:value="task.percentDone * 100" max="100">
|
<time :datetime="formatISO(task.dueDate)">
|
||||||
{{ task.percentDone * 100 }}%
|
{{ formatDateSince(task.dueDate) }}
|
||||||
</progress>
|
</time>
|
||||||
<div class="footer">
|
</span>
|
||||||
<labels :labels="task.labels"/>
|
<h3>{{ task.title }}</h3>
|
||||||
<priority-label :priority="task.priority" :done="task.done"/>
|
<progress
|
||||||
<div class="assignees" v-if="task.assignees.length > 0">
|
class="progress is-small"
|
||||||
<user
|
v-if="task.percentDone > 0"
|
||||||
v-for="u in task.assignees"
|
:value="task.percentDone * 100" max="100">
|
||||||
:avatar-size="24"
|
{{ task.percentDone * 100 }}%
|
||||||
:key="task.id + 'assignee' + u.id"
|
</progress>
|
||||||
:show-username="false"
|
<div class="footer">
|
||||||
:user="u"
|
<labels :labels="task.labels"/>
|
||||||
/>
|
<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} from 'vue'
|
import {ref, computed, watch} 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'
|
||||||
|
@ -77,6 +85,8 @@ 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'
|
||||||
|
@ -114,6 +124,29 @@ 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>
|
||||||
|
@ -125,12 +158,11 @@ $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;
|
||||||
|
|
|
@ -37,7 +37,11 @@
|
||||||
@create="createAndRelateTask"
|
@create="createAndRelateTask"
|
||||||
>
|
>
|
||||||
<template #searchResult="{option: task}">
|
<template #searchResult="{option: task}">
|
||||||
<span v-if="typeof task !== 'string'" class="search-result">
|
<span
|
||||||
|
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"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* @param color
|
* @param color
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function colorFromHex(color: string) {
|
export function colorFromHex(color) {
|
||||||
if (color.substring(0, 1) === '#') {
|
if (color.substring(0, 1) === '#') {
|
||||||
color = color.substring(1, 7)
|
color = color.substring(1, 7)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
||||||
const dateIsValid = date => {
|
export function dateIsValid(date) {
|
||||||
if (date === null) {
|
if (date === null) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Updated"
|
"updated": "Updated"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "You are now subscribed to this {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Aktualizováno"
|
"updated": "Aktualizováno"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "Zde se nemůžete odhlásit, protože jste přihlášeni k odběru {entity} prostřednictvím jeho {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "Aktuálně jste přihlášeni k odběru {entity} a budete dostávat oznámení o změnách.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "Nejste přihlášeni k odběru {entity} a nebudete dostávat upozornění na změny.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "Nyní jste přihlášeni k odběru {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "Nyní jste odhlášeni od odběru {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Aktualisiert"
|
"updated": "Aktualisiert"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "Du kannst hier nicht de-abonnieren, da du diese {entity} durch {parent} abonniert hast.",
|
"subscribedListThroughParentNamespace": "Du kannst hier nicht de-abonnieren, da du diese Liste über ihren Namespace abonniert hast.",
|
||||||
"subscribed": "Du erhältst Benachrichtigungen zu dieser {entity}.",
|
"subscribedTaskThroughParentNamespace": "Du kannst hier nicht de-abonnieren, da du diese Aufgabe über ihren Namespace abonniert hast.",
|
||||||
"notSubscribed": "Du hast diese {entity} nicht abonniert und erhältst keine Benachrichtigungen über Änderungen.",
|
"subscribedTaskThroughParentList": "Du kannst hier nicht de-abonnieren, da du diese Aufgabe über ihre Liste abonniert hast.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "Du hast jetzt diese {entity} abonniert",
|
"subscribeSuccessNamespace": "Du hast diesen Namespace jetzt abonniert",
|
||||||
"unsubscribeSuccess": "Du hast diese {entity} jetzt abbestellt"
|
"unsubscribeSuccessNamespace": "Du hast diesen Namespace jetzt nicht mehr abonniert",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Aktualisiert"
|
"updated": "Aktualisiert"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "Du chasch da nid deabonnierä, weil du zu dere {entity} durch {parent} dezue abonniert bisch.",
|
"subscribedListThroughParentNamespace": "Du kannst hier nicht de-abonnieren, da du diese Liste über ihren Namespace abonniert hast.",
|
||||||
"subscribed": "Du bisch momentan zu dere {entity} abonniert und bechunsch Benachrichtigunge für Änderige.",
|
"subscribedTaskThroughParentNamespace": "Du kannst hier nicht de-abonnieren, da du diese Aufgabe über ihren Namespace abonniert hast.",
|
||||||
"notSubscribed": "Du bisch momentan nid zu dere {entity} abonniert und bechunsch kei Benachrichtigunge für Änderige.",
|
"subscribedTaskThroughParentList": "Du kannst hier nicht de-abonnieren, da du diese Aufgabe über ihre Liste abonniert hast.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "Du hesch die {entity} abonniert",
|
"subscribeSuccessNamespace": "Du hast diesen Namespace jetzt abonniert",
|
||||||
"unsubscribeSuccess": "Du hesch die {entity} deabonniert"
|
"unsubscribeSuccessNamespace": "Du hast diesen Namespace jetzt nicht mehr abonniert",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,7 @@
|
||||||
"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",
|
||||||
|
@ -285,8 +286,8 @@
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
"month": "Month",
|
"month": "Month",
|
||||||
"day": "Day",
|
"day": "Day",
|
||||||
"hour": "Hour",
|
"from": "From",
|
||||||
"range": "Range",
|
"to": "To",
|
||||||
"noDates": "This task has no dates set."
|
"noDates": "This task has no dates set."
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
|
@ -675,13 +676,23 @@
|
||||||
"updated": "Updated"
|
"updated": "Updated"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "You are now subscribed to this {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -693,7 +704,11 @@
|
||||||
"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",
|
||||||
|
@ -842,6 +857,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Updated"
|
"updated": "Updated"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "You are now subscribed to this {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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 n’a été fixée",
|
"showTasksWithoutDates": "Afficher les tâches pour lesquelles aucune date n’a été fixée",
|
||||||
|
@ -672,13 +672,23 @@
|
||||||
"updated": "Mis à jour"
|
"updated": "Mis à jour"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "Tu ne peux pas te désabonner ici car tu es abonné·e à cette {entity} par le biais de son {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "Tu es actuellement abonné·e à cette {entity} et recevras des notifications pour les changements.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "Tu n’es pas abonné·e à cette {entity} et ne recevras pas de notifications pour les changements.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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": "S’abonner",
|
"subscribe": "S’abonner",
|
||||||
"unsubscribe": "Se désabonner",
|
"unsubscribe": "Se désabonner",
|
||||||
"subscribeSuccess": "Tu es maintenant abonné·e à cette {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "Tu es maintenant désabonné·e de cette {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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 l’URL",
|
"copyUrl": "Copier l’URL",
|
||||||
"copyUrlTooltip": "Copier l’URL de cette pièce jointe pour l’utiliser dans le texte"
|
"copyUrlTooltip": "Copier l’URL de cette pièce jointe pour l’utiliser 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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Aggiornato"
|
"updated": "Aggiornato"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "Non puoi annullare l'iscrizione qui perché sei iscritto a questo {entity} attraverso il suo {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "Sei attualmente iscritto a questo {entity} e riceverai notifiche per le modifiche.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "Non sei iscritto a questo {entity} e non riceverai notifiche per le modifiche.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "Ti sei iscritto a questo {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "Ti sei disiscritto a questo {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Bijgewerkt"
|
"updated": "Bijgewerkt"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "You are now subscribed to this {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Zaktualizowano"
|
"updated": "Zaktualizowano"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "Nie możesz zrezygnować z subskrypcji, ponieważ subskrybujesz {entity} za pośrednictwem {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "Obecnie subskrybujesz {entity} i będziesz otrzymywać powiadomienia o zmianach.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "Nie subskrybujesz {entity} i nie będziesz otrzymywać powiadomień o zmianach.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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ę",
|
||||||
"subscribeSuccess": "Od teraz subskrybujesz {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "Już nie subskrybujesz {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Updated"
|
"updated": "Updated"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "You are now subscribed to this {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Atualizado"
|
"updated": "Atualizado"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "Não podes cancelar a tua subscrição aqui porque estás subscrito nesta {entity} através de {parent}.",
|
"subscribedListThroughParentNamespace": "Não podes cancelar a tua subscrição aqui porque estás subscrito nesta lista através do seu espaço.",
|
||||||
"subscribed": "Estás atualmente subscrito a esta {entity} e serás notificado de alterações.",
|
"subscribedTaskThroughParentNamespace": "Não podes cancelar a tua subscrição aqui porque estás subscrito nesta tarefa através do seu espaço.",
|
||||||
"notSubscribed": "Não estás subscrito a esta {entity} e não 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.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "Estás agora subscrito a esta {entity}",
|
"subscribeSuccessNamespace": "Estás agora subscrito a este espaço",
|
||||||
"unsubscribeSuccess": "Não estás mais subcrito a esta {entity}"
|
"unsubscribeSuccessNamespace": "Não estás mais subcrito a este espaço",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Updated"
|
"updated": "Updated"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "You are now subscribed to this {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"title": "Название списка",
|
"title": "Название списка",
|
||||||
"color": "Цвет",
|
"color": "Цвет",
|
||||||
"lists": "Списки",
|
"lists": "Списки",
|
||||||
|
"list": {
|
||||||
|
"title": "Список",
|
||||||
|
"add": "Добавить",
|
||||||
|
"addPlaceholder": "Добавить новую задачу…",
|
||||||
|
"empty": "Список сейчас пуст.",
|
||||||
|
"newTaskCta": "Создать новую задачу.",
|
||||||
|
"editTask": "Изменить задачу"
|
||||||
|
},
|
||||||
"search": "Введи запрос для поиска списка…",
|
"search": "Введи запрос для поиска списка…",
|
||||||
"searchSelect": "Кликни или нажми Enter для выбора этого списка",
|
"searchSelect": "Кликни или нажми Enter для выбора этого списка",
|
||||||
"shared": "Общие списки",
|
"shared": "Общие списки",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"delete": "Удалить"
|
"delete": "Удалить"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"list": {
|
|
||||||
"title": "Список",
|
|
||||||
"add": "Добавить",
|
|
||||||
"addPlaceholder": "Добавить новую задачу…",
|
|
||||||
"empty": "Список сейчас пуст.",
|
|
||||||
"newTaskCta": "Создать новую задачу.",
|
|
||||||
"editTask": "Изменить задачу"
|
|
||||||
},
|
|
||||||
"gantt": {
|
"gantt": {
|
||||||
"title": "Гант",
|
"title": "Гант",
|
||||||
"showTasksWithoutDates": "Показать задачи без установленной даты",
|
"showTasksWithoutDates": "Показать задачи без установленной даты",
|
||||||
|
@ -672,13 +672,23 @@
|
||||||
"updated": "Дата изменения"
|
"updated": "Дата изменения"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "Ты не можешь отписаться здесь, потому что ты подписан на {entity} через {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "Ты подписан на {entity} и будешь получать уведомления об изменениях.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "Ты не подписан на {entity} и не будешь получать уведомления об изменениях.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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": "Отписаться",
|
||||||
"subscribeSuccess": "Ты подписался на {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "Ты отписался от {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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": "Вложения",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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": "Комментарии",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Updated"
|
"updated": "Updated"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "You are now subscribed to this {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Updated"
|
"updated": "Updated"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "You are now subscribed to this {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Updated"
|
"updated": "Updated"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "You are now subscribed to this {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Đã cập nhật"
|
"updated": "Đã cập nhật"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"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ó.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list 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.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"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.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "Bạn hiện đã theo dõi {entity} này",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "Bạn đã bỏ theo dõi {entity} này"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -169,6 +169,14 @@
|
||||||
"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",
|
||||||
|
@ -270,14 +278,6 @@
|
||||||
"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,13 +672,23 @@
|
||||||
"updated": "Updated"
|
"updated": "Updated"
|
||||||
},
|
},
|
||||||
"subscription": {
|
"subscription": {
|
||||||
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
|
"subscribedListThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this list through its namespace.",
|
||||||
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
|
"subscribedTaskThroughParentNamespace": "You can't unsubscribe here because you are subscribed to this task through its namespace.",
|
||||||
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
|
"subscribedTaskThroughParentList": "You can't unsubscribe here because you are subscribed to this task through its list.",
|
||||||
|
"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",
|
||||||
"subscribeSuccess": "You are now subscribed to this {entity}",
|
"subscribeSuccessNamespace": "You are now subscribed to this namespace",
|
||||||
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
|
"unsubscribeSuccessNamespace": "You are now unsubscribed to this namespace",
|
||||||
|
"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",
|
||||||
|
@ -690,7 +700,11 @@
|
||||||
"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",
|
||||||
|
@ -839,6 +853,12 @@
|
||||||
"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": {
|
||||||
|
|
|
@ -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,6 +11,7 @@ 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
|
||||||
|
@ -31,8 +32,9 @@ 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
|
||||||
|
|
|
@ -5,6 +5,8 @@ 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
|
||||||
|
|
|
@ -488,4 +488,8 @@ export function getAuthForRoute(route: RouteLocation) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
router.beforeEach((to) => {
|
||||||
|
return getAuthForRoute(to)
|
||||||
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
|
@ -22,7 +22,6 @@ 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,
|
||||||
|
|
|
@ -75,12 +75,11 @@ 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 && taskIndex && state.buckets[bucketIndex]?.tasks?.[taskIndex] || null,
|
task: bucketIndex !== null && taskIndex !== null && state.buckets[bucketIndex]?.tasks?.[taskIndex] || null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,6 +64,13 @@ 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
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -203,5 +210,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))
|
||||||
}
|
}
|
|
@ -171,32 +171,39 @@ export const useTaskStore = defineStore('task', {
|
||||||
user: IUser,
|
user: IUser,
|
||||||
taskId: ITask['id']
|
taskId: ITask['id']
|
||||||
}) {
|
}) {
|
||||||
const kanbanStore = useKanbanStore()
|
const cancel = setModuleLoading(this)
|
||||||
const taskAssigneeService = new TaskAssigneeService()
|
|
||||||
const r = await taskAssigneeService.create(new TaskAssigneeModel({
|
try {
|
||||||
userId: user.id,
|
const kanbanStore = useKanbanStore()
|
||||||
taskId: taskId,
|
const taskAssigneeService = new TaskAssigneeService()
|
||||||
}))
|
const r = await taskAssigneeService.create(new TaskAssigneeModel({
|
||||||
const t = kanbanStore.getTaskById(taskId)
|
userId: user.id,
|
||||||
if (t.task === null) {
|
taskId: taskId,
|
||||||
// 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.
|
const t = kanbanStore.getTaskById(taskId)
|
||||||
// Vuex seems to have its difficulties with that, so we just log the error and fail silently.
|
if (t.task === null) {
|
||||||
console.debug('Could not add assignee to task in kanban, task not found', t)
|
// Don't try further adding a label if the task is not in kanban
|
||||||
return r
|
// 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({
|
kanbanStore.setTaskInBucketByIndex({
|
||||||
...t,
|
...t,
|
||||||
task: {
|
task: {
|
||||||
...t.task,
|
...t.task,
|
||||||
assignees: [
|
assignees: [
|
||||||
...t.task.assignees,
|
...t.task.assignees,
|
||||||
user,
|
user,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return r
|
|
||||||
|
return r
|
||||||
|
} finally {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async removeAssignee({
|
async removeAssignee({
|
||||||
|
@ -252,7 +259,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', t)
|
console.debug('Could not add label to task in kanban, task not found', {taskId, t})
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,6 +416,13 @@ export const useTaskStore = defineStore('task', {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async setCoverImage(task: ITask, attachment: IAttachment | null) {
|
||||||
|
return this.update({
|
||||||
|
...task,
|
||||||
|
coverImageAttachmentId: attachment ? attachment.id : 0,
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// 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
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
|
|
1
src/types/vue-flatpickr-component.d.ts
vendored
1
src/types/vue-flatpickr-component.d.ts
vendored
|
@ -1 +0,0 @@
|
||||||
declare module 'vue-flatpickr-component';
|
|
|
@ -1,23 +1,47 @@
|
||||||
<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>
|
<card class="gantt-options">
|
||||||
<div class="gantt-options">
|
<fancycheckbox class="is-block" v-model="showTaskswithoutDates">
|
||||||
|
{{ $t('list.gantt.showTasksWithoutDates') }}
|
||||||
|
</fancycheckbox>
|
||||||
|
<div class="range-picker">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="range">{{ $t('list.gantt.range') }}</label>
|
<label class="label" for="dayWidth">{{ $t('list.gantt.size') }}</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="range"
|
id="fromDate"
|
||||||
:placeholder="$t('list.gantt.range')"
|
:placeholder="$t('list.gantt.from')"
|
||||||
v-model="range"
|
v-model="dateFrom"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
@ -29,8 +53,9 @@
|
||||||
<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-tasks-without-dates="showTasksWithoutDates"
|
:show-taskswithout-dates="showTaskswithoutDates"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</card>
|
</card>
|
||||||
|
@ -47,9 +72,8 @@ 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-chart.vue'
|
import GanttChart from '@/components/tasks/gantt-component.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: {
|
||||||
|
@ -58,16 +82,14 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const showTasksWithoutDates = ref(false)
|
const DEFAULT_DAY_COUNT = 35
|
||||||
|
|
||||||
const now = new Date()
|
const showTaskswithoutDates = ref(false)
|
||||||
const defaultFrom = format(new Date((new Date()).setDate(now.getDate() - 15)), 'yyyy-LL-dd')
|
const dayWidth = ref(DEFAULT_DAY_COUNT)
|
||||||
const defaultTo = format(new Date((new Date()).setDate(now.getDate() + 55)), 'yyyy-LL-dd')
|
|
||||||
const range = ref(`${defaultFrom} to ${defaultTo}`)
|
|
||||||
|
|
||||||
// TODO: only update once both dates are available (maybe use a watcher + refs instead?)
|
const now = ref(new Date())
|
||||||
const dateFrom = computed(() => range.value?.split(' to ')[0] ?? defaultFrom)
|
const dateFrom = ref(new Date((new Date()).setDate(now.value.getDate() - 15)))
|
||||||
const dateTo = computed(() => range.value?.split(' to ')[1] ?? defaultTo)
|
const dateTo = ref(new Date((new Date()).setDate(now.value.getDate() + 30)))
|
||||||
|
|
||||||
const {t} = useI18n({useScope: 'global'})
|
const {t} = useI18n({useScope: 'global'})
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
@ -76,7 +98,6 @@ 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,
|
||||||
},
|
},
|
||||||
|
@ -98,35 +119,46 @@ const flatPickerConfig = computed(() => ({
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field {
|
.range-picker {
|
||||||
margin-bottom: 0;
|
display: flex;
|
||||||
width: 33%;
|
margin-bottom: 1rem;
|
||||||
|
width: 50%;
|
||||||
&:not(:last-child) {
|
|
||||||
padding-right: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: $tablet) {
|
@media screen and (max-width: $tablet) {
|
||||||
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
|
||||||
margin-top: .5rem;
|
|
||||||
padding-right: 0 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&, .input {
|
.field {
|
||||||
font-size: .8rem;
|
margin-bottom: 0;
|
||||||
}
|
width: 33%;
|
||||||
|
|
||||||
.select, .select select {
|
&:not(:last-child) {
|
||||||
height: auto;
|
padding-right: .5rem;
|
||||||
width: 100%;
|
}
|
||||||
font-size: .8rem;
|
|
||||||
}
|
@media screen and (max-width: $tablet) {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-top: .5rem;
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1
src/views/migrator/icons/ticktick.svg
Normal file
1
src/views/migrator/icons/ticktick.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<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>
|
After Width: | Height: | Size: 471 B |
|
@ -3,6 +3,7 @@ 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
|
||||||
|
@ -42,4 +43,10 @@ export const MIGRATORS: IMigratorRecord = {
|
||||||
icon: vikunjaFileIcon,
|
icon: vikunjaFileIcon,
|
||||||
isFileMigrator: true,
|
isFileMigrator: true,
|
||||||
},
|
},
|
||||||
|
ticktick: {
|
||||||
|
id: 'ticktick',
|
||||||
|
name: 'TickTick',
|
||||||
|
icon: tickTickIcon as string,
|
||||||
|
isFileMigrator: true,
|
||||||
|
},
|
||||||
}
|
}
|
|
@ -39,7 +39,7 @@
|
||||||
</div>
|
</div>
|
||||||
<priority-select
|
<priority-select
|
||||||
:disabled="!canWrite"
|
:disabled="!canWrite"
|
||||||
@update:model-value="saveTask"
|
@update:model-value="setPriority"
|
||||||
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="saveTask"
|
@update:model-value="setPercentDone"
|
||||||
ref="percentDone"
|
ref="percentDone"
|
||||||
v-model="task.percentDone"/>
|
v-model="task.percentDone"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -218,7 +218,8 @@
|
||||||
<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-id="taskId"
|
:task="task"
|
||||||
|
@task-changed="({coverImageAttachmentId}) => task.coverImageAttachmentId = coverImageAttachmentId"
|
||||||
ref="attachments"
|
ref="attachments"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -442,7 +443,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} from '@/constants/priorities'
|
import {PRIORITIES, type Priority} 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'
|
||||||
|
@ -500,7 +501,7 @@ const attachmentStore = useAttachmentStore()
|
||||||
const taskStore = useTaskStore()
|
const taskStore = useTaskStore()
|
||||||
const kanbanStore = useKanbanStore()
|
const kanbanStore = useKanbanStore()
|
||||||
|
|
||||||
const task = reactive(new TaskModel())
|
const task = reactive<ITask>(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
|
||||||
|
@ -675,21 +676,19 @@ function setFieldActive(fieldName: keyof typeof activeFields) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveTask(args?: {
|
async function saveTask(args?: {
|
||||||
task: ITask,
|
task: ITask,
|
||||||
showNotification?: boolean,
|
undoCallback?: () => void,
|
||||||
undoCallback?: () => void,
|
}) {
|
||||||
}) {
|
const {
|
||||||
const {
|
task: currentTask,
|
||||||
task: currentTask,
|
undoCallback,
|
||||||
showNotification,
|
} = {
|
||||||
undoCallback,
|
...{
|
||||||
} = {
|
task: cloneDeep(task),
|
||||||
...{
|
},
|
||||||
task: cloneDeep(task),
|
...args,
|
||||||
showNotification: true,
|
}
|
||||||
},
|
|
||||||
...args,
|
|
||||||
}
|
|
||||||
if (!canWrite.value) {
|
if (!canWrite.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -710,10 +709,6 @@ 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 = [{
|
||||||
|
@ -759,6 +754,28 @@ 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>
|
||||||
|
|
|
@ -127,6 +127,24 @@
|
||||||
</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
|
||||||
|
@ -202,13 +220,14 @@ 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(() => route.params.id)
|
const teamId = computed(() => Number(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('')
|
||||||
|
@ -287,6 +306,19 @@ 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>
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
<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>
|
||||||
|
@ -33,6 +36,7 @@ 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,
|
||||||
|
|
BIN
vendor/infectoone-vue-ganttastic-2.1.1.tgz
vendored
BIN
vendor/infectoone-vue-ganttastic-2.1.1.tgz
vendored
Binary file not shown.
Loading…
Reference in a new issue