diff --git a/cypress/integration/list/list-history.spec.js b/cypress/integration/list/list-history.spec.js
new file mode 100644
index 00000000..b7633cbd
--- /dev/null
+++ b/cypress/integration/list/list-history.spec.js
@@ -0,0 +1,56 @@
+import {ListFactory} from '../../factories/list'
+
+import '../../support/authenticateUser'
+import {prepareLists} from './prepareLists'
+
+describe('List History', () => {
+ prepareLists()
+
+ it('should show a list history on the home page', () => {
+ cy.intercept(Cypress.env('API_URL') + '/namespaces*').as('loadNamespaces')
+ cy.intercept(Cypress.env('API_URL') + '/lists/*').as('loadList')
+
+ const lists = ListFactory.create(6)
+
+ cy.visit('/')
+ cy.wait('@loadNamespaces')
+ cy.get('body')
+ .should('not.contain', 'Last viewed')
+
+ cy.visit(`/lists/${lists[0].id}`)
+ cy.wait('@loadNamespaces')
+ cy.wait('@loadList')
+ cy.visit(`/lists/${lists[1].id}`)
+ cy.wait('@loadNamespaces')
+ cy.wait('@loadList')
+ cy.visit(`/lists/${lists[2].id}`)
+ cy.wait('@loadNamespaces')
+ cy.wait('@loadList')
+ cy.visit(`/lists/${lists[3].id}`)
+ cy.wait('@loadNamespaces')
+ cy.wait('@loadList')
+ cy.visit(`/lists/${lists[4].id}`)
+ cy.wait('@loadNamespaces')
+ cy.wait('@loadList')
+ cy.visit(`/lists/${lists[5].id}`)
+ cy.wait('@loadNamespaces')
+ cy.wait('@loadList')
+
+ // cy.visit('/')
+ // cy.wait('@loadNamespaces')
+ // Not using cy.visit here to work around the redirect issue fixed in #1337
+ cy.get('nav.menu.top-menu a')
+ .contains('Overview')
+ .click()
+
+ cy.get('body')
+ .should('contain', 'Last viewed')
+ cy.get('.list-cards-wrapper-2-rows')
+ .should('not.contain', lists[0].title)
+ .should('contain', lists[1].title)
+ .should('contain', lists[2].title)
+ .should('contain', lists[3].title)
+ .should('contain', lists[4].title)
+ .should('contain', lists[5].title)
+ })
+})
\ No newline at end of file
diff --git a/cypress/integration/list/list-view-gantt.spec.js b/cypress/integration/list/list-view-gantt.spec.js
new file mode 100644
index 00000000..69805a30
--- /dev/null
+++ b/cypress/integration/list/list-view-gantt.spec.js
@@ -0,0 +1,76 @@
+import {formatISO, format} from 'date-fns'
+import {TaskFactory} from '../../factories/task'
+import {prepareLists} from './prepareLists'
+
+import '../../support/authenticateUser'
+
+describe('List View Gantt', () => {
+ prepareLists()
+
+ it('Hides tasks with no dates', () => {
+ const tasks = TaskFactory.create(1)
+ cy.visit('/lists/1/gantt')
+
+ cy.get('.gantt-chart .tasks')
+ .should('not.contain', tasks[0].title)
+ })
+
+ it('Shows tasks from the current and next month', () => {
+ const now = new Date()
+ const nextMonth = now
+ nextMonth.setDate(1)
+ nextMonth.setMonth(now.getMonth() + 1)
+
+ cy.visit('/lists/1/gantt')
+
+ cy.get('.gantt-chart .months')
+ .should('contain', format(now, 'MMMM'))
+ .should('contain', format(nextMonth, 'MMMM'))
+ })
+
+ it('Shows tasks with dates', () => {
+ const now = new Date()
+ const tasks = TaskFactory.create(1, {
+ start_date: formatISO(now),
+ end_date: formatISO(now.setDate(now.getDate() + 4))
+ })
+ cy.visit('/lists/1/gantt')
+
+ cy.get('.gantt-chart .tasks')
+ .should('not.be.empty')
+ cy.get('.gantt-chart .tasks')
+ .should('contain', tasks[0].title)
+ })
+
+ it('Shows tasks with no dates after enabling them', () => {
+ TaskFactory.create(1, {
+ start_date: null,
+ end_date: null,
+ })
+ cy.visit('/lists/1/gantt')
+
+ cy.get('.gantt-options .fancycheckbox')
+ .contains('Show tasks which don\'t have dates set')
+ .click()
+
+ cy.get('.gantt-chart .tasks')
+ .should('not.be.empty')
+ cy.get('.gantt-chart .tasks .task.nodate')
+ .should('exist')
+ })
+
+ it('Drags a task around', () => {
+ const now = new Date()
+ TaskFactory.create(1, {
+ start_date: formatISO(now),
+ end_date: formatISO(now.setDate(now.getDate() + 4))
+ })
+ cy.visit('/lists/1/gantt')
+
+ cy.get('.gantt-chart .tasks .task')
+ .first()
+ .trigger('mousedown', {which: 1})
+ .trigger('mousemove', {clientX: 500, clientY: 0})
+ .trigger('mouseup', {force: true})
+ })
+})
\ No newline at end of file
diff --git a/cypress/integration/list/list-view-kanban.spec.js b/cypress/integration/list/list-view-kanban.spec.js
new file mode 100644
index 00000000..52d67282
--- /dev/null
+++ b/cypress/integration/list/list-view-kanban.spec.js
@@ -0,0 +1,196 @@
+import {BucketFactory} from '../../factories/bucket'
+import {ListFactory} from '../../factories/list'
+import {TaskFactory} from '../../factories/task'
+import {prepareLists} from './prepareLists'
+
+import '../../support/authenticateUser'
+
+describe('List View Kanban', () => {
+ let buckets
+ prepareLists()
+
+ beforeEach(() => {
+ buckets = BucketFactory.create(2)
+ })
+
+ it('Shows all buckets with their tasks', () => {
+ const data = TaskFactory.create(10, {
+ list_id: 1,
+ bucket_id: 1,
+ })
+ cy.visit('/lists/1/kanban')
+
+ cy.get('.kanban .bucket .title')
+ .contains(buckets[0].title)
+ .should('exist')
+ cy.get('.kanban .bucket .title')
+ .contains(buckets[1].title)
+ .should('exist')
+ cy.get('.kanban .bucket')
+ .first()
+ .should('contain', data[0].title)
+ })
+
+ it('Can add a new task to a bucket', () => {
+ TaskFactory.create(2, {
+ list_id: 1,
+ bucket_id: 1,
+ })
+ cy.visit('/lists/1/kanban')
+
+ cy.getSettled('.kanban .bucket')
+ .contains(buckets[0].title)
+ .get('.bucket-footer .button')
+ .contains('Add another task')
+ .click()
+ cy.get('.kanban .bucket')
+ .contains(buckets[0].title)
+ .get('.bucket-footer .field .control input.input')
+ .type('New Task{enter}')
+
+ cy.get('.kanban .bucket')
+ .first()
+ .should('contain', 'New Task')
+ })
+
+ it('Can create a new bucket', () => {
+ cy.visit('/lists/1/kanban')
+
+ cy.get('.kanban .bucket.new-bucket .button')
+ .click()
+ cy.get('.kanban .bucket.new-bucket input.input')
+ .type('New Bucket{enter}')
+
+ cy.wait(1000) // Wait for the request to finish
+ cy.get('.kanban .bucket .title')
+ .contains('New Bucket')
+ .should('exist')
+ })
+
+ it('Can set a bucket limit', () => {
+ cy.visit('/lists/1/kanban')
+
+ cy.getSettled('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
+ .first()
+ .click()
+ cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
+ .contains('Limit: Not Set')
+ .click()
+ cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input')
+ .first()
+ .type(3)
+ cy.get('[data-cy="setBucketLimit"]')
+ .first()
+ .click()
+
+ cy.get('.kanban .bucket .bucket-header span.limit')
+ .contains('0/3')
+ .should('exist')
+ })
+
+ it('Can rename a bucket', () => {
+ cy.visit('/lists/1/kanban')
+
+ cy.getSettled('.kanban .bucket .bucket-header .title')
+ .first()
+ .type('{selectall}New Bucket Title{enter}')
+ cy.get('.kanban .bucket .bucket-header .title')
+ .first()
+ .should('contain', 'New Bucket Title')
+ })
+
+ it('Can delete a bucket', () => {
+ cy.visit('/lists/1/kanban')
+
+ cy.getSettled('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
+ .first()
+ .click()
+ cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
+ .contains('Delete')
+ .click()
+ cy.get('.modal-mask .modal-container .modal-content .header')
+ .should('contain', 'Delete the bucket')
+ cy.get('.modal-mask .modal-container .modal-content .actions .button')
+ .contains('Do it!')
+ .click()
+
+ cy.get('.kanban .bucket .title')
+ .contains(buckets[0].title)
+ .should('not.exist')
+ cy.get('.kanban .bucket .title')
+ .contains(buckets[1].title)
+ .should('exist')
+ })
+
+ it('Can drag tasks around', () => {
+ const tasks = TaskFactory.create(2, {
+ list_id: 1,
+ bucket_id: 1,
+ })
+ cy.visit('/lists/1/kanban')
+
+ cy.getSettled('.kanban .bucket .tasks .task')
+ .contains(tasks[0].title)
+ .first()
+ .drag('.kanban .bucket:nth-child(2) .tasks .dropper')
+
+ cy.get('.kanban .bucket:nth-child(2) .tasks')
+ .should('contain', tasks[0].title)
+ cy.get('.kanban .bucket:nth-child(1) .tasks')
+ .should('not.contain', tasks[0].title)
+ })
+
+ it('Should navigate to the task when the task card is clicked', () => {
+ const tasks = TaskFactory.create(5, {
+ id: '{increment}',
+ list_id: 1,
+ bucket_id: 1,
+ })
+ cy.visit('/lists/1/kanban')
+
+ cy.getSettled('.kanban .bucket .tasks .task')
+ .contains(tasks[0].title)
+ .should('be.visible')
+ .click()
+
+ cy.url()
+ .should('contain', `/tasks/${tasks[0].id}`, { timeout: 1000 })
+ })
+
+ it('Should remove a task from the kanban board when moving it to another list', () => {
+ const lists = ListFactory.create(2)
+ BucketFactory.create(2, {
+ list_id: '{increment}',
+ })
+ const tasks = TaskFactory.create(5, {
+ id: '{increment}',
+ list_id: 1,
+ bucket_id: 1,
+ })
+ const task = tasks[0]
+ cy.visit('/lists/1/kanban')
+
+ cy.getSettled('.kanban .bucket .tasks .task')
+ .contains(task.title)
+ .should('be.visible')
+ .click()
+
+ cy.get('.task-view .action-buttons .button', { timeout: 3000 })
+ .contains('Move task')
+ .click()
+ cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
+ .type(`${lists[1].title}{enter}`)
+ // The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
+ // presses enter and we can't simulate pressing on enter to select the item.
+ cy.get('.task-view .content.details .field .multiselect.control .search-results')
+ .children()
+ .first()
+ .click()
+
+ cy.get('.global-notification', { timeout: 1000 })
+ .should('contain', 'Success')
+ cy.go('back')
+ cy.get('.kanban .bucket')
+ .should('not.contain', task.title)
+ })
+})
\ No newline at end of file
diff --git a/cypress/integration/list/list-view-list.spec.js b/cypress/integration/list/list-view-list.spec.js
new file mode 100644
index 00000000..e1a4a0f6
--- /dev/null
+++ b/cypress/integration/list/list-view-list.spec.js
@@ -0,0 +1,97 @@
+import {UserListFactory} from '../../factories/users_list'
+import {TaskFactory} from '../../factories/task'
+import {UserFactory} from '../../factories/user'
+import {ListFactory} from '../../factories/list'
+import {prepareLists} from './prepareLists'
+
+import '../../support/authenticateUser'
+
+describe('List View List', () => {
+ prepareLists()
+
+ it('Should be an empty list', () => {
+ cy.visit('/lists/1')
+ cy.url()
+ .should('contain', '/lists/1/list')
+ cy.get('.list-title h1')
+ .should('contain', 'First List')
+ cy.get('.list-title .dropdown')
+ .should('exist')
+ cy.get('p')
+ .contains('This list is currently empty.')
+ .should('exist')
+ })
+
+ it('Should navigate to the task when the title is clicked', () => {
+ const tasks = TaskFactory.create(5, {
+ id: '{increment}',
+ list_id: 1,
+ })
+ cy.visit('/lists/1/list')
+
+ cy.get('.tasks .task .tasktext')
+ .contains(tasks[0].title)
+ .first()
+ .click()
+
+ cy.url()
+ .should('contain', `/tasks/${tasks[0].id}`)
+ })
+
+ it('Should not see any elements for a list which is shared read only', () => {
+ UserFactory.create(2)
+ UserListFactory.create(1, {
+ list_id: 2,
+ user_id: 1,
+ right: 0,
+ })
+ const lists = ListFactory.create(2, {
+ owner_id: '{increment}',
+ namespace_id: '{increment}',
+ })
+ cy.visit(`/lists/${lists[1].id}/`)
+
+ cy.get('.list-title a.icon')
+ .should('not.exist')
+ cy.get('input.input[placeholder="Add a new task..."')
+ .should('not.exist')
+ })
+
+ it('Should only show the color of a list in the navigation and not in the list view', () => {
+ const lists = ListFactory.create(1, {
+ hex_color: '00db60',
+ })
+ TaskFactory.create(10, {
+ list_id: lists[0].id,
+ })
+ cy.visit(`/lists/${lists[0].id}/`)
+
+ cy.get('.menu-list li .list-menu-link .color-bubble')
+ .should('have.css', 'background-color', 'rgb(0, 219, 96)')
+ cy.get('.tasks-container .tasks .color-bubble')
+ .should('not.exist')
+ })
+
+ it('Should paginate for > 50 tasks', () => {
+ const tasks = TaskFactory.create(100, {
+ id: '{increment}',
+ title: i => `task${i}`,
+ list_id: 1,
+ })
+ cy.visit('/lists/1/list')
+
+ cy.get('.tasks-container .tasks')
+ .should('contain', tasks[99].title)
+
+ cy.get('.card-content .pagination .pagination-link')
+ .contains('2')
+ .click()
+
+ cy.url()
+ .should('contain', '?page=2')
+ cy.get('.tasks-container .tasks')
+ .should('contain', tasks[1].title)
+ cy.get('.tasks-container .tasks')
+ .should('not.contain', tasks[99].title)
+ })
+})
\ No newline at end of file
diff --git a/cypress/integration/list/list-view-table.spec.js b/cypress/integration/list/list-view-table.spec.js
new file mode 100644
index 00000000..e0336efc
--- /dev/null
+++ b/cypress/integration/list/list-view-table.spec.js
@@ -0,0 +1,52 @@
+import {TaskFactory} from '../../factories/task'
+
+import '../../support/authenticateUser'
+
+describe('List View Table', () => {
+ it('Should show a table with tasks', () => {
+ const tasks = TaskFactory.create(1)
+ cy.visit('/lists/1/table')
+
+ cy.get('.list-table table.table')
+ .should('exist')
+ cy.get('.list-table table.table')
+ .should('contain', tasks[0].title)
+ })
+
+ it('Should have working column switches', () => {
+ TaskFactory.create(1)
+ cy.visit('/lists/1/table')
+
+ cy.get('.list-table .filter-container .items .button')
+ .contains('Columns')
+ .click()
+ cy.get('.list-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
+ .contains('Priority')
+ .click()
+ cy.get('.list-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
+ .contains('Done')
+ .click()
+
+ cy.get('.list-table table.table th')
+ .contains('Priority')
+ .should('exist')
+ cy.get('.list-table table.table th')
+ .contains('Done')
+ .should('not.exist')
+ })
+
+ it('Should navigate to the task when the title is clicked', () => {
+ const tasks = TaskFactory.create(5, {
+ id: '{increment}',
+ list_id: 1,
+ })
+ cy.visit('/lists/1/table')
+
+ cy.get('.list-table table.table')
+ .contains(tasks[0].title)
+ .click()
+
+ cy.url()
+ .should('contain', `/tasks/${tasks[0].id}`)
+ })
+})
\ No newline at end of file
diff --git a/cypress/integration/list/list.spec.js b/cypress/integration/list/list.spec.js
index 8b8630fd..ce7f6f55 100644
--- a/cypress/integration/list/list.spec.js
+++ b/cypress/integration/list/list.spec.js
@@ -1,25 +1,11 @@
-import {formatISO, format} from 'date-fns'
-
import {TaskFactory} from '../../factories/task'
-import {ListFactory} from '../../factories/list'
-import {UserListFactory} from '../../factories/users_list'
-import {UserFactory} from '../../factories/user'
-import {NamespaceFactory} from '../../factories/namespace'
-import {BucketFactory} from '../../factories/bucket'
+import {prepareLists} from './prepareLists'
import '../../support/authenticateUser'
describe('Lists', () => {
let lists
-
- beforeEach(() => {
- UserFactory.create(1)
- NamespaceFactory.create(1)
- lists = ListFactory.create(1, {
- title: 'First List'
- })
- TaskFactory.truncate()
- })
+ prepareLists((newLists) => (lists = newLists))
it('Should create a new list', () => {
cy.visit('/')
@@ -29,7 +15,7 @@ describe('Lists', () => {
.contains('New list')
.click()
cy.url()
- .should('contain', '/namespaces/1/list')
+ .should('contain', '/lists/new/1')
cy.get('.card-header-title')
.contains('New list')
cy.get('input.input')
@@ -56,7 +42,7 @@ describe('Lists', () => {
})
it('Should rename the list in all places', () => {
- const tasks = TaskFactory.create(5, {
+ TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
})
@@ -112,429 +98,4 @@ describe('Lists', () => {
cy.location('pathname')
.should('equal', '/')
})
-
- describe('List View', () => {
- it('Should be an empty list', () => {
- cy.visit('/lists/1')
- cy.url()
- .should('contain', '/lists/1/list')
- cy.get('.list-title h1')
- .should('contain', 'First List')
- cy.get('.list-title .dropdown')
- .should('exist')
- cy.get('p')
- .contains('This list is currently empty.')
- .should('exist')
- })
-
- it('Should navigate to the task when the title is clicked', () => {
- const tasks = TaskFactory.create(5, {
- id: '{increment}',
- list_id: 1,
- })
- cy.visit('/lists/1/list')
-
- cy.get('.tasks .task .tasktext')
- .contains(tasks[0].title)
- .first()
- .click()
-
- cy.url()
- .should('contain', `/tasks/${tasks[0].id}`)
- })
-
- it('Should not see any elements for a list which is shared read only', () => {
- UserFactory.create(2)
- UserListFactory.create(1, {
- list_id: 2,
- user_id: 1,
- right: 0,
- })
- const lists = ListFactory.create(2, {
- owner_id: '{increment}',
- namespace_id: '{increment}',
- })
- cy.visit(`/lists/${lists[1].id}/`)
-
- cy.get('.list-title a.icon')
- .should('not.exist')
- cy.get('input.input[placeholder="Add a new task..."')
- .should('not.exist')
- })
-
- it('Should only show the color of a list in the navigation and not in the list view', () => {
- const lists = ListFactory.create(1, {
- hex_color: '00db60',
- })
- TaskFactory.create(10, {
- list_id: lists[0].id,
- })
- cy.visit(`/lists/${lists[0].id}/`)
-
- cy.get('.menu-list li .list-menu-link .color-bubble')
- .should('have.css', 'background-color', 'rgb(0, 219, 96)')
- cy.get('.tasks-container .tasks .color-bubble')
- .should('not.exist')
- })
-
- it('Should paginate for > 50 tasks', () => {
- const tasks = TaskFactory.create(100, {
- id: '{increment}',
- title: i => `task${i}`,
- list_id: 1,
- })
- cy.visit('/lists/1/list')
-
- cy.get('.tasks-container .tasks')
- .should('contain', tasks[99].title)
-
- cy.get('.card-content .pagination .pagination-link')
- .contains('2')
- .click()
-
- cy.url()
- .should('contain', '?page=2')
- cy.get('.tasks-container .tasks')
- .should('contain', tasks[1].title)
- cy.get('.tasks-container .tasks')
- .should('not.contain', tasks[99].title)
- })
- })
-
- describe('Table View', () => {
- it('Should show a table with tasks', () => {
- const tasks = TaskFactory.create(1)
- cy.visit('/lists/1/table')
-
- cy.get('.table-view table.table')
- .should('exist')
- cy.get('.table-view table.table')
- .should('contain', tasks[0].title)
- })
-
- it('Should have working column switches', () => {
- TaskFactory.create(1)
- cy.visit('/lists/1/table')
-
- cy.get('.table-view .filter-container .items .button')
- .contains('Columns')
- .click()
- cy.get('.table-view .filter-container .card.columns-filter .card-content .fancycheckbox .check')
- .contains('Priority')
- .click()
- cy.get('.table-view .filter-container .card.columns-filter .card-content .fancycheckbox .check')
- .contains('Done')
- .click()
-
- cy.get('.table-view table.table th')
- .contains('Priority')
- .should('exist')
- cy.get('.table-view table.table th')
- .contains('Done')
- .should('not.exist')
- })
-
- it('Should navigate to the task when the title is clicked', () => {
- const tasks = TaskFactory.create(5, {
- id: '{increment}',
- list_id: 1,
- })
- cy.visit('/lists/1/table')
-
- cy.get('.table-view table.table')
- .contains(tasks[0].title)
- .click()
-
- cy.url()
- .should('contain', `/tasks/${tasks[0].id}`)
- })
- })
-
- describe('Gantt View', () => {
- it('Hides tasks with no dates', () => {
- const tasks = TaskFactory.create(1)
- cy.visit('/lists/1/gantt')
-
- cy.get('.gantt-chart-container .gantt-chart .tasks')
- .should('not.contain', tasks[0].title)
- })
-
- it('Shows tasks from the current and next month', () => {
- const now = new Date()
- const nextMonth = now
- nextMonth.setDate(1)
- nextMonth.setMonth(now.getMonth() + 1)
-
- cy.visit('/lists/1/gantt')
-
- cy.get('.gantt-chart-container .gantt-chart .months')
- .should('contain', format(now, 'MMMM'))
- .should('contain', format(nextMonth, 'MMMM'))
- })
-
- it('Shows tasks with dates', () => {
- const now = new Date()
- const tasks = TaskFactory.create(1, {
- start_date: formatISO(now),
- end_date: formatISO(now.setDate(now.getDate() + 4))
- })
- cy.visit('/lists/1/gantt')
-
- cy.get('.gantt-chart-container .gantt-chart .tasks')
- .should('not.be.empty')
- cy.get('.gantt-chart-container .gantt-chart .tasks')
- .should('contain', tasks[0].title)
- })
-
- it('Shows tasks with no dates after enabling them', () => {
- TaskFactory.create(1, {
- start_date: null,
- end_date: null,
- })
- cy.visit('/lists/1/gantt')
-
- cy.get('.gantt-chart-container .gantt-options .fancycheckbox')
- .contains('Show tasks which don\'t have dates set')
- .click()
-
- cy.get('.gantt-chart-container .gantt-chart .tasks')
- .should('not.be.empty')
- cy.get('.gantt-chart-container .gantt-chart .tasks .task.nodate')
- .should('exist')
- })
-
- it('Drags a task around', () => {
- const now = new Date()
- TaskFactory.create(1, {
- start_date: formatISO(now),
- end_date: formatISO(now.setDate(now.getDate() + 4))
- })
- cy.visit('/lists/1/gantt')
-
- cy.get('.gantt-chart-container .gantt-chart .tasks .task')
- .first()
- .trigger('mousedown', {which: 1})
- .trigger('mousemove', {clientX: 500, clientY: 0})
- .trigger('mouseup', {force: true})
- })
- })
-
- describe('Kanban', () => {
- let buckets
-
- beforeEach(() => {
- buckets = BucketFactory.create(2)
- })
-
- it('Shows all buckets with their tasks', () => {
- const data = TaskFactory.create(10, {
- list_id: 1,
- bucket_id: 1,
- })
- cy.visit('/lists/1/kanban')
-
- cy.get('.kanban .bucket .title')
- .contains(buckets[0].title)
- .should('exist')
- cy.get('.kanban .bucket .title')
- .contains(buckets[1].title)
- .should('exist')
- cy.get('.kanban .bucket')
- .first()
- .should('contain', data[0].title)
- })
-
- it('Can add a new task to a bucket', () => {
- const data = TaskFactory.create(2, {
- list_id: 1,
- bucket_id: 1,
- })
- cy.visit('/lists/1/kanban')
-
- cy.get('.kanban .bucket')
- .contains(buckets[0].title)
- .get('.bucket-footer .button')
- .contains('Add another task')
- .click()
- cy.get('.kanban .bucket')
- .contains(buckets[0].title)
- .get('.bucket-footer .field .control input.input')
- .type('New Task{enter}')
-
- cy.get('.kanban .bucket')
- .first()
- .should('contain', 'New Task')
- })
-
- it('Can create a new bucket', () => {
- cy.visit('/lists/1/kanban')
-
- cy.get('.kanban .bucket.new-bucket .button')
- .click()
- cy.get('.kanban .bucket.new-bucket input.input')
- .type('New Bucket{enter}')
-
- cy.wait(1000) // Wait for the request to finish
- cy.get('.kanban .bucket .title')
- .contains('New Bucket')
- .should('exist')
- })
-
- it('Can set a bucket limit', () => {
- cy.visit('/lists/1/kanban')
-
- cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
- .first()
- .click()
- cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
- .contains('Limit: Not Set')
- .click()
- cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input')
- .first()
- .type(3)
- cy.get('[data-cy="setBucketLimit"]')
- .first()
- .click()
-
- cy.get('.kanban .bucket .bucket-header span.limit')
- .contains('0/3')
- .should('exist')
- })
-
- it('Can rename a bucket', () => {
- cy.visit('/lists/1/kanban')
-
- cy.get('.kanban .bucket .bucket-header .title')
- .first()
- .type('{selectall}New Bucket Title{enter}')
- cy.get('.kanban .bucket .bucket-header .title')
- .first()
- .should('contain', 'New Bucket Title')
- })
-
- it('Can delete a bucket', () => {
- cy.visit('/lists/1/kanban')
-
- cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
- .first()
- .click()
- cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
- .contains('Delete')
- .click()
- cy.get('.modal-mask .modal-container .modal-content .header')
- .should('contain', 'Delete the bucket')
- cy.get('.modal-mask .modal-container .modal-content .actions .button')
- .contains('Do it!')
- .click()
-
- cy.get('.kanban .bucket .title')
- .contains(buckets[0].title)
- .should('not.exist')
- cy.get('.kanban .bucket .title')
- .contains(buckets[1].title)
- .should('exist')
- })
-
- it('Can drag tasks around', () => {
- const tasks = TaskFactory.create(2, {
- list_id: 1,
- bucket_id: 1,
- })
- cy.visit('/lists/1/kanban')
-
- cy.get('.kanban .bucket .tasks .task')
- .contains(tasks[0].title)
- .first()
- .drag('.kanban .bucket:nth-child(2) .tasks .dropper')
-
- cy.get('.kanban .bucket:nth-child(2) .tasks')
- .should('contain', tasks[0].title)
- cy.get('.kanban .bucket:nth-child(1) .tasks')
- .should('not.contain', tasks[0].title)
- })
-
- it('Should navigate to the task when the task card is clicked', () => {
- const tasks = TaskFactory.create(5, {
- id: '{increment}',
- list_id: 1,
- bucket_id: 1,
- })
- cy.visit('/lists/1/kanban')
-
- cy.getSettled('.kanban .bucket .tasks .task')
- .contains(tasks[0].title)
- .should('be.visible')
- .click()
-
- cy.url()
- .should('contain', `/tasks/${tasks[0].id}`)
- })
-
- it('Should remove a task from the kanban board when moving it to another list', () => {
- const lists = ListFactory.create(2)
- BucketFactory.create(2, {
- list_id: '{increment}',
- })
- const tasks = TaskFactory.create(5, {
- id: '{increment}',
- list_id: 1,
- bucket_id: 1,
- })
- const task = tasks[0]
- cy.visit('/lists/1/kanban')
-
- cy.getSettled('.kanban .bucket .tasks .task')
- .contains(task.title)
- .should('be.visible')
- .click()
-
- cy.get('.task-view .action-buttons .button')
- .contains('Move task')
- .click()
- cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
- .type(`${lists[1].title}{enter}`)
- // The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
- // presses enter and we can't simulate pressing on enter to select the item.
- cy.get('.task-view .content.details .field .multiselect.control .search-results')
- .children()
- .first()
- .click()
-
- cy.get('.global-notification', { timeout: 1000 })
- .should('contain', 'Success')
- cy.go('back')
- cy.get('.kanban .bucket')
- .should('not.contain', task.title)
- })
- })
-
- describe('List history', () => {
- it('should show a list history on the home page', () => {
- const lists = ListFactory.create(6)
-
- cy.visit('/')
- cy.get('h3')
- .contains('Last viewed')
- .should('not.exist')
-
- cy.visit(`/lists/${lists[0].id}`)
- cy.visit(`/lists/${lists[1].id}`)
- cy.visit(`/lists/${lists[2].id}`)
- cy.visit(`/lists/${lists[3].id}`)
- cy.visit(`/lists/${lists[4].id}`)
- cy.visit(`/lists/${lists[5].id}`)
-
- cy.visit('/')
- cy.get('h3')
- .contains('Last viewed')
- .should('exist')
- cy.get('.list-cards-wrapper-2-rows')
- .should('not.contain', lists[0].title)
- .should('contain', lists[1].title)
- .should('contain', lists[2].title)
- .should('contain', lists[3].title)
- .should('contain', lists[4].title)
- .should('contain', lists[5].title)
- })
- })
})
diff --git a/cypress/integration/list/prepareLists.js b/cypress/integration/list/prepareLists.js
new file mode 100644
index 00000000..afef6ba4
--- /dev/null
+++ b/cypress/integration/list/prepareLists.js
@@ -0,0 +1,16 @@
+import {ListFactory} from '../../factories/list'
+import {UserFactory} from '../../factories/user'
+import {NamespaceFactory} from '../../factories/namespace'
+import {TaskFactory} from '../../factories/task'
+
+export function prepareLists(setLists = () => {}) {
+ beforeEach(() => {
+ UserFactory.create(1)
+ NamespaceFactory.create(1)
+ const lists = ListFactory.create(1, {
+ title: 'First List'
+ })
+ setLists(lists)
+ TaskFactory.truncate()
+ })
+}
\ No newline at end of file
diff --git a/cypress/integration/task/task.spec.js b/cypress/integration/task/task.spec.js
index 1b85e992..62343c91 100644
--- a/cypress/integration/task/task.spec.js
+++ b/cypress/integration/task/task.spec.js
@@ -116,6 +116,7 @@ describe('Task', () => {
.should('be.visible')
.should('contain', 'Done')
cy.get('.task-view .action-buttons p.created')
+ .scrollIntoView()
.should('be.visible')
.should('contain', 'Done')
})
@@ -372,13 +373,13 @@ describe('Task', () => {
cy.visit(`/tasks/${tasks[0].id}`)
- cy.get('.task-view .details.labels-list .multiselect .input-wrapper')
+ cy.getSettled('.task-view .details.labels-list .multiselect .input-wrapper')
.should('be.visible')
.should('contain', labels[0].title)
- cy.get('.task-view .details.labels-list .multiselect .input-wrapper')
+ cy.getSettled('.task-view .details.labels-list .multiselect .input-wrapper')
.children()
.first()
- .get('a.delete')
+ .get('[data-cy="taskDetail.removeLabel"]')
.click()
cy.get('.global-notification')
diff --git a/package.json b/package.json
index 9b2eeebc..9adfbbbe 100644
--- a/package.json
+++ b/package.json
@@ -129,7 +129,7 @@
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
- "ecmaVersion": 2021
+ "ecmaVersion": 2022
},
"ignorePatterns": [
"*.test.*",
diff --git a/src/components/home/contentAuth.vue b/src/components/home/contentAuth.vue
index 06ab48e2..25fd01e7 100644
--- a/src/components/home/contentAuth.vue
+++ b/src/components/home/contentAuth.vue
@@ -1,8 +1,12 @@
-
+
-
+
-
-
-
+
+
-
+
+
+
+
+
+
+
diff --git a/src/components/list/partials/filter-popup.vue b/src/components/list/partials/filter-popup.vue
index efbfcbe3..d4caf9b4 100644
--- a/src/components/list/partials/filter-popup.vue
+++ b/src/components/list/partials/filter-popup.vue
@@ -29,9 +29,10 @@
diff --git a/src/components/misc/keyboard-shortcuts/shortcuts.js b/src/components/misc/keyboard-shortcuts/shortcuts.ts
similarity index 78%
rename from src/components/misc/keyboard-shortcuts/shortcuts.js
rename to src/components/misc/keyboard-shortcuts/shortcuts.ts
index bcc5014b..f68b7c35 100644
--- a/src/components/misc/keyboard-shortcuts/shortcuts.js
+++ b/src/components/misc/keyboard-shortcuts/shortcuts.ts
@@ -1,11 +1,24 @@
+import {RouteLocation} from 'vue-router'
+
import {isAppleDevice} from '@/helpers/isAppleDevice'
const ctrl = isAppleDevice() ? '⌘' : 'ctrl'
-export const KEYBOARD_SHORTCUTS = [
+interface Shortcut {
+ title: string
+ keys: string[]
+ combination?: 'then'
+}
+
+interface ShortcutGroup {
+ title: string
+ available?: (route: RouteLocation) => boolean
+ shortcuts: Shortcut[]
+}
+
+export const KEYBOARD_SHORTCUTS : ShortcutGroup[] = [
{
title: 'keyboardShortcuts.general',
- available: () => null,
shortcuts: [
{
title: 'keyboardShortcuts.toggleMenu',
@@ -29,7 +42,7 @@ export const KEYBOARD_SHORTCUTS = [
},
{
title: 'keyboardShortcuts.list.title',
- available: (route) => route.name.startsWith('list.'),
+ available: (route) => (route.name as string)?.startsWith('list.'),
shortcuts: [
{
title: 'keyboardShortcuts.list.switchToListView',
@@ -55,13 +68,7 @@ export const KEYBOARD_SHORTCUTS = [
},
{
title: 'keyboardShortcuts.task.title',
- available: (route) => [
- 'task.detail',
- 'task.list.detail',
- 'task.gantt.detail',
- 'task.kanban.detail',
- 'task.detail',
- ].includes(route.name),
+ available: (route) => route.name === 'task.detail',
shortcuts: [
{
title: 'keyboardShortcuts.task.assign',
diff --git a/src/components/misc/ready.vue b/src/components/misc/ready.vue
index 07ee0ffa..8fd3f4fd 100644
--- a/src/components/misc/ready.vue
+++ b/src/components/misc/ready.vue
@@ -52,9 +52,15 @@ import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
import {ERROR_NO_API_URL} from '@/helpers/checkAndSetApiUrl'
import {useOnline} from '@/composables/useOnline'
+import {useRouter, useRoute} from 'vue-router'
+import {getAuthForRoute} from '@/router'
+
+const router = useRouter()
+const route = useRoute()
+
const store = useStore()
-const ready = computed(() => store.state.vikunjaReady)
+const ready = ref(false)
const online = useOnline()
const error = ref('')
@@ -63,7 +69,12 @@ const showLoading = computed(() => !ready.value && error.value === '')
async function load() {
try {
await store.dispatch('loadApp')
- } catch(e: any) {
+ const redirectTo = getAuthForRoute(route)
+ if (typeof redirectTo !== 'undefined') {
+ await router.push(redirectTo)
+ }
+ ready.value = true
+ } catch (e: any) {
error.value = e
}
}
diff --git a/src/components/misc/subscription.vue b/src/components/misc/subscription.vue
index c17e9373..dbda373e 100644
--- a/src/components/misc/subscription.vue
+++ b/src/components/misc/subscription.vue
@@ -1,55 +1,47 @@
{{ buttonText }}
-
{{ buttonText }}
-
+
+
+
\ No newline at end of file
diff --git a/src/components/tasks/edit-task.vue b/src/components/tasks/edit-task.vue
index e4ada125..718304c6 100644
--- a/src/components/tasks/edit-task.vue
+++ b/src/components/tasks/edit-task.vue
@@ -67,7 +67,7 @@
{{ $t('task.openDetail') }}
@@ -97,6 +97,15 @@ export default {
taskEditTask: TaskModel,
}
},
+ computed: {
+ taskDetailRoute() {
+ return {
+ name: 'task.detail',
+ params: { id: this.taskEditTask.id },
+ state: { backdropView: this.$router.currentRoute.value.fullPath },
+ }
+ },
+ },
components: {
ColorPicker,
Reminders,
diff --git a/src/components/tasks/mixins/taskList.js b/src/components/tasks/mixins/taskList.js
deleted file mode 100644
index a9b2e587..00000000
--- a/src/components/tasks/mixins/taskList.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import TaskCollectionService from '@/services/taskCollection'
-
-// FIXME: merge with DEFAULT_PARAMS in filters.vue
-export const getDefaultParams = () => ({
- sort_by: ['position', 'id'],
- order_by: ['asc', 'desc'],
- filter_by: ['done'],
- filter_value: ['false'],
- filter_comparator: ['equals'],
- filter_concat: 'and',
-})
-
-/**
- * This mixin provides a base set of methods and properties to get tasks on a list.
- */
-export default {
- data() {
- return {
- taskCollectionService: new TaskCollectionService(),
- tasks: [],
-
- currentPage: 0,
-
- loadedList: null,
-
- searchTerm: '',
-
- showTaskFilter: false,
- params: {...getDefaultParams()},
- }
- },
- watch: {
- // Only listen for query path changes
- '$route.query': {
- handler: 'loadTasksForPage',
- immediate: true,
- },
- '$route.path': 'loadTasksOnSavedFilter',
- },
- methods: {
- async loadTasks(
- page,
- search = '',
- params = null,
- forceLoading = false,
- ) {
- // Because this function is triggered every time on topNavigation, we're putting a condition here to only load it when we actually want to show tasks
- // FIXME: This is a bit hacky -> Cleanup.
- if (
- this.$route.name !== 'list.list' &&
- this.$route.name !== 'list.table' &&
- !forceLoading
- ) {
- return
- }
-
- if (params === null) {
- params = this.params
- }
-
- if (search !== '') {
- params.s = search
- }
-
- const list = {listId: parseInt(this.$route.params.listId)}
-
- const currentList = {
- id: list.listId,
- params,
- search,
- page,
- }
- if (JSON.stringify(currentList) === JSON.stringify(this.loadedList) && !forceLoading) {
- return
- }
-
- this.tasks = []
- this.tasks = await this.taskCollectionService.getAll(list, params, page)
- this.currentPage = page
- this.loadedList = JSON.parse(JSON.stringify(currentList))
- },
-
- loadTasksForPage(e) {
- // The page parameter can be undefined, in the case where the user loads a new list from the side bar menu
- let page = Number(e.page)
- if (typeof e.page === 'undefined') {
- page = 1
- }
- let search = e.search
- if (typeof e.search === 'undefined') {
- search = ''
- }
- this.initTasks(page, search)
- },
- loadTasksOnSavedFilter() {
- if (typeof this.$route.params.listId !== 'undefined' && parseInt(this.$route.params.listId) < 0) {
- this.loadTasks(1, '', null, true)
- }
- },
- },
-}
\ No newline at end of file
diff --git a/src/components/tasks/partials/attachments.vue b/src/components/tasks/partials/attachments.vue
index 16bc9328..7d507e5e 100644
--- a/src/components/tasks/partials/attachments.vue
+++ b/src/components/tasks/partials/attachments.vue
@@ -400,4 +400,6 @@ export default {
transform: translate3d(0, -4px, 0);
}
}
+
+@include modal-transition();
\ No newline at end of file
diff --git a/src/components/tasks/partials/comments.vue b/src/components/tasks/partials/comments.vue
index d5db45c3..23105771 100644
--- a/src/components/tasks/partials/comments.vue
+++ b/src/components/tasks/partials/comments.vue
@@ -162,7 +162,7 @@ import {mapState} from 'vuex'
export default {
name: 'comments',
components: {
- editor: AsyncEditor,
+ Editor: AsyncEditor,
},
props: {
taskId: {
@@ -339,4 +339,6 @@ export default {
.media-content {
width: calc(100% - 48px - 2rem);
}
+
+@include modal-transition();
\ No newline at end of file
diff --git a/src/components/tasks/partials/description.vue b/src/components/tasks/partials/description.vue
index 28fd5a88..07d136fd 100644
--- a/src/components/tasks/partials/description.vue
+++ b/src/components/tasks/partials/description.vue
@@ -38,7 +38,7 @@ import {mapState} from 'vuex'
export default {
name: 'description',
components: {
- editor: AsyncEditor,
+ Editor: AsyncEditor,
},
data() {
return {
diff --git a/src/components/tasks/partials/editLabels.vue b/src/components/tasks/partials/editLabels.vue
index 4047192d..f2d01934 100644
--- a/src/components/tasks/partials/editLabels.vue
+++ b/src/components/tasks/partials/editLabels.vue
@@ -19,7 +19,7 @@
:style="{'background': props.item.hexColor, 'color': props.item.textColor}"
class="tag">
{{ props.item.title }}
-
+
@@ -114,23 +114,17 @@ export default {
},
async removeLabel(label) {
- const removeFromState = () => {
- for (const l in this.labels) {
- if (this.labels[l].id === label.id) {
- this.labels.splice(l, 1)
- }
+ if (!this.taskId === 0) {
+ await this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
+ }
+
+ for (const l in this.labels) {
+ if (this.labels[l].id === label.id) {
+ this.labels.splice(l, 1)
}
- this.$emit('update:modelValue', this.labels)
- this.$emit('change', this.labels)
}
-
- if (this.taskId === 0) {
- removeFromState()
- return
- }
-
- await this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
- removeFromState()
+ this.$emit('update:modelValue', this.labels)
+ this.$emit('change', this.labels)
this.$message.success({message: this.$t('task.label.removeSuccess')})
},
diff --git a/src/components/tasks/partials/kanban-card.vue b/src/components/tasks/partials/kanban-card.vue
index e901ac84..d0d4501d 100644
--- a/src/components/tasks/partials/kanban-card.vue
+++ b/src/components/tasks/partials/kanban-card.vue
@@ -7,8 +7,8 @@
'has-light-text': !colorIsDark(task.hexColor) && task.hexColor !== `#${task.defaultColor}` && task.hexColor !== task.defaultColor,
}"
:style="{'background-color': task.hexColor !== '#' && task.hexColor !== `#${task.defaultColor}` ? task.hexColor : false}"
+ @click.exact="openTaskDetail()"
@click.ctrl="() => toggleTaskDone(task)"
- @click.exact="() => $router.push({ name: 'task.kanban.detail', params: { id: task.id } })"
@click.meta="() => toggleTaskDone(task)"
>
@@ -115,6 +115,13 @@ export default {
this.loadingInternal = false
}
},
+ openTaskDetail() {
+ this.$router.push({
+ name: 'task.detail',
+ params: { id: this.task.id },
+ state: { backdropView: this.$router.currentRoute.value.fullPath },
+ })
+ },
},
}
diff --git a/src/components/tasks/partials/relatedTasks.vue b/src/components/tasks/partials/relatedTasks.vue
index 9f65455b..80c04e04 100644
--- a/src/components/tasks/partials/relatedTasks.vue
+++ b/src/components/tasks/partials/relatedTasks.vue
@@ -365,4 +365,6 @@ export default {
:deep(.multiselect .search-results button) {
padding: 0.5rem;
}
+
+@include modal-transition();
\ No newline at end of file
diff --git a/src/components/tasks/partials/singleTaskInList.vue b/src/components/tasks/partials/singleTaskInList.vue
index 7eb07fb6..85293dec 100644
--- a/src/components/tasks/partials/singleTaskInList.vue
+++ b/src/components/tasks/partials/singleTaskInList.vue
@@ -8,7 +8,7 @@
>
@@ -129,10 +129,6 @@ export default {
type: Boolean,
default: false,
},
- taskDetailRoute: {
- type: String,
- default: 'task.list.detail',
- },
showList: {
type: Boolean,
default: false,
@@ -170,6 +166,14 @@ export default {
title: '',
} : this.$store.state.currentList
},
+ taskDetailRoute() {
+ return {
+ name: 'task.detail',
+ params: { id: this.task.id },
+ // TODO: re-enable opening task detail in modal
+ // state: { backdropView: this.$router.currentRoute.value.fullPath },
+ }
+ },
},
methods: {
async markAsDone(checked) {
diff --git a/src/components/tasks/partials/sort.vue b/src/components/tasks/partials/sort.vue
index c3581ba5..4eeb5e9f 100644
--- a/src/components/tasks/partials/sort.vue
+++ b/src/components/tasks/partials/sort.vue
@@ -1,20 +1,21 @@
-
+
-
+
-
+
-
diff --git a/src/composables/taskList.js b/src/composables/taskList.js
new file mode 100644
index 00000000..e80c5cbe
--- /dev/null
+++ b/src/composables/taskList.js
@@ -0,0 +1,111 @@
+import { ref, shallowReactive, watch, computed } from 'vue'
+import {useRoute} from 'vue-router'
+
+import TaskCollectionService from '@/services/taskCollection'
+
+// FIXME: merge with DEFAULT_PARAMS in filters.vue
+export const getDefaultParams = () => ({
+ sort_by: ['position', 'id'],
+ order_by: ['asc', 'desc'],
+ filter_by: ['done'],
+ filter_value: ['false'],
+ filter_comparator: ['equals'],
+ filter_concat: 'and',
+})
+
+const SORT_BY_DEFAULT = {
+ id: 'desc',
+}
+
+/**
+ * This mixin provides a base set of methods and properties to get tasks on a list.
+ */
+export function useTaskList(listId) {
+ const params = ref({...getDefaultParams()})
+
+ const search = ref('')
+ const page = ref(1)
+
+ const sortBy = ref({ ...SORT_BY_DEFAULT })
+
+
+ // This makes sure an id sort order is always sorted last.
+ // When tasks would be sorted first by id and then by whatever else was specified, the id sort takes
+ // precedence over everything else, making any other sort columns pretty useless.
+ function formatSortOrder(params) {
+ let hasIdFilter = false
+ const sortKeys = Object.keys(sortBy.value)
+ for (const s of sortKeys) {
+ if (s === 'id') {
+ sortKeys.splice(s, 1)
+ hasIdFilter = true
+ break
+ }
+ }
+ if (hasIdFilter) {
+ sortKeys.push('id')
+ }
+ params.sort_by = sortKeys
+ params.order_by = sortKeys.map(s => sortBy.value[s])
+
+ return params
+ }
+
+ const getAllTasksParams = computed(() => {
+ let loadParams = {...params.value}
+
+ if (search.value !== '') {
+ loadParams.s = search.value
+ }
+
+ loadParams = formatSortOrder(loadParams)
+
+ return [
+ {listId: listId.value},
+ loadParams,
+ page.value || 1,
+ ]
+ })
+
+ const taskCollectionService = shallowReactive(new TaskCollectionService())
+ const loading = computed(() => taskCollectionService.loading)
+ const totalPages = computed(() => taskCollectionService.totalPages)
+
+ const tasks = ref([])
+ async function loadTasks() {
+ tasks.value = await taskCollectionService.getAll(...getAllTasksParams.value)
+ return tasks.value
+ }
+
+ const route = useRoute()
+ watch(() => route.query, (query) => {
+ const { page: pageQueryValue, search: searchQuery } = query
+ if (searchQuery !== undefined) {
+ search.value = searchQuery
+ }
+ if (pageQueryValue !== undefined) {
+ page.value = parseInt(pageQueryValue)
+ }
+
+ }, { immediate: true })
+
+
+ // Only listen for query path changes
+ watch(() => JSON.stringify(getAllTasksParams.value), (newParams, oldParams) => {
+ if (oldParams === newParams) {
+ return
+ }
+
+ loadTasks()
+ }, { immediate: true })
+
+ return {
+ tasks,
+ loading,
+ totalPages,
+ currentPage: page,
+ loadTasks,
+ searchTerm: search,
+ params,
+ }
+}
\ No newline at end of file
diff --git a/src/composables/useTitle.ts b/src/composables/useTitle.ts
index e3544829..84aaa177 100644
--- a/src/composables/useTitle.ts
+++ b/src/composables/useTitle.ts
@@ -1,9 +1,9 @@
import { computed, watchEffect } from 'vue'
import { setTitle } from '@/helpers/setTitle'
-import { ComputedGetter, ComputedRef } from '@vue/reactivity'
+import { ComputedGetter } from '@vue/reactivity'
-export function useTitle(titleGetter: ComputedGetter) : ComputedRef {
+export function useTitle(titleGetter: ComputedGetter) {
const titleRef = computed(titleGetter)
watchEffect(() => setTitle(titleRef.value))
diff --git a/src/helpers/auth.ts b/src/helpers/auth.ts
index b073a8ca..81cd5580 100644
--- a/src/helpers/auth.ts
+++ b/src/helpers/auth.ts
@@ -53,6 +53,7 @@ export async function refreshToken(persist: boolean): Promise {
return response
} catch(e) {
+ // @ts-ignore
throw new Error('Error renewing token: ', { cause: e })
}
}
diff --git a/src/helpers/saveListView.js b/src/helpers/saveListView.js
index b7f735e1..96be6342 100644
--- a/src/helpers/saveListView.js
+++ b/src/helpers/saveListView.js
@@ -1,3 +1,5 @@
+// Save the current list view to local storage
+// We use local storage and not vuex here to make it persistent across reloads.
export const saveListView = (listId, routeName) => {
if (routeName.includes('settings.')) {
return
diff --git a/src/helpers/setTitle.js b/src/helpers/setTitle.ts
similarity index 65%
rename from src/helpers/setTitle.js
rename to src/helpers/setTitle.ts
index a2f31a45..d7e6b5dd 100644
--- a/src/helpers/setTitle.js
+++ b/src/helpers/setTitle.ts
@@ -1,4 +1,4 @@
-export function setTitle(title) {
+export function setTitle(title : undefined | string) {
document.title = (typeof title === 'undefined' || title === '')
? 'Vikunja'
: `${title} | Vikunja`
diff --git a/src/models/task.js b/src/models/task.js
index c0334237..63839689 100644
--- a/src/models/task.js
+++ b/src/models/task.js
@@ -10,9 +10,6 @@ import {parseDateOrNull} from '@/helpers/parseDateOrNull'
const SUPPORTS_TRIGGERED_NOTIFICATION = 'Notification' in window && 'showTrigger' in Notification.prototype
export default class TaskModel extends AbstractModel {
-
- defaultColor = '198CFF'
-
constructor(data) {
super(data)
diff --git a/src/router/index.ts b/src/router/index.ts
index 7b28a509..5a2d8f97 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -2,6 +2,8 @@ import { createRouter, createWebHistory, RouteLocation } from 'vue-router'
import {saveLastVisited} from '@/helpers/saveLastVisited'
import {store} from '@/store'
+import {saveListView, getListView} from '@/helpers/saveListView'
+
import HomeComponent from '../views/Home.vue'
import NotFoundComponent from '../views/404.vue'
import About from '../views/About.vue'
@@ -13,9 +15,8 @@ import DataExportDownload from '../views/user/DataExportDownload.vue'
// Tasks
import ShowTasksInRangeComponent from '../views/tasks/ShowTasksInRange.vue'
import LinkShareAuthComponent from '../views/sharing/LinkSharingAuth.vue'
-import TaskDetailViewModal from '../views/tasks/TaskDetailViewModal.vue'
-import TaskDetailView from '../views/tasks/TaskDetailView.vue'
import ListNamespaces from '../views/namespaces/ListNamespaces.vue'
+import TaskDetailView from '../views/tasks/TaskDetailView.vue'
// Team Handling
import ListTeamsComponent from '../views/teams/ListTeams.vue'
// Label Handling
@@ -25,11 +26,11 @@ import NewLabelComponent from '../views/labels/NewLabel.vue'
import MigrationComponent from '../views/migrator/Migrate.vue'
import MigrateServiceComponent from '../views/migrator/MigrateService.vue'
// List Views
-import ShowListComponent from '../views/list/ShowList.vue'
-import Kanban from '../views/list/views/Kanban.vue'
-import List from '../views/list/views/List.vue'
-import Gantt from '../views/list/views/Gantt.vue'
-import Table from '../views/list/views/Table.vue'
+import ListList from '../views/list/ListList.vue'
+import ListGantt from '../views/list/ListGantt.vue'
+import ListTable from '../views/list/ListTable.vue'
+import ListKanban from '../views/list/ListKanban.vue'
+
// List Settings
import ListSettingEdit from '../views/list/settings/edit.vue'
import ListSettingBackground from '../views/list/settings/background.vue'
@@ -80,7 +81,7 @@ const router = createRouter({
// Scroll to anchor should still work
if (to.hash) {
- return {el: document.getElementById(to.hash.slice(1))}
+ return {el: to.hash}
}
// Otherwise just scroll to the top
@@ -201,320 +202,170 @@ const router = createRouter({
{
path: '/namespaces/new',
name: 'namespace.create',
- components: {
- popup: NewNamespaceComponent,
- },
- },
- {
- path: '/namespaces/:id/list',
- name: 'list.create',
- components: {
- popup: NewListComponent,
+ component: NewNamespaceComponent,
+ meta: {
+ showAsModal: true,
},
},
{
path: '/namespaces/:id/settings/edit',
name: 'namespace.settings.edit',
- components: {
- popup: NamespaceSettingEdit,
+ component: NamespaceSettingEdit,
+ meta: {
+ showAsModal: true,
},
},
{
- path: '/namespaces/:id/settings/share',
+ path: '/namespaces/:namespaceId/settings/share',
name: 'namespace.settings.share',
- components: {
- popup: NamespaceSettingShare,
+ component: NamespaceSettingShare,
+ meta: {
+ showAsModal: true,
},
},
{
path: '/namespaces/:id/settings/archive',
name: 'namespace.settings.archive',
- components: {
- popup: NamespaceSettingArchive,
+ component: NamespaceSettingArchive,
+ meta: {
+ showAsModal: true,
},
},
{
path: '/namespaces/:id/settings/delete',
name: 'namespace.settings.delete',
- components: {
- popup: NamespaceSettingDelete,
+ component: NamespaceSettingDelete,
+ meta: {
+ showAsModal: true,
},
},
{
path: '/tasks/:id',
name: 'task.detail',
component: TaskDetailView,
+ props: route => ({ taskId: parseInt(route.params.id as string) }),
},
{
path: '/tasks/by/upcoming',
name: 'tasks.range',
component: ShowTasksInRangeComponent,
},
+ {
+ path: '/lists/new/:namespaceId/',
+ name: 'list.create',
+ component: NewListComponent,
+ meta: {
+ showAsModal: true,
+ },
+ },
{
path: '/lists/:listId/settings/edit',
name: 'list.settings.edit',
- components: {
- popup: ListSettingEdit,
+ component: ListSettingEdit,
+ meta: {
+ showAsModal: true,
},
},
{
path: '/lists/:listId/settings/background',
name: 'list.settings.background',
- components: {
- popup: ListSettingBackground,
+ component: ListSettingBackground,
+ meta: {
+ showAsModal: true,
},
},
{
path: '/lists/:listId/settings/duplicate',
name: 'list.settings.duplicate',
- components: {
- popup: ListSettingDuplicate,
+ component: ListSettingDuplicate,
+ meta: {
+ showAsModal: true,
},
},
{
path: '/lists/:listId/settings/share',
name: 'list.settings.share',
- components: {
- popup: ListSettingShare,
+ component: ListSettingShare,
+ meta: {
+ showAsModal: true,
},
},
{
path: '/lists/:listId/settings/delete',
name: 'list.settings.delete',
- components: {
- popup: ListSettingDelete,
+ component: ListSettingDelete,
+ meta: {
+ showAsModal: true,
},
},
{
path: '/lists/:listId/settings/archive',
name: 'list.settings.archive',
- components: {
- popup: ListSettingArchive,
+ component: ListSettingArchive,
+ meta: {
+ showAsModal: true,
},
},
{
path: '/lists/:listId/settings/edit',
name: 'filter.settings.edit',
- components: {
- popup: FilterEdit,
+ component: FilterEdit,
+ meta: {
+ showAsModal: true,
},
},
{
path: '/lists/:listId/settings/delete',
name: 'filter.settings.delete',
- components: {
- popup: FilterDelete,
+ component: FilterDelete,
+ meta: {
+ showAsModal: true,
},
},
{
path: '/lists/:listId',
name: 'list.index',
- component: ShowListComponent,
- children: [
- {
- path: '/lists/:listId/list',
- name: 'list.list',
- component: List,
- children: [
- {
- path: '/tasks/:id',
- name: 'task.list.detail',
- component: TaskDetailViewModal,
- },
- {
- path: '/lists/:listId/settings/edit',
- name: 'list.list.settings.edit',
- component: ListSettingEdit,
- },
- {
- path: '/lists/:listId/settings/background',
- name: 'list.list.settings.background',
- component: ListSettingBackground,
- },
- {
- path: '/lists/:listId/settings/duplicate',
- name: 'list.list.settings.duplicate',
- component: ListSettingDuplicate,
- },
- {
- path: '/lists/:listId/settings/share',
- name: 'list.list.settings.share',
- component: ListSettingShare,
- },
- {
- path: '/lists/:listId/settings/delete',
- name: 'list.list.settings.delete',
- component: ListSettingDelete,
- },
- {
- path: '/lists/:listId/settings/archive',
- name: 'list.list.settings.archive',
- component: ListSettingArchive,
- },
- {
- path: '/lists/:listId/settings/edit',
- name: 'filter.list.settings.edit',
- component: FilterEdit,
- },
- {
- path: '/lists/:listId/settings/delete',
- name: 'filter.list.settings.delete',
- component: FilterDelete,
- },
- ],
- },
- {
- path: '/lists/:listId/gantt',
- name: 'list.gantt',
- component: Gantt,
- children: [
- {
- path: '/tasks/:id',
- name: 'task.gantt.detail',
- component: TaskDetailViewModal,
- },
- {
- path: '/lists/:listId/settings/edit',
- name: 'list.gantt.settings.edit',
- component: ListSettingEdit,
- },
- {
- path: '/lists/:listId/settings/background',
- name: 'list.gantt.settings.background',
- component: ListSettingBackground,
- },
- {
- path: '/lists/:listId/settings/duplicate',
- name: 'list.gantt.settings.duplicate',
- component: ListSettingDuplicate,
- },
- {
- path: '/lists/:listId/settings/share',
- name: 'list.gantt.settings.share',
- component: ListSettingShare,
- },
- {
- path: '/lists/:listId/settings/delete',
- name: 'list.gantt.settings.delete',
- component: ListSettingDelete,
- },
- {
- path: '/lists/:listId/settings/archive',
- name: 'list.gantt.settings.archive',
- component: ListSettingArchive,
- },
- {
- path: '/lists/:listId/settings/edit',
- name: 'filter.gantt.settings.edit',
- component: FilterEdit,
- },
- {
- path: '/lists/:listId/settings/delete',
- name: 'filter.gantt.settings.delete',
- component: FilterDelete,
- },
- ],
- },
- {
- path: '/lists/:listId/table',
- name: 'list.table',
- component: Table,
- children: [
- {
- path: '/lists/:listId/settings/edit',
- name: 'list.table.settings.edit',
- component: ListSettingEdit,
- },
- {
- path: '/lists/:listId/settings/background',
- name: 'list.table.settings.background',
- component: ListSettingBackground,
- },
- {
- path: '/lists/:listId/settings/duplicate',
- name: 'list.table.settings.duplicate',
- component: ListSettingDuplicate,
- },
- {
- path: '/lists/:listId/settings/share',
- name: 'list.table.settings.share',
- component: ListSettingShare,
- },
- {
- path: '/lists/:listId/settings/delete',
- name: 'list.table.settings.delete',
- component: ListSettingDelete,
- },
- {
- path: '/lists/:listId/settings/archive',
- name: 'list.table.settings.archive',
- component: ListSettingArchive,
- },
- {
- path: '/lists/:listId/settings/edit',
- name: 'filter.table.settings.edit',
- component: FilterEdit,
- },
- {
- path: '/lists/:listId/settings/delete',
- name: 'filter.table.settings.delete',
- component: FilterDelete,
- },
- ],
- },
- {
- path: '/lists/:listId/kanban',
- name: 'list.kanban',
- component: Kanban,
- children: [
- {
- path: '/tasks/:id',
- name: 'task.kanban.detail',
- component: TaskDetailViewModal,
- },
- {
- path: '/lists/:listId/settings/edit',
- name: 'list.kanban.settings.edit',
- component: ListSettingEdit,
- },
- {
- path: '/lists/:listId/settings/background',
- name: 'list.kanban.settings.background',
- component: ListSettingBackground,
- },
- {
- path: '/lists/:listId/settings/duplicate',
- name: 'list.kanban.settings.duplicate',
- component: ListSettingDuplicate,
- },
- {
- path: '/lists/:listId/settings/share',
- name: 'list.kanban.settings.share',
- component: ListSettingShare,
- },
- {
- path: '/lists/:listId/settings/delete',
- name: 'list.kanban.settings.delete',
- component: ListSettingDelete,
- },
- {
- path: '/lists/:listId/settings/archive',
- name: 'list.kanban.settings.archive',
- component: ListSettingArchive,
- },
- {
- path: '/lists/:listId/settings/edit',
- name: 'filter.kanban.settings.edit',
- component: FilterEdit,
- },
- {
- path: '/lists/:listId/settings/delete',
- name: 'filter.kanban.settings.delete',
- component: FilterDelete,
- },
- ],
- },
- ],
+ redirect(to) {
+ // Redirect the user to list view by default
+
+ const savedListView = getListView(to.params.listId)
+ console.debug('Replaced list view with', savedListView)
+
+ return {
+ name: router.hasRoute(savedListView)
+ ? savedListView
+ : 'list.list',
+ params: {listId: to.params.listId},
+ }
+ },
+ },
+ {
+ path: '/lists/:listId/list',
+ name: 'list.list',
+ component: ListList,
+ beforeEnter: (to) => saveListView(to.params.listId, to.name),
+ props: route => ({ listId: parseInt(route.params.listId as string) }),
+ },
+ {
+ path: '/lists/:listId/gantt',
+ name: 'list.gantt',
+ component: ListGantt,
+ beforeEnter: (to) => saveListView(to.params.listId, to.name),
+ props: route => ({ listId: parseInt(route.params.listId as string) }),
+ },
+ {
+ path: '/lists/:listId/table',
+ name: 'list.table',
+ component: ListTable,
+ beforeEnter: (to) => saveListView(to.params.listId, to.name),
+ props: route => ({ listId: parseInt(route.params.listId as string) }),
+ },
+ {
+ path: '/lists/:listId/kanban',
+ name: 'list.kanban',
+ component: ListKanban,
+ beforeEnter: (to) => saveListView(to.params.listId, to.name),
+ props: route => ({ listId: parseInt(route.params.listId as string) }),
},
{
path: '/teams',
@@ -524,8 +375,9 @@ const router = createRouter({
{
path: '/teams/new',
name: 'teams.create',
- components: {
- popup: NewTeamComponent,
+ component: NewTeamComponent,
+ meta: {
+ showAsModal: true,
},
},
{
@@ -541,8 +393,9 @@ const router = createRouter({
{
path: '/labels/new',
name: 'labels.create',
- components: {
- popup: NewLabelComponent,
+ component: NewLabelComponent,
+ meta: {
+ showAsModal: true,
},
},
{
@@ -558,8 +411,9 @@ const router = createRouter({
{
path: '/filters/new',
name: 'filters.create',
- components: {
- popup: FilterNew,
+ component: FilterNew,
+ meta: {
+ showAsModal: true,
},
},
{
@@ -575,11 +429,7 @@ const router = createRouter({
],
})
-router.beforeEach((to) => {
- return checkAuth(to)
-})
-
-function checkAuth(route: RouteLocation) {
+export function getAuthForRoute(route: RouteLocation) {
const authUser = store.getters['auth/authUser']
const authLinkShare = store.getters['auth/authLinkShare']
diff --git a/src/store/index.js b/src/store/index.js
index 37c74ae8..0833e3f6 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -18,6 +18,8 @@ import lists from './modules/lists'
import attachments from './modules/attachments'
import labels from './modules/labels'
+import ListModel from '@/models/list'
+
import ListService from '../services/list'
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
@@ -37,13 +39,15 @@ export const store = createStore({
loading: false,
loadingModule: null,
// This is used to highlight the current list in menu for all list related views
- currentList: {id: 0},
+ currentList: new ListModel({
+ id: 0,
+ isArchived: false,
+ }),
background: '',
hasTasks: false,
menuActive: true,
keyboardShortcutsActive: false,
quickActionsActive: false,
- vikunjaReady: false,
},
mutations: {
[LOADING](state, loading) {
@@ -79,9 +83,6 @@ export const store = createStore({
[BACKGROUND](state, background) {
state.background = background
},
- vikunjaReady(state, ready) {
- state.vikunjaReady = ready
- },
},
actions: {
async [CURRENT_LIST]({state, commit}, currentList) {
@@ -136,10 +137,9 @@ export const store = createStore({
commit(CURRENT_LIST, currentList)
},
- async loadApp({commit, dispatch}) {
+ async loadApp({dispatch}) {
await checkAndSetApiUrl(window.API_URL)
await dispatch('auth/checkAuth')
- commit('vikunjaReady', true)
},
},
})
diff --git a/src/store/modules/auth.js b/src/store/modules/auth.js
index c5dd1032..e71bbe73 100644
--- a/src/store/modules/auth.js
+++ b/src/store/modules/auth.js
@@ -1,12 +1,12 @@
import {HTTPFactory} from '@/http-common'
-import {getCurrentLanguage, saveLanguage} from '@/i18n'
+import {i18n, getCurrentLanguage, saveLanguage} from '@/i18n'
import {LOADING} from '../mutation-types'
import UserModel from '@/models/user'
import UserSettingsService from '@/services/userSettings'
import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
import {setLoading} from '@/store/helper'
-import {i18n} from '@/i18n'
import {success} from '@/message'
+import {redirectToProvider} from '@/helpers/redirectToProvider'
const AUTH_TYPES = {
'UNKNOWN': 0,
@@ -201,7 +201,19 @@ export default {
ctx.commit('authenticated', authenticated)
if (!authenticated) {
ctx.commit('info', null)
- ctx.dispatch('config/redirectToProviderIfNothingElseIsEnabled', null, {root: true})
+ ctx.dispatch('redirectToProviderIfNothingElseIsEnabled')
+ }
+ },
+
+ redirectToProviderIfNothingElseIsEnabled({rootState}) {
+ const {auth} = rootState.config
+ if (
+ auth.local.enabled === false &&
+ auth.openidConnect.enabled &&
+ auth.openidConnect.providers?.length === 1 &&
+ window.location.pathname.startsWith('/login') // Kinda hacky, but prevents an endless loop.
+ ) {
+ redirectToProvider(auth.openidConnect.providers[0], auth.openidConnect.redirectUrl)
}
},
diff --git a/src/store/modules/config.js b/src/store/modules/config.js
index 82c2b20c..dfe1e01f 100644
--- a/src/store/modules/config.js
+++ b/src/store/modules/config.js
@@ -1,7 +1,6 @@
import {CONFIG} from '../mutation-types'
import {HTTPFactory} from '@/http-common'
import {objectToCamelCase} from '@/helpers/case'
-import {redirectToProvider} from '../../helpers/redirectToProvider'
import {parseURL} from 'ufo'
export default {
@@ -75,16 +74,5 @@ export default {
ctx.commit(CONFIG, info)
return info
},
-
- redirectToProviderIfNothingElseIsEnabled(ctx) {
- if (ctx.state.auth.local.enabled === false &&
- ctx.state.auth.openidConnect.enabled &&
- ctx.state.auth.openidConnect.providers &&
- ctx.state.auth.openidConnect.providers.length === 1 &&
- window.location.pathname.startsWith('/login') // Kinda hacky, but prevents an endless loop.
- ) {
- redirectToProvider(ctx.state.auth.openidConnect.providers[0])
- }
- },
},
}
\ No newline at end of file
diff --git a/src/styles/common-imports.scss b/src/styles/common-imports.scss
index 5ca35fc4..a9e163e3 100644
--- a/src/styles/common-imports.scss
+++ b/src/styles/common-imports.scss
@@ -16,6 +16,8 @@
// since $tablet is defined by bulma we can just define it after importing the utilities
$mobile: math.div($tablet, 2);
+@import "mixins";
+
$family-sans-serif: 'Open Sans', Helvetica, Arial, sans-serif;
$vikunja-font: 'Quicksand', sans-serif;
diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss
new file mode 100644
index 00000000..687355e7
--- /dev/null
+++ b/src/styles/mixins.scss
@@ -0,0 +1,12 @@
+/* Transitions */
+@mixin modal-transition() {
+ .modal-enter,
+ .modal-leave-active {
+ opacity: 0;
+ }
+
+ .modal-enter .modal-container,
+ .modal-leave-active .modal-container {
+ transform: scale(0.9);
+ }
+}
\ No newline at end of file
diff --git a/src/styles/theme/background.scss b/src/styles/theme/background.scss
index b03b04f4..d478aac4 100644
--- a/src/styles/theme/background.scss
+++ b/src/styles/theme/background.scss
@@ -14,7 +14,7 @@
.box,
.card,
.switch-view,
- .table-view .button,
+ .list-table .button,
.filter-container .button,
.search .button {
box-shadow: none;
diff --git a/src/types/shims-vue.d.ts b/src/types/shims-vue.d.ts
index 4f0571df..c04ff091 100644
--- a/src/types/shims-vue.d.ts
+++ b/src/types/shims-vue.d.ts
@@ -1,8 +1,11 @@
declare module 'vue' {
import { CompatVue } from '@vue/runtime-dom'
const Vue: CompatVue
- export default Vue
- export * from '@vue/runtime-dom'
+ export default Vue
+ export * from '@vue/runtime-dom'
+
+ const { configureCompat } = Vue
+ export { configureCompat }
}
// https://next.vuex.vuejs.org/guide/migrating-to-4-0-from-3-x.html#typescript-support
diff --git a/src/types/vue-flatpickr-component.d.ts b/src/types/vue-flatpickr-component.d.ts
new file mode 100644
index 00000000..2ac9cc87
--- /dev/null
+++ b/src/types/vue-flatpickr-component.d.ts
@@ -0,0 +1 @@
+declare module 'vue-flatpickr-component';
\ No newline at end of file
diff --git a/src/views/Home.vue b/src/views/Home.vue
index f3d2b3ec..14c160d4 100644
--- a/src/views/Home.vue
+++ b/src/views/Home.vue
@@ -23,7 +23,7 @@
{{ $t('home.list.newText') }}
diff --git a/src/views/list/views/Gantt.vue b/src/views/list/ListGantt.vue
similarity index 66%
rename from src/views/list/views/Gantt.vue
rename to src/views/list/ListGantt.vue
index b3628238..82b765dd 100644
--- a/src/views/list/views/Gantt.vue
+++ b/src/views/list/ListGantt.vue
@@ -1,6 +1,6 @@
-
-
+
+
{{ $t('list.gantt.showTasksWithoutDates') }}
@@ -44,65 +44,64 @@
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
-
\ No newline at end of file
diff --git a/src/views/list/views/List.vue b/src/views/list/ListList.vue
similarity index 79%
rename from src/views/list/views/List.vue
rename to src/views/list/ListList.vue
index d61a39e8..bb9e9478 100644
--- a/src/views/list/views/List.vue
+++ b/src/views/list/ListList.vue
@@ -1,8 +1,6 @@
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
\ No newline at end of file
diff --git a/src/views/list/ListWrapper.vue b/src/views/list/ListWrapper.vue
new file mode 100644
index 00000000..d816b90e
--- /dev/null
+++ b/src/views/list/ListWrapper.vue
@@ -0,0 +1,186 @@
+
+
+
+
+
+ {{ $t('list.list.title') }}
+
+
+ {{ $t('list.gantt.title') }}
+
+
+ {{ $t('list.table.title') }}
+
+
+ {{ $t('list.kanban.title') }}
+
+
+
+
+
+
+ {{ $t('list.archived') }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/views/list/NewList.vue b/src/views/list/NewList.vue
index fb48e42b..0317a88e 100644
--- a/src/views/list/NewList.vue
+++ b/src/views/list/NewList.vue
@@ -61,7 +61,7 @@ export default {
}
this.showError = false
- this.list.namespaceId = parseInt(this.$route.params.id)
+ this.list.namespaceId = parseInt(this.$route.params.namespaceId)
const list = await this.$store.dispatch('lists/createList', this.list)
this.$message.success({message: this.$t('list.create.createdSuccess') })
this.$router.push({
diff --git a/src/views/list/ShowList.vue b/src/views/list/ShowList.vue
deleted file mode 100644
index d0c1d20f..00000000
--- a/src/views/list/ShowList.vue
+++ /dev/null
@@ -1,211 +0,0 @@
-
-
-
-
-
- {{ $t('list.list.title') }}
-
-
- {{ $t('list.gantt.title') }}
-
-
- {{ $t('list.table.title') }}
-
-
- {{ $t('list.kanban.title') }}
-
-
-
-
-
- {{ $t('list.archived') }}
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/views/list/settings/share.vue b/src/views/list/settings/share.vue
index 4a9967f8..ca11b4e5 100644
--- a/src/views/list/settings/share.vue
+++ b/src/views/list/settings/share.vue
@@ -3,24 +3,38 @@
:title="$t('list.share.header')"
primary-label=""
>
-
-
+
+
+
+
-
+
-
+
+
diff --git a/src/views/list/views/Table.vue b/src/views/list/views/Table.vue
deleted file mode 100644
index b15b0a1b..00000000
--- a/src/views/list/views/Table.vue
+++ /dev/null
@@ -1,331 +0,0 @@
-
-
-
-
-
-
-
- {{ $t('list.table.columns') }}
-
-
-
-
- #
-
- {{ $t('task.attributes.done') }}
-
-
- {{ $t('task.attributes.title') }}
-
-
- {{ $t('task.attributes.priority') }}
-
-
- {{ $t('task.attributes.labels') }}
-
-
- {{ $t('task.attributes.assignees') }}
-
-
- {{ $t('task.attributes.dueDate') }}
-
-
- {{ $t('task.attributes.startDate') }}
-
-
- {{ $t('task.attributes.endDate') }}
-
-
- {{ $t('task.attributes.percentDone') }}
-
-
- {{ $t('task.attributes.created') }}
-
-
- {{ $t('task.attributes.updated') }}
-
-
- {{ $t('task.attributes.createdBy') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- #
-
-
-
- {{ $t('task.attributes.done') }}
-
-
-
- {{ $t('task.attributes.title') }}
-
-
-
- {{ $t('task.attributes.priority') }}
-
-
-
- {{ $t('task.attributes.labels') }}
-
-
- {{ $t('task.attributes.assignees') }}
-
-
- {{ $t('task.attributes.dueDate') }}
-
-
-
- {{ $t('task.attributes.startDate') }}
-
-
-
- {{ $t('task.attributes.endDate') }}
-
-
-
- {{ $t('task.attributes.percentDone') }}
-
-
-
- {{ $t('task.attributes.created') }}
-
-
-
- {{ $t('task.attributes.updated') }}
-
-
-
- {{ $t('task.attributes.createdBy') }}
-
-
-
-
-
-
-
-
- #{{ t.index }}
-
-
- {{ t.identifier }}
-
-
-
-
-
-
-
- {{ t.title }}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t.percentDone * 100 }}%
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/views/namespaces/ListNamespaces.vue b/src/views/namespaces/ListNamespaces.vue
index 949702c6..0a63b050 100644
--- a/src/views/namespaces/ListNamespaces.vue
+++ b/src/views/namespaces/ListNamespaces.vue
@@ -24,7 +24,7 @@
{{ $t('namespace.noLists') }}
-
+
{{ $t('namespace.createList') }}
diff --git a/src/views/namespaces/settings/share.vue b/src/views/namespaces/settings/share.vue
index 8f70cbd5..f3e4a20c 100644
--- a/src/views/namespaces/settings/share.vue
+++ b/src/views/namespaces/settings/share.vue
@@ -3,69 +3,67 @@
:title="title"
primary-label=""
>
-
-
+
+
+
+
-
+
+
\ No newline at end of file
diff --git a/src/views/tasks/ShowTasks.vue b/src/views/tasks/ShowTasks.vue
index d4f06ba3..9b139984 100644
--- a/src/views/tasks/ShowTasks.vue
+++ b/src/views/tasks/ShowTasks.vue
@@ -231,23 +231,25 @@ export default {
},
setDatesToNextWeek() {
- this.cStartDate = new Date()
- this.cEndDate = new Date((new Date()).getTime() + 7 * 24 * 60 * 60 * 1000)
+ const now = new Date()
+ this.cStartDate = now
+ this.cEndDate = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000)
this.showOverdue = false
this.setDate()
},
setDatesToNextMonth() {
- this.cStartDate = new Date()
- this.cEndDate = new Date((new Date()).setMonth((new Date()).getMonth() + 1))
+ const now = new Date()
+ this.cStartDate = now
+ this.cEndDate = new Date((new Date()).setMonth(now.getMonth() + 1))
this.showOverdue = false
this.setDate()
},
showTodaysTasks() {
- const d = new Date()
- this.cStartDate = new Date()
- this.cEndDate = new Date(d.setDate(d.getDate() + 1))
+ const now = new Date()
+ this.cStartDate = now
+ this.cEndDate = new Date((new Date()).setDate(now.getDate() + 1))
this.showOverdue = true
this.setDate()
},
diff --git a/src/views/tasks/TaskDetailView.vue b/src/views/tasks/TaskDetailView.vue
index 40de50de..ed1c0eb2 100644
--- a/src/views/tasks/TaskDetailView.vue
+++ b/src/views/tasks/TaskDetailView.vue
@@ -263,6 +263,7 @@
{{ task.done ? $t('task.detail.undone') : $t('task.detail.done') }}
\ No newline at end of file
diff --git a/src/views/tasks/TaskDetailViewModal.vue b/src/views/tasks/TaskDetailViewModal.vue
deleted file mode 100644
index 9b493246..00000000
--- a/src/views/tasks/TaskDetailViewModal.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/views/teams/EditTeam.vue b/src/views/teams/EditTeam.vue
index 059530e8..31c690f9 100644
--- a/src/views/teams/EditTeam.vue
+++ b/src/views/teams/EditTeam.vue
@@ -308,4 +308,6 @@ export default {
padding: 0;
}
}
+
+@include modal-transition();
\ No newline at end of file