Compare commits

...
This repository has been archived on 2024-02-08. You can view files and clone it, but cannot push or open issues or pull requests.

39 Commits
main ... main

Author SHA1 Message Date
kolaente dc0a2defda
Fix saving settings when no default list was selected 2021-07-17 23:11:57 +02:00
kolaente 901aabe976
Increase cypress command timeout 2021-07-17 22:38:12 +02:00
kolaente 1f946aa47d
Increase cypress command timeout 2021-07-17 22:37:26 +02:00
kolaente c1d8626fed
Add explanatory fixme 2021-07-17 22:19:52 +02:00
kolaente a0f1913645
Add a v-model prop to listSearch 2021-07-17 22:14:24 +02:00
kolaente 8a3fe5daa2
Revert changes to listSearch component 2021-07-17 22:02:49 +02:00
kolaente 894cf6c3f8
Add setting for default list 2021-07-17 21:57:03 +02:00
kolaente 3d375ddf56
Fix input styles 2021-07-17 21:45:38 +02:00
kolaente 3d18c69429
Cleanup 2021-07-17 21:37:41 +02:00
kolaente e405ebe88f
Check if we have a list specified 2021-07-17 21:34:14 +02:00
kolaente 0e02b38c1e
Actually use the list when creating a new task 2021-07-17 21:22:43 +02:00
kolaente 7b8fbe1835
Move add task logic from list with quick add 2021-07-17 21:19:11 +02:00
kolaente d3c04ef738
Fix lint 2021-07-17 21:05:03 +02:00
kolaente 2c4829bc04
Merge branch 'main' into pr-520-add-task-component
# Conflicts:
#	src/views/Home.vue
#	src/views/list/views/List.vue
2021-07-17 21:03:28 +02:00
Sytone 7680840d01 Merge branch 'main' of ssh://kolaente.dev:9022/vikunja/frontend into main 2021-06-29 09:06:44 -07:00
Sytone 74494ebee1 Merge branch 'main' of ssh://kolaente.dev:9022/vikunja/frontend into main 2021-06-29 09:06:00 -07:00
Sytone 3a6d81d221 fixes for comments 2021-06-17 12:55:20 -07:00
Sytone f39a4d76da Fix nesting to show warning if list not set.
Cleanup comments
Cleanup user settings
Move default list id to computed
2021-06-15 16:59:53 -07:00
Sytone 657f572471 Merge branch 'main' of ssh://kolaente.dev:9022/vikunja/frontend into main 2021-06-15 16:46:30 -07:00
Sytone 046607683e Cleanup commented code 2021-06-08 08:32:38 -07:00
Sytone 6082ba5c05 Merge branch 'main' of ssh://kolaente.dev:9022/vikunja/frontend into main 2021-06-08 08:25:18 -07:00
Sytone 816c5bb4ec Set list in search box in settings 2021-06-08 08:24:11 -07:00
Sytone a9728f774d Add settings lookup to add-task
Enable specification of List ID for list-search
Add default list to user settings
Add default setting
Update settings UI for default list
2021-06-07 15:12:32 -07:00
Sytone b005d023c9 Update label logic on add-task component
Lint updates
2021-06-07 09:07:42 -07:00
Sytone 92727e1c01 Merge branch 'main' of ssh://kolaente.dev:9022/vikunja/frontend into main 2021-06-07 09:06:23 -07:00
Sytone 15b5ad9d84 Fix focus on new task 2021-06-02 17:21:39 -07:00
Sytone 714c01403c Cleanup semi colons and add rule 2021-06-02 15:32:13 -07:00
Sytone 194b1291e6 Fix json spacing
Update editor config
Add configuration warning
2021-06-02 15:31:13 -07:00
Sytone 422ce6795a fix json spacing
Add configuration warning
2021-06-02 15:30:43 -07:00
Sytone 52b0a4c6ac Add validation for list ID and disable if not valid.
Add component to Home with undefined list id.
2021-06-02 12:23:48 -07:00
Sytone 9f9c942c73 Revert welcome message 2021-06-02 11:47:33 -07:00
Sytone d363cfc972 Cleanup comments 2021-06-02 11:43:48 -07:00
Sytone c3c22bf202 Enforce trailing commas 2021-06-02 11:40:31 -07:00
Sytone 5d36d9399d Update to use single quotes uniformaly 2021-06-02 11:35:59 -07:00
Sytone f70932dd39 Move html to double quote as single used inside 2021-06-02 11:33:57 -07:00
Sytone 4642ce2b0f Update rules to use single quote
Update editor config to be consistent for YAML,JSON
2021-06-02 11:32:25 -07:00
Sytone 034a30046c Merge branch 'main' of ssh://kolaente.dev:9022/vikunja/frontend into main 2021-06-02 09:26:13 -07:00
sytone a4b64f9bc6 Merge remote-tracking branch 'upstream/main' 2021-05-26 13:59:43 -07:00
sytone 1200f0b416 Make add task a component
Add add-task to home
Change add task to component for lists
Made welcome update on time.. because.
2021-05-26 13:33:12 -07:00
37 changed files with 342 additions and 163 deletions

View File

@ -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
View 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

View File

@ -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
```

View File

@ -1,5 +1,5 @@
module.exports = {
presets: [
'@vue/app'
]
'@vue/app',
],
}

View File

@ -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')

View File

@ -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"
},
@ -95,4 +112,4 @@
],
"testEnvironment": "jsdom"
}
}
}

View File

@ -55,7 +55,7 @@ export default {
computed: {
showIconOnly() {
return this.icon !== '' && typeof this.$slots.default === 'undefined'
}
},
},
methods: {
click(e) {

View File

@ -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)

View File

@ -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) {

View File

@ -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: {

View File

@ -22,7 +22,7 @@ export default {
type: String,
required: false,
default: '',
}
},
},
}
</script>

View File

@ -6,6 +6,6 @@
<script>
export default {
name: 'nothing'
name: 'nothing',
}
</script>

View File

@ -14,7 +14,7 @@ export default {
keys: {
type: Array,
required: true,
}
},
},
}
</script>

View File

@ -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>

View File

@ -481,7 +481,7 @@ export default {
reset() {
this.query = ''
this.selectedCmd = null
}
},
},
}
</script>

View File

@ -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) => {

View 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>

View File

@ -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
}

View File

@ -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)
@ -34,7 +39,11 @@ export default {
if (listId === null) {
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)
})
}),
)
})

View File

@ -229,7 +229,7 @@ export default {
.then((r) => {
this.$store.commit(
'attachments/removeById',
this.attachmentToDelete.id
this.attachmentToDelete.id,
)
this.success(r)
})

View File

@ -91,7 +91,7 @@ export default {
.finally(() => {
this.saving = false
})
}
},
},
}
</script>

View File

@ -95,7 +95,7 @@ export default {
.finally(() => {
this.saving = false
})
}
},
},
}
</script>

View File

@ -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)

View File

@ -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 => {

View File

@ -12,7 +12,7 @@ export const createDateFromString = dateString => {
}
if (dateString.includes('-')) {
dateString = dateString.replace(/-/g, "/")
dateString = dateString.replace(/-/g, '/')
}
return new Date(dateString)

View File

@ -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"

View File

@ -9,6 +9,7 @@ export default class UserSettingsModel extends AbstractModel {
discoverableByName: false,
discoverableByEmail: false,
overdueTasksRemindersEnabled: true,
defaultListId: undefined,
weekStart: 0,
}
}

View File

@ -171,7 +171,7 @@ export default new Router({
name: 'list.create',
components: {
popup: NewListComponent,
}
},
},
{
path: '/namespaces/:id/settings/edit',

View File

@ -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,

View File

@ -10,13 +10,8 @@
}
}
.task-add {
padding: 1rem 1rem 0;
margin-bottom: 0;
.button {
height: 40px;
}
.list-view .task-add {
padding: 1rem 1rem 0;
}
.list-title {

View File

@ -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>

View File

@ -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"
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
v-if="!list.isArchived && canWrite && list.id > 0"
>
<add-task
@taskAdded="updateTaskList"
ref="newTaskInput"
/>
</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">&hellip;</span></li>
<li :key="'page'+i" v-else>
<li :key="'page' + i" v-if="p.isEllipsis">
<span class="pagination-ellipsis">&hellip;</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 => {
this.tasks.push(task)
this.sortTasks()
this.newTaskText = ''
})
.catch(e => {
this.error(e)
})
focusNewTaskInput() {
this.$refs.newTaskInput.$refs.newTaskInput.focus()
},
updateTaskList(task) {
this.tasks.push(task)
this.sortTasks()
this.$store.commit(HAS_TASKS, true)
},
editTask(id) {
// Find the selected task and set it to the current object
@ -263,4 +246,4 @@ export default {
},
},
}
</script>
</script>

View File

@ -645,7 +645,7 @@ export default {
this.$refs[fieldName].$el.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'nearest'
inline: 'nearest',
})
}
})

View File

@ -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) => {

View File

@ -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

View File

@ -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(() => {

View File

@ -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',
}
]
},
],
},
}
},
}