Add default list setting & creating tasks from home (#520)
Co-authored-by: sytone <github@sytone.com> Co-authored-by: Sytone <github@sytone.com> Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/520 Reviewed-by: konrad <konrad@kola-entertainments.de> Co-authored-by: sytone <kolaente@sytone.com> Co-committed-by: sytone <kolaente@sytone.com>
This commit is contained in:
parent
bad5e3d0ec
commit
306a926c66
37 changed files with 342 additions and 163 deletions
|
@ -95,7 +95,7 @@ steps:
|
|||
CYPRESS_TEST_SECRET: averyLongSecretToSe33dtheDB
|
||||
YARN_CACHE_FOLDER: .cache/yarn/
|
||||
CYPRESS_CACHE_FOLDER: .cache/cypress/
|
||||
CYPRESS_DEFAULT_COMMAND_TIMEOUT: 20000
|
||||
CYPRESS_DEFAULT_COMMAND_TIMEOUT: 60000
|
||||
commands:
|
||||
- sed -i 's/localhost/api/g' public/index.html
|
||||
- yarn serve & npx wait-on http://localhost:8080
|
||||
|
|
22
.editorconfig
Normal file
22
.editorconfig
Normal file
|
@ -0,0 +1,22 @@
|
|||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = false
|
||||
|
||||
[*.vue]
|
||||
indent_style = tab
|
||||
|
||||
[*.{yaml,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
12
README.md
12
README.md
|
@ -20,21 +20,25 @@ If you find any security-related issues you don't want to disclose publicly, ple
|
|||
There is a [docker image available](https://hub.docker.com/r/vikunja/api) with support for http/2 and aggressive caching enabled.
|
||||
|
||||
## Project setup
|
||||
```
|
||||
|
||||
```shell
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
|
||||
```shell
|
||||
yarn run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
|
||||
```shell
|
||||
yarn run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
|
||||
```shell
|
||||
yarn run lint
|
||||
```
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
'@vue/app',
|
||||
],
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ describe('User Settings', () => {
|
|||
.contains('Save')
|
||||
.click()
|
||||
|
||||
cy.wait(3000) // Wait for the request to finish
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
cy.get('.navbar .user .username')
|
||||
|
|
19
package.json
19
package.json
|
@ -69,7 +69,24 @@
|
|||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"rules": {},
|
||||
"rules": {
|
||||
"vue/html-quotes": [
|
||||
"error",
|
||||
"double"
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"single"
|
||||
],
|
||||
"comma-dangle": [
|
||||
"error",
|
||||
"always-multiline"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"never"
|
||||
]
|
||||
},
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
|
|
|
@ -55,7 +55,7 @@ export default {
|
|||
computed: {
|
||||
showIconOnly() {
|
||||
return this.icon !== '' && typeof this.$slots.default === 'undefined'
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
click(e) {
|
||||
|
|
|
@ -137,18 +137,18 @@ export default {
|
|||
},
|
||||
props: {
|
||||
value: {
|
||||
validator: prop => prop instanceof Date || prop === null || typeof prop === 'string'
|
||||
validator: prop => prop instanceof Date || prop === null || typeof prop === 'string',
|
||||
},
|
||||
chooseDateLabel: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.$t('input.datepicker.chooseDate')
|
||||
}
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setDateValue(this.value)
|
||||
|
|
|
@ -366,7 +366,7 @@ export default {
|
|||
link: (href, title, text) => {
|
||||
const isLocal = href.startsWith(`${location.protocol}//${location.hostname}`)
|
||||
const html = linkRenderer.call(renderer, href, title, text)
|
||||
return isLocal ? html : html.replace(/^<a /, `<a target="_blank" rel="noreferrer noopener nofollow" `)
|
||||
return isLocal ? html : html.replace(/^<a /, '<a target="_blank" rel="noreferrer noopener nofollow" ')
|
||||
},
|
||||
},
|
||||
highlight: function (code, language) {
|
||||
|
|
|
@ -108,21 +108,21 @@ export default {
|
|||
type: Boolean,
|
||||
default() {
|
||||
return false
|
||||
}
|
||||
},
|
||||
},
|
||||
// The placeholder of the search input
|
||||
placeholder: {
|
||||
type: String,
|
||||
default() {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
},
|
||||
// The search results where the @search listener needs to put the results into
|
||||
searchResults: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
},
|
||||
// The name of the property of the searched object to show the user.
|
||||
// If empty the component will show all raw data of an entry.
|
||||
|
@ -130,13 +130,13 @@ export default {
|
|||
type: String,
|
||||
default() {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
},
|
||||
// The object with the value, updated every time an entry is selected.
|
||||
value: {
|
||||
default() {
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
// If true, will provide an "add this as a new value" entry which fires an @create event when clicking on it.
|
||||
creatable: {
|
||||
|
@ -150,14 +150,14 @@ export default {
|
|||
type: String,
|
||||
default() {
|
||||
return this.$t('input.multiselect.createPlaceholder')
|
||||
}
|
||||
},
|
||||
},
|
||||
// The text shown next to an option.
|
||||
selectPlaceholder: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.$t('input.multiselect.selectPlaceholder')
|
||||
}
|
||||
},
|
||||
},
|
||||
// If true, allows for selecting multiple items. v-model will be an array with all selected values in that case.
|
||||
multiple: {
|
||||
|
|
|
@ -22,7 +22,7 @@ export default {
|
|||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
name: 'nothing'
|
||||
name: 'nothing',
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -14,7 +14,7 @@ export default {
|
|||
keys: {
|
||||
type: Array,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -57,7 +57,7 @@ export default {
|
|||
if (this.disabled) {
|
||||
return this.$t('task.subscription.subscribedThroughParent', {
|
||||
entity: this.entity,
|
||||
parent: this.subscription.entity
|
||||
parent: this.subscription.entity,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -118,7 +118,7 @@ export default {
|
|||
.catch(e => {
|
||||
this.error(e)
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -481,7 +481,7 @@ export default {
|
|||
reset() {
|
||||
this.query = ''
|
||||
this.selectedCmd = null
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -235,11 +235,11 @@ export default {
|
|||
this.searchLabel = 'username'
|
||||
|
||||
if (this.type === 'list') {
|
||||
this.typeString = `list`
|
||||
this.typeString = 'list'
|
||||
this.stuffService = new UserListService()
|
||||
this.stuffModel = new UserListModel({listId: this.id})
|
||||
} else if (this.type === 'namespace') {
|
||||
this.typeString = `namespace`
|
||||
this.typeString = 'namespace'
|
||||
this.stuffService = new UserNamespaceService()
|
||||
this.stuffModel = new UserNamespaceModel({
|
||||
namespaceId: this.id,
|
||||
|
@ -253,11 +253,11 @@ export default {
|
|||
this.searchLabel = 'name'
|
||||
|
||||
if (this.type === 'list') {
|
||||
this.typeString = `list`
|
||||
this.typeString = 'list'
|
||||
this.stuffService = new TeamListService()
|
||||
this.stuffModel = new TeamListModel({listId: this.id})
|
||||
} else if (this.type === 'namespace') {
|
||||
this.typeString = `namespace`
|
||||
this.typeString = 'namespace'
|
||||
this.stuffService = new TeamNamespaceService()
|
||||
this.stuffModel = new TeamNamespaceModel({
|
||||
namespaceId: this.id,
|
||||
|
@ -278,7 +278,7 @@ export default {
|
|||
.then((r) => {
|
||||
this.$set(this, 'sharables', r)
|
||||
r.forEach((s) =>
|
||||
this.$set(this.selectedRight, s.id, s.right)
|
||||
this.$set(this.selectedRight, s.id, s.right),
|
||||
)
|
||||
})
|
||||
.catch((e) => {
|
||||
|
|
102
src/components/tasks/add-task.vue
Normal file
102
src/components/tasks/add-task.vue
Normal file
|
@ -0,0 +1,102 @@
|
|||
<template>
|
||||
<div class="task-add">
|
||||
<div class="field is-grouped">
|
||||
<p :class="{ 'is-loading': taskService.loading}" class="control has-icons-left is-expanded">
|
||||
<input
|
||||
:class="{ 'disabled': taskService.loading}"
|
||||
@keyup.enter="addTask()"
|
||||
class="input"
|
||||
:placeholder="$t('list.list.addPlaceholder')"
|
||||
type="text"
|
||||
v-focus
|
||||
v-model="newTaskTitle"
|
||||
ref="newTaskInput"
|
||||
@keyup="errorMessage = ''"
|
||||
/>
|
||||
<span class="icon is-small is-left">
|
||||
<icon icon="tasks"/>
|
||||
</span>
|
||||
</p>
|
||||
<p class="control">
|
||||
<x-button
|
||||
:disabled="newTaskTitle.length === 0"
|
||||
@click="addTask()"
|
||||
icon="plus"
|
||||
>
|
||||
{{ $t('list.list.add') }}
|
||||
</x-button>
|
||||
</p>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errorMessage !== ''">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
<quick-add-magic v-if="errorMessage === ''"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListService from '../../services/list'
|
||||
import TaskService from '../../services/task'
|
||||
import LabelService from '../../services/label'
|
||||
import LabelTaskService from '../../services/labelTask'
|
||||
import createTask from '@/components/tasks/mixins/createTask'
|
||||
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic'
|
||||
|
||||
export default {
|
||||
name: 'add-task',
|
||||
data() {
|
||||
return {
|
||||
newTaskTitle: '',
|
||||
listService: ListService,
|
||||
taskService: TaskService,
|
||||
labelService: LabelService,
|
||||
labelTaskService: LabelTaskService,
|
||||
errorMessage: '',
|
||||
}
|
||||
},
|
||||
mixins: [
|
||||
createTask,
|
||||
],
|
||||
components: {
|
||||
QuickAddMagic,
|
||||
},
|
||||
created() {
|
||||
this.listService = new ListService()
|
||||
this.taskService = new TaskService()
|
||||
this.labelService = new LabelService()
|
||||
this.labelTaskService = new LabelTaskService()
|
||||
},
|
||||
methods: {
|
||||
addTask() {
|
||||
if (this.newTaskTitle === '') {
|
||||
this.errorMessage = this.$t('list.create.addTitleRequired')
|
||||
return
|
||||
}
|
||||
this.errorMessage = ''
|
||||
|
||||
this.createNewTask(this.newTaskTitle, 0, this.$store.state.auth.settings.defaultListId)
|
||||
.then(task => {
|
||||
this.newTaskTitle = ''
|
||||
this.$emit('taskAdded', task)
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 'NO_LIST') {
|
||||
this.errorMessage = this.$t('list.create.addListRequired')
|
||||
return
|
||||
}
|
||||
this.error(e)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.task-add {
|
||||
margin-bottom: 0;
|
||||
|
||||
.button {
|
||||
height: 2.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -388,7 +388,7 @@ export default {
|
|||
|
||||
let startDate = new Date(this.startDate)
|
||||
startDate.setDate(
|
||||
startDate.getDate() + newRect.left / this.dayWidth
|
||||
startDate.getDate() + newRect.left / this.dayWidth,
|
||||
)
|
||||
startDate.setUTCHours(0)
|
||||
startDate.setUTCMinutes(0)
|
||||
|
@ -397,7 +397,7 @@ export default {
|
|||
this.taskDragged.startDate = startDate
|
||||
let endDate = new Date(startDate)
|
||||
endDate.setDate(
|
||||
startDate.getDate() + newRect.width / this.dayWidth
|
||||
startDate.getDate() + newRect.width / this.dayWidth,
|
||||
)
|
||||
this.taskDragged.startDate = startDate
|
||||
this.taskDragged.endDate = endDate
|
||||
|
@ -440,7 +440,7 @@ export default {
|
|||
this.$set(
|
||||
this.theTasks,
|
||||
tt,
|
||||
this.addGantAttributes(r)
|
||||
this.addGantAttributes(r),
|
||||
)
|
||||
break
|
||||
}
|
||||
|
|
|
@ -26,6 +26,11 @@ export default {
|
|||
const parsedTask = parseTaskText(newTaskTitle)
|
||||
const assignees = []
|
||||
|
||||
// Uses the following ways to get the list id of the new task:
|
||||
// 1. If specified in quick add magic, look in store if it exists and use it if it does
|
||||
// 2. Else check if a list was passed as parameter
|
||||
// 3. Otherwise use the id from the route parameter
|
||||
// 4. If none of the above worked, reject the promise with an error.
|
||||
let listId = null
|
||||
if (parsedTask.list !== null) {
|
||||
const list = this.$store.getters['lists/findListByExactname'](parsedTask.list)
|
||||
|
@ -35,6 +40,10 @@ export default {
|
|||
listId = lId !== 0 ? lId : this.$route.params.listId
|
||||
}
|
||||
|
||||
if (typeof listId === 'undefined' || listId === 0) {
|
||||
return Promise.reject('NO_LIST')
|
||||
}
|
||||
|
||||
// Separate closure because we need to wait for the results of the user search if users were entered in the
|
||||
// task create request. Because _that_ happens in a promise, we'll need something to call when it resolves.
|
||||
const createTask = () => {
|
||||
|
@ -83,7 +92,7 @@ export default {
|
|||
.then(res => {
|
||||
return addLabelToTask(res)
|
||||
})
|
||||
.catch(e => Promise.reject(e))
|
||||
.catch(e => Promise.reject(e)),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
@ -110,7 +119,7 @@ export default {
|
|||
assignees.push(user)
|
||||
}
|
||||
return Promise.resolve(users)
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
@ -229,7 +229,7 @@ export default {
|
|||
.then((r) => {
|
||||
this.$store.commit(
|
||||
'attachments/removeById',
|
||||
this.attachmentToDelete.id
|
||||
this.attachmentToDelete.id,
|
||||
)
|
||||
this.success(r)
|
||||
})
|
||||
|
|
|
@ -91,7 +91,7 @@ export default {
|
|||
.finally(() => {
|
||||
this.saving = false
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -95,7 +95,7 @@ export default {
|
|||
.finally(() => {
|
||||
this.saving = false
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -32,6 +32,11 @@ export default {
|
|||
foundLists: [],
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Multiselect,
|
||||
},
|
||||
|
@ -39,6 +44,14 @@ export default {
|
|||
this.listSerivce = new ListService()
|
||||
this.list = new ListModel()
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.list = newVal
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.list = this.value
|
||||
},
|
||||
methods: {
|
||||
findLists(query) {
|
||||
if (query === '') {
|
||||
|
@ -58,7 +71,9 @@ export default {
|
|||
this.$set(this, 'foundLists', [])
|
||||
},
|
||||
select(list) {
|
||||
this.list = list
|
||||
this.$emit('selected', list)
|
||||
this.$emit('input', list)
|
||||
},
|
||||
namespace(namespaceId) {
|
||||
const namespace = this.$store.getters['namespaces/getNamespaceById'](namespaceId)
|
||||
|
|
|
@ -135,7 +135,7 @@ export default {
|
|||
showListColor: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
theTask(newVal) {
|
||||
|
@ -178,13 +178,13 @@ export default {
|
|||
this.success({
|
||||
message: this.task.done ?
|
||||
this.$t('task.doneSuccess') :
|
||||
this.$t('task.undoneSuccess')
|
||||
this.$t('task.undoneSuccess'),
|
||||
}, [{
|
||||
title: 'Undo',
|
||||
callback: () => {
|
||||
this.task.done = !this.task.done
|
||||
this.markAsDone(!checked)
|
||||
}
|
||||
},
|
||||
}])
|
||||
})
|
||||
.catch(e => {
|
||||
|
|
|
@ -12,7 +12,7 @@ export const createDateFromString = dateString => {
|
|||
}
|
||||
|
||||
if (dateString.includes('-')) {
|
||||
dateString = dateString.replace(/-/g, "/")
|
||||
dateString = dateString.replace(/-/g, '/')
|
||||
}
|
||||
|
||||
return new Date(dateString)
|
||||
|
|
|
@ -65,7 +65,8 @@
|
|||
"weekStart": "Week starts on",
|
||||
"weekStartSunday": "Sunday",
|
||||
"weekStartMonday": "Monday",
|
||||
"language": "Language"
|
||||
"language": "Language",
|
||||
"defaultList": "Default List"
|
||||
},
|
||||
"totp": {
|
||||
"title": "Two Factor Authentication",
|
||||
|
@ -109,7 +110,8 @@
|
|||
"header": "Create a new list",
|
||||
"titlePlaceholder": "The list's title goes here…",
|
||||
"addTitleRequired": "Please specify a title.",
|
||||
"createdSuccess": "The list was successfully created."
|
||||
"createdSuccess": "The list was successfully created.",
|
||||
"addListRequired": "Please specify a list or set a default list in the settings."
|
||||
},
|
||||
"archive": {
|
||||
"title": "Archive \"{list}\"",
|
||||
|
@ -204,7 +206,6 @@
|
|||
"title": "List",
|
||||
"add": "Add",
|
||||
"addPlaceholder": "Add a new task…",
|
||||
"addTitleRequired": "Please specify a title.",
|
||||
"empty": "This list is currently empty.",
|
||||
"newTaskCta": "Create a new task.",
|
||||
"editTask": "Edit Task"
|
||||
|
|
|
@ -9,6 +9,7 @@ export default class UserSettingsModel extends AbstractModel {
|
|||
discoverableByName: false,
|
||||
discoverableByEmail: false,
|
||||
overdueTasksRemindersEnabled: true,
|
||||
defaultListId: undefined,
|
||||
weekStart: 0,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,7 +171,7 @@ export default new Router({
|
|||
name: 'list.create',
|
||||
components: {
|
||||
popup: NewListComponent,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/namespaces/:id/settings/edit',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {HTTPFactory} from '@/http-common'
|
||||
import {ERROR_MESSAGE, LOADING} from '../mutation-types'
|
||||
import { HTTPFactory } from '@/http-common'
|
||||
import { ERROR_MESSAGE, LOADING } from '../mutation-types'
|
||||
import UserModel from '../../models/user'
|
||||
import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
|
||||
|
||||
|
@ -58,7 +58,7 @@ export default {
|
|||
// Logs a user in with a set of credentials.
|
||||
login(ctx, credentials) {
|
||||
const HTTP = HTTPFactory()
|
||||
ctx.commit(LOADING, true, {root: true})
|
||||
ctx.commit(LOADING, true, { root: true })
|
||||
|
||||
// Delete an eventually preexisting old token
|
||||
removeToken()
|
||||
|
@ -93,7 +93,7 @@ export default {
|
|||
return Promise.reject(e)
|
||||
})
|
||||
.finally(() => {
|
||||
ctx.commit(LOADING, false, {root: true})
|
||||
ctx.commit(LOADING, false, { root: true })
|
||||
})
|
||||
},
|
||||
// Registers a new user and logs them in.
|
||||
|
@ -110,18 +110,18 @@ export default {
|
|||
})
|
||||
.catch(e => {
|
||||
if (e.response && e.response.data && e.response.data.message) {
|
||||
ctx.commit(ERROR_MESSAGE, e.response.data.message, {root: true})
|
||||
ctx.commit(ERROR_MESSAGE, e.response.data.message, { root: true })
|
||||
}
|
||||
|
||||
return Promise.reject(e)
|
||||
})
|
||||
.finally(() => {
|
||||
ctx.commit(LOADING, false, {root: true})
|
||||
ctx.commit(LOADING, false, { root: true })
|
||||
})
|
||||
},
|
||||
openIdAuth(ctx, {provider, code}) {
|
||||
openIdAuth(ctx, { provider, code }) {
|
||||
const HTTP = HTTPFactory()
|
||||
ctx.commit(LOADING, true, {root: true})
|
||||
ctx.commit(LOADING, true, { root: true })
|
||||
|
||||
const data = {
|
||||
code: code,
|
||||
|
@ -143,10 +143,10 @@ export default {
|
|||
return Promise.reject(e)
|
||||
})
|
||||
.finally(() => {
|
||||
ctx.commit(LOADING, false, {root: true})
|
||||
ctx.commit(LOADING, false, { root: true })
|
||||
})
|
||||
},
|
||||
linkShareAuth(ctx, {hash, password}) {
|
||||
linkShareAuth(ctx, { hash, password }) {
|
||||
const HTTP = HTTPFactory()
|
||||
return HTTP.post('/shares/' + hash + '/auth', {
|
||||
password: password,
|
||||
|
|
|
@ -10,13 +10,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.task-add {
|
||||
.list-view .task-add {
|
||||
padding: 1rem 1rem 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
.button {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.list-title {
|
||||
|
|
|
@ -3,10 +3,15 @@
|
|||
<h2>
|
||||
{{ $t(`home.welcome${welcome}`, {username: userInfo.name !== '' ? userInfo.name : userInfo.username}) }}!
|
||||
</h2>
|
||||
<add-task
|
||||
:listId="defaultListId"
|
||||
@taskAdded="updateTaskList"
|
||||
class="is-max-width-desktop"
|
||||
/>
|
||||
<template v-if="!hasTasks">
|
||||
<p>{{ $t('home.list.newText') }}</p>
|
||||
<x-button
|
||||
:to="{name: 'list.create', params: { id: defaultNamespaceId }}"
|
||||
:to="{ name: 'list.create', params: { id: defaultNamespaceId } }"
|
||||
:shadow="false"
|
||||
class="ml-2"
|
||||
v-if="defaultNamespaceId > 0"
|
||||
|
@ -34,7 +39,7 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ShowTasks :show-all="true" v-if="hasLists"/>
|
||||
<ShowTasks :show-all="true" v-if="hasLists" :key="showTasksKey"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -43,18 +48,21 @@ import {mapState} from 'vuex'
|
|||
import ShowTasks from './tasks/ShowTasks'
|
||||
import {getHistory} from '@/modules/listHistory'
|
||||
import ListCard from '@/components/list/partials/list-card'
|
||||
import AddTask from '../components/tasks/add-task'
|
||||
|
||||
export default {
|
||||
name: 'Home',
|
||||
components: {
|
||||
ListCard,
|
||||
ShowTasks,
|
||||
AddTask,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
currentDate: new Date(),
|
||||
tasks: [],
|
||||
showTasksKey: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -86,10 +94,13 @@ export default {
|
|||
})
|
||||
},
|
||||
...mapState({
|
||||
migratorsEnabled: state => state.config.availableMigrators !== null && state.config.availableMigrators.length > 0,
|
||||
migratorsEnabled: state =>
|
||||
state.config.availableMigrators !== null &&
|
||||
state.config.availableMigrators.length > 0,
|
||||
authenticated: state => state.auth.authenticated,
|
||||
userInfo: state => state.auth.info,
|
||||
hasTasks: state => state.hasTasks,
|
||||
defaultListId: state => state.auth.defaultListId,
|
||||
defaultNamespaceId: state => {
|
||||
if (state.namespaces.namespaces.length === 0) {
|
||||
return 0
|
||||
|
@ -105,6 +116,13 @@ export default {
|
|||
return state.namespaces.namespaces[0].lists.length > 0
|
||||
},
|
||||
}),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// This is to reload the tasks list after adding a new task through the global task add.
|
||||
// FIXME: Should use vuex (somehow?)
|
||||
updateTaskList() {
|
||||
this.showTasksKey++
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{ 'is-loading': taskCollectionService.loading}"
|
||||
class="loader-container is-max-width-desktop list-view">
|
||||
<div class="filter-container" v-if="list.isSavedFilter && !list.isSavedFilter()">
|
||||
:class="{ 'is-loading': taskCollectionService.loading }"
|
||||
class="loader-container is-max-width-desktop list-view"
|
||||
>
|
||||
<div
|
||||
class="filter-container"
|
||||
v-if="list.isSavedFilter && !list.isSavedFilter()"
|
||||
>
|
||||
<div class="items">
|
||||
<div class="search">
|
||||
<div :class="{ 'hidden': !showTaskSearch }" class="field has-addons">
|
||||
<div :class="{ hidden: !showTaskSearch }" class="field has-addons">
|
||||
<div class="control has-icons-left has-icons-right">
|
||||
<input
|
||||
@blur="hideSearchBar()"
|
||||
|
@ -14,9 +18,10 @@
|
|||
:placeholder="$t('misc.search')"
|
||||
type="text"
|
||||
v-focus
|
||||
v-model="searchTerm"/>
|
||||
v-model="searchTerm"
|
||||
/>
|
||||
<span class="icon is-left">
|
||||
<icon icon="search"/>
|
||||
<icon icon="search" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="control">
|
||||
|
@ -52,48 +57,30 @@
|
|||
</div>
|
||||
|
||||
<card :padding="false" :has-content="false" class="has-overflow">
|
||||
<div class="field task-add" v-if="!list.isArchived && canWrite && list.id > 0">
|
||||
<div class="field is-grouped">
|
||||
<p :class="{ 'is-loading': taskService.loading}" class="control has-icons-left is-expanded">
|
||||
<input
|
||||
:class="{ 'disabled': taskService.loading}"
|
||||
@keyup.enter="addTask()"
|
||||
class="input"
|
||||
:placeholder="$t('list.list.addPlaceholder')"
|
||||
type="text"
|
||||
v-focus
|
||||
v-model="newTaskText"
|
||||
<template
|
||||
v-if="!list.isArchived && canWrite && list.id > 0"
|
||||
>
|
||||
<add-task
|
||||
@taskAdded="updateTaskList"
|
||||
ref="newTaskInput"
|
||||
/>
|
||||
<span class="icon is-small is-left">
|
||||
<icon icon="tasks"/>
|
||||
</span>
|
||||
</p>
|
||||
<p class="control">
|
||||
<x-button
|
||||
:disabled="newTaskText.length === 0"
|
||||
@click="addTask()"
|
||||
icon="plus"
|
||||
>
|
||||
{{ $t('list.list.add') }}
|
||||
</x-button>
|
||||
</p>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="showError && newTaskText === ''">
|
||||
{{ $t('list.list.addTitleRequired') }}
|
||||
</p>
|
||||
<quick-add-magic v-if="!showError"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<nothing v-if="ctaVisible && tasks.length === 0 && !taskCollectionService.loading">
|
||||
|
||||
{{ $t('list.list.empty') }}
|
||||
<a @click="$refs.newTaskInput.focus()">
|
||||
<a @click="focusNewTaskInput()">
|
||||
{{ $t('list.list.newTaskCta') }}
|
||||
</a>
|
||||
|
||||
</nothing>
|
||||
|
||||
<div class="tasks-container">
|
||||
<div :class="{'short': isTaskEdit}" class="tasks mt-0" v-if="tasks && tasks.length > 0">
|
||||
<div
|
||||
:class="{ short: isTaskEdit }"
|
||||
class="tasks mt-0"
|
||||
v-if="tasks && tasks.length > 0"
|
||||
>
|
||||
<single-task-in-list
|
||||
:show-list-color="false"
|
||||
:disabled="!canWrite"
|
||||
|
@ -103,8 +90,12 @@
|
|||
task-detail-route="task.detail"
|
||||
v-for="t in tasks"
|
||||
>
|
||||
<div @click="editTask(t.id)" class="icon settings" v-if="!list.isArchived && canWrite">
|
||||
<icon icon="pencil-alt"/>
|
||||
<div
|
||||
@click="editTask(t.id)"
|
||||
class="icon settings"
|
||||
v-if="!list.isArchived && canWrite"
|
||||
>
|
||||
<icon icon="pencil-alt" />
|
||||
</div>
|
||||
</single-task-in-list>
|
||||
</div>
|
||||
|
@ -121,7 +112,8 @@
|
|||
aria-label="pagination"
|
||||
class="pagination is-centered p-4"
|
||||
role="navigation"
|
||||
v-if="taskCollectionService.totalPages > 1">
|
||||
v-if="taskCollectionService.totalPages > 1"
|
||||
>
|
||||
<router-link
|
||||
:disabled="currentPage === 1"
|
||||
:to="getRouteForPagination(currentPage - 1)"
|
||||
|
@ -138,13 +130,16 @@
|
|||
</router-link>
|
||||
<ul class="pagination-list">
|
||||
<template v-for="(p, i) in pages">
|
||||
<li :key="'page'+i" v-if="p.isEllipsis"><span class="pagination-ellipsis">…</span></li>
|
||||
<li :key="'page'+i" v-else>
|
||||
<li :key="'page' + i" v-if="p.isEllipsis">
|
||||
<span class="pagination-ellipsis">…</span>
|
||||
</li>
|
||||
<li :key="'page' + i" v-else>
|
||||
<router-link
|
||||
:aria-label="'Goto page ' + p.number"
|
||||
:class="{'is-current': p.number === currentPage}"
|
||||
:class="{ 'is-current': p.number === currentPage }"
|
||||
:to="getRouteForPagination(p.number)"
|
||||
class="pagination-link">
|
||||
class="pagination-link"
|
||||
>
|
||||
{{ p.number }}
|
||||
</router-link>
|
||||
</li>
|
||||
|
@ -155,10 +150,8 @@
|
|||
|
||||
<!-- This router view is used to show the task popup while keeping the kanban board itself -->
|
||||
<transition name="modal">
|
||||
<router-view/>
|
||||
<router-view />
|
||||
</transition>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -167,15 +160,16 @@ import TaskService from '../../../services/task'
|
|||
import TaskModel from '../../../models/task'
|
||||
|
||||
import EditTask from '../../../components/tasks/edit-task'
|
||||
import AddTask from '../../../components/tasks/add-task'
|
||||
import SingleTaskInList from '../../../components/tasks/partials/singleTaskInList'
|
||||
import taskList from '../../../components/tasks/mixins/taskList'
|
||||
import {saveListView} from '@/helpers/saveListView'
|
||||
import { saveListView } from '@/helpers/saveListView'
|
||||
import Rights from '../../../models/rights.json'
|
||||
import {mapState} from 'vuex'
|
||||
import { mapState } from 'vuex'
|
||||
import FilterPopup from '@/components/list/partials/filter-popup'
|
||||
import { HAS_TASKS } from '@/store/mutation-types'
|
||||
import Nothing from '@/components/misc/nothing'
|
||||
import createTask from '@/components/tasks/mixins/createTask'
|
||||
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic'
|
||||
|
||||
export default {
|
||||
name: 'List',
|
||||
|
@ -184,8 +178,6 @@ export default {
|
|||
taskService: TaskService,
|
||||
isTaskEdit: false,
|
||||
taskEditTask: TaskModel,
|
||||
newTaskText: '',
|
||||
showError: false,
|
||||
ctaVisible: false,
|
||||
}
|
||||
},
|
||||
|
@ -194,11 +186,11 @@ export default {
|
|||
createTask,
|
||||
],
|
||||
components: {
|
||||
QuickAddMagic,
|
||||
Nothing,
|
||||
FilterPopup,
|
||||
SingleTaskInList,
|
||||
EditTask,
|
||||
AddTask,
|
||||
},
|
||||
created() {
|
||||
this.taskService = new TaskService()
|
||||
|
@ -212,7 +204,7 @@ export default {
|
|||
list: state => state.currentList,
|
||||
}),
|
||||
mounted() {
|
||||
this.$nextTick(() => this.ctaVisible = true)
|
||||
this.$nextTick(() => (this.ctaVisible = true))
|
||||
},
|
||||
methods: {
|
||||
// This function initializes the tasks page and loads the first page of tasks
|
||||
|
@ -221,22 +213,13 @@ export default {
|
|||
this.isTaskEdit = false
|
||||
this.loadTasks(page, search)
|
||||
},
|
||||
addTask() {
|
||||
if (this.newTaskText === '') {
|
||||
this.showError = true
|
||||
return
|
||||
}
|
||||
this.showError = false
|
||||
|
||||
this.createNewTask(this.newTaskText)
|
||||
.then(task => {
|
||||
focusNewTaskInput() {
|
||||
this.$refs.newTaskInput.$refs.newTaskInput.focus()
|
||||
},
|
||||
updateTaskList(task) {
|
||||
this.tasks.push(task)
|
||||
this.sortTasks()
|
||||
this.newTaskText = ''
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e)
|
||||
})
|
||||
this.$store.commit(HAS_TASKS, true)
|
||||
},
|
||||
editTask(id) {
|
||||
// Find the selected task and set it to the current object
|
||||
|
|
|
@ -645,7 +645,7 @@ export default {
|
|||
this.$refs[fieldName].$el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest'
|
||||
inline: 'nearest',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -312,7 +312,7 @@ export default {
|
|||
this.success({
|
||||
message: member.admin ?
|
||||
this.$t('team.edit.madeAdmin') :
|
||||
this.$t('team.edit.madeMember')
|
||||
this.$t('team.edit.madeMember'),
|
||||
})
|
||||
})
|
||||
.catch((e) => {
|
||||
|
|
|
@ -129,7 +129,7 @@ export default {
|
|||
let emailVerifyToken = localStorage.getItem('emailConfirmToken')
|
||||
if (emailVerifyToken) {
|
||||
const cancel = this.setLoading()
|
||||
HTTP.post(`user/confirm`, {token: emailVerifyToken})
|
||||
HTTP.post('user/confirm', {token: emailVerifyToken})
|
||||
.then(() => {
|
||||
localStorage.removeItem('emailConfirmToken')
|
||||
this.confirmedEmailSuccess = true
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
v-model="settings.name"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">
|
||||
{{ $t('user.settings.general.defaultList') }}
|
||||
</label>
|
||||
<list-search v-model="defaultList"/>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" v-model="settings.emailRemindersEnabled"/>
|
||||
|
@ -282,6 +288,7 @@ import {mapState} from 'vuex'
|
|||
|
||||
import AvatarSettings from '../../components/user/avatar-settings'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import ListSearch from '@/components/tasks/partials/listSearch'
|
||||
|
||||
export default {
|
||||
name: 'Settings',
|
||||
|
@ -306,9 +313,12 @@ export default {
|
|||
|
||||
settings: UserSettingsModel,
|
||||
userSettingsService: UserSettingsService,
|
||||
|
||||
defaultList: null,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ListSearch,
|
||||
AvatarSettings,
|
||||
},
|
||||
created() {
|
||||
|
@ -326,6 +336,8 @@ export default {
|
|||
|
||||
this.playSoundWhenDone = localStorage.getItem(playSoundWhenDoneKey) === 'true' || localStorage.getItem(playSoundWhenDoneKey) === null
|
||||
|
||||
this.defaultList = this.$store.getters['lists/getListById'](this.settings.defaultListId)
|
||||
|
||||
this.totpStatus()
|
||||
},
|
||||
mounted() {
|
||||
|
@ -351,7 +363,7 @@ export default {
|
|||
migratorsEnabled: state => state.config.availableMigrators !== null && state.config.availableMigrators.length > 0,
|
||||
caldavEnabled: state => state.config.caldavEnabled,
|
||||
userInfo: state => state.auth.info,
|
||||
})
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
updatePassword() {
|
||||
|
@ -428,6 +440,7 @@ export default {
|
|||
updateSettings() {
|
||||
localStorage.setItem(playSoundWhenDoneKey, this.playSoundWhenDone)
|
||||
saveLanguage(this.language)
|
||||
this.settings.defaultListId = this.defaultList ? this.defaultList.id : 0
|
||||
|
||||
this.userSettingsService.update(this.settings)
|
||||
.then(() => {
|
||||
|
|
|
@ -20,22 +20,22 @@ module.exports = {
|
|||
msTileImage: 'images/icons/msapplication-icon-144x144.png',
|
||||
},
|
||||
manifestOptions: {
|
||||
"icons": [
|
||||
'icons': [
|
||||
{
|
||||
"src": "./images/icons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
'src': './images/icons/android-chrome-192x192.png',
|
||||
'sizes': '192x192',
|
||||
'type': 'image/png',
|
||||
},
|
||||
{
|
||||
"src": "./images/icons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
'src': './images/icons/android-chrome-512x512.png',
|
||||
'sizes': '512x512',
|
||||
'type': 'image/png',
|
||||
},
|
||||
{
|
||||
"src": "./images/icons/icon-maskable.png",
|
||||
"sizes": "1024x1024",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
'src': './images/icons/icon-maskable.png',
|
||||
'sizes': '1024x1024',
|
||||
'type': 'image/png',
|
||||
'purpose': 'maskable',
|
||||
},
|
||||
],
|
||||
shortcuts: [
|
||||
|
@ -62,8 +62,8 @@ module.exports = {
|
|||
name: 'Teams Overview',
|
||||
short_name: 'Teams',
|
||||
url: '/teams',
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
Loading…
Reference in a new issue