Merge branch 'main' into feature/simplify-taskList-mixin
continuous-integration/drone/pr Build is passing
Details
continuous-integration/drone/pr Build is passing
Details
# Conflicts: # src/components/tasks/mixins/taskList.js
This commit is contained in:
commit
7a4b5d6449
|
@ -35,7 +35,7 @@
|
|||
"vue-shortkey": "3.1.7",
|
||||
"vuedraggable": "2.24.3",
|
||||
"vuex": "3.6.2",
|
||||
"workbox-precaching": "6.2.4"
|
||||
"workbox-precaching": "6.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@4tw/cypress-drag-drop": "2.0.0",
|
||||
|
@ -53,7 +53,7 @@
|
|||
"babel-eslint": "10.1.0",
|
||||
"cypress": "8.3.1",
|
||||
"cypress-file-upload": "5.0.8",
|
||||
"esbuild": "0.12.25",
|
||||
"esbuild": "0.12.26",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-plugin-vue": "7.17.0",
|
||||
"express": "4.17.1",
|
||||
|
@ -61,7 +61,7 @@
|
|||
"jest": "27.1.1",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rollup-plugin-visualizer": "5.5.2",
|
||||
"sass": "1.39.0",
|
||||
"sass": "1.39.2",
|
||||
"ts-jest": "27.0.5",
|
||||
"typescript": "4.4.2",
|
||||
"vite": "2.5.6",
|
||||
|
@ -72,7 +72,7 @@
|
|||
"vue-router": "3.5.2",
|
||||
"vue-template-compiler": "2.6.14",
|
||||
"wait-on": "6.0.0",
|
||||
"workbox-cli": "6.2.4"
|
||||
"workbox-cli": "6.3.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
|
10
src/App.vue
10
src/App.vue
|
@ -23,11 +23,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import {mapState, mapGetters} from 'vuex'
|
||||
import isTouchDevice from 'is-touch-device'
|
||||
|
||||
import authTypes from './models/authTypes'
|
||||
|
||||
import Notification from './components/misc/notification'
|
||||
import {KEYBOARD_SHORTCUTS_ACTIVE, ONLINE} from './store/mutation-types'
|
||||
import KeyboardShortcuts from './components/misc/keyboard-shortcuts'
|
||||
|
@ -74,11 +72,13 @@ export default {
|
|||
return isTouchDevice()
|
||||
},
|
||||
...mapState({
|
||||
authUser: state => state.auth.authenticated && (state.auth.info && state.auth.info.type === authTypes.USER),
|
||||
authLinkShare: state => state.auth.authenticated && (state.auth.info && state.auth.info.type === authTypes.LINK_SHARE),
|
||||
online: ONLINE,
|
||||
keyboardShortcutsActive: KEYBOARD_SHORTCUTS_ACTIVE,
|
||||
}),
|
||||
...mapGetters('auth', [
|
||||
'authUser',
|
||||
'authLinkShare',
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
setupOnlineStatus() {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
:checked="checked"
|
||||
:disabled="disabled"
|
||||
:id="checkBoxId"
|
||||
@change="updateData"
|
||||
@change="(event) => updateData(event.target.checked)"
|
||||
style="display: none;"
|
||||
type="checkbox"/>
|
||||
<label :for="checkBoxId" class="check">
|
||||
|
@ -51,10 +51,10 @@ export default {
|
|||
this.checkBoxId = 'fancycheckbox' + Math.random()
|
||||
},
|
||||
methods: {
|
||||
updateData(e) {
|
||||
this.checked = e.target.checked
|
||||
this.$emit('input', this.checked)
|
||||
this.$emit('change', e.target.checked)
|
||||
updateData(checked) {
|
||||
this.checked = checked
|
||||
this.$emit('input', checked)
|
||||
this.$emit('change', checked)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -190,19 +190,8 @@ import ListService from '@/services/list'
|
|||
import NamespaceService from '@/services/namespace'
|
||||
import EditLabels from '@/components/tasks/partials/editLabels.vue'
|
||||
|
||||
export default {
|
||||
name: 'filters',
|
||||
components: {
|
||||
EditLabels,
|
||||
PrioritySelect,
|
||||
Fancycheckbox,
|
||||
flatPickr,
|
||||
PercentDoneSelect,
|
||||
Multiselect,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
params: {
|
||||
// FIXME: merge with DEFAULT_PARAMS in taskList.js
|
||||
const DEFAULT_PARAMS = {
|
||||
sort_by: [],
|
||||
order_by: [],
|
||||
filter_by: [],
|
||||
|
@ -211,8 +200,9 @@ export default {
|
|||
filter_include_nulls: true,
|
||||
filter_concat: 'or',
|
||||
s: '',
|
||||
},
|
||||
filters: {
|
||||
}
|
||||
|
||||
const DEFAULT_FILTERS = {
|
||||
done: false,
|
||||
dueDate: '',
|
||||
requireAllFilters: false,
|
||||
|
@ -227,7 +217,22 @@ export default {
|
|||
labels: '',
|
||||
list_id: '',
|
||||
namespace: '',
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'filters',
|
||||
components: {
|
||||
EditLabels,
|
||||
PrioritySelect,
|
||||
Fancycheckbox,
|
||||
flatPickr,
|
||||
PercentDoneSelect,
|
||||
Multiselect,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
params: DEFAULT_PARAMS,
|
||||
filters: DEFAULT_FILTERS,
|
||||
|
||||
usersService: UserService,
|
||||
foundusers: [],
|
||||
|
|
|
@ -24,10 +24,7 @@
|
|||
class="month"
|
||||
v-for="(m, mk) in days[yk]"
|
||||
>
|
||||
{{
|
||||
new Date(new Date(`${yk}-${parseInt(mk) + 1}-01`)).toLocaleString('en-us', {month: 'long'})
|
||||
}},
|
||||
{{ new Date(yk).getFullYear() }}
|
||||
{{ formatYear(new Date(`${yk}-${parseInt(mk) + 1}-01`)) }}
|
||||
<div class="days">
|
||||
<div
|
||||
:class="{ today: d.toDateString() === now.toDateString() }"
|
||||
|
@ -197,6 +194,7 @@ import TaskCollectionService from '../../services/taskCollection'
|
|||
import {mapState} from 'vuex'
|
||||
import Rights from '../../models/rights.json'
|
||||
import FilterPopup from '@/components/list/partials/filter-popup.vue'
|
||||
import {format} from 'date-fns'
|
||||
|
||||
export default {
|
||||
name: 'GanttChart',
|
||||
|
@ -481,6 +479,9 @@ export default {
|
|||
this.error(e)
|
||||
})
|
||||
},
|
||||
formatYear(date) {
|
||||
return format(date, 'MMMM, yyyy')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -2,6 +2,16 @@ import TaskCollectionService from '@/services/taskCollection'
|
|||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import {calculateItemPosition} from '../../../helpers/calculateItemPosition'
|
||||
|
||||
// FIXME: merge with DEFAULT_PARAMS in filters.vue
|
||||
const DEFAULT_PARAMS = {
|
||||
sort_by: ['position', 'id'],
|
||||
order_by: ['asc', 'desc'],
|
||||
filter_by: ['done'],
|
||||
filter_value: ['false'],
|
||||
filter_comparator: ['equals'],
|
||||
filter_concat: 'and',
|
||||
}
|
||||
|
||||
function createPagination(totalPages, currentPage) {
|
||||
const pages = []
|
||||
for (let i = 0; i < totalPages; i++) {
|
||||
|
@ -63,14 +73,7 @@ export default {
|
|||
searchTerm: '',
|
||||
|
||||
showTaskFilter: false,
|
||||
params: {
|
||||
sort_by: ['position', 'id'],
|
||||
order_by: ['asc', 'desc'],
|
||||
filter_by: ['done'],
|
||||
filter_value: ['false'],
|
||||
filter_comparator: ['equals'],
|
||||
filter_concat: 'and',
|
||||
},
|
||||
params: DEFAULT_PARAMS,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
|
@ -7,16 +7,17 @@
|
|||
<h1
|
||||
class="title input"
|
||||
:class="{'disabled': !canWrite}"
|
||||
@focusout="save()"
|
||||
@keydown.enter.prevent.stop="save()"
|
||||
@blur="save($event.target.textContent)"
|
||||
@keydown.enter.prevent.stop="$event.target.blur()"
|
||||
:contenteditable="canWrite ? 'true' : 'false'"
|
||||
spellcheck="false"
|
||||
ref="taskTitle">{{ task.title.trim() }}</h1>
|
||||
<transition name="fade">
|
||||
<span class="is-inline-flex is-align-items-center" v-if="loading && saving">
|
||||
<span class="loader is-inline-block mr-2"></span>
|
||||
{{ $t('misc.saving') }}
|
||||
</span>
|
||||
<span class="has-text-success is-inline-flex is-align-content-center" v-if="!loading && saved">
|
||||
<span class="has-text-success is-inline-flex is-align-content-center" v-if="!loading && showSavedMessage">
|
||||
<icon icon="check" class="mr-2"/>
|
||||
{{ $t('misc.saved') }}
|
||||
</span>
|
||||
|
@ -25,22 +26,22 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {LOADING} from '@/store/mutation-types'
|
||||
import {mapState} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'heading',
|
||||
data() {
|
||||
return {
|
||||
task: {title: '', identifier: '', index: ''},
|
||||
taskTitle: '',
|
||||
saved: false,
|
||||
showSavedMessage: false,
|
||||
saving: false, // Since loading is global state, this variable ensures we're only showing the saving icon when saving the description.
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
loading: LOADING,
|
||||
}),
|
||||
computed: {
|
||||
...mapState(['loading']),
|
||||
task() {
|
||||
return this.value
|
||||
},
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
required: true,
|
||||
|
@ -50,50 +51,29 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.task = newVal
|
||||
this.taskTitle = this.task.title
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.task = this.value
|
||||
this.taskTitle = this.task.title
|
||||
},
|
||||
methods: {
|
||||
save() {
|
||||
this.$refs.taskTitle.spellcheck = false
|
||||
|
||||
// Pull the task title from the contenteditable
|
||||
let taskTitle = this.$refs.taskTitle.textContent
|
||||
this.task.title = taskTitle
|
||||
|
||||
// We only want to save if the title was actually change.
|
||||
// Because the contenteditable does not have a change event,
|
||||
// we're building it ourselves and only calling saveTask()
|
||||
save(title) {
|
||||
// We only want to save if the title was actually changed.
|
||||
// Because the contenteditable does not have a change event
|
||||
// we're building it ourselves and only continue
|
||||
// if the task title changed.
|
||||
if (this.task.title !== this.taskTitle) {
|
||||
this.$refs.taskTitle.blur()
|
||||
this.saveTask()
|
||||
this.taskTitle = taskTitle
|
||||
}
|
||||
},
|
||||
saveTask() {
|
||||
// When only saving with enter, the focusout event is called as well. This then leads to the saveTask
|
||||
// method being called twice, overriding some task attributes in the second run.
|
||||
// If we simply check if we're already in the process of saving, we can prevent that.
|
||||
if (this.saving) {
|
||||
if (title === this.task.title) {
|
||||
return
|
||||
}
|
||||
|
||||
this.saving = true
|
||||
|
||||
this.$store.dispatch('tasks/update', this.task)
|
||||
.then(() => {
|
||||
this.$emit('input', this.task)
|
||||
this.saved = true
|
||||
const newTask = {
|
||||
...this.task,
|
||||
title,
|
||||
}
|
||||
|
||||
this.$store.dispatch('tasks/update', newTask)
|
||||
.then((task) => {
|
||||
this.$emit('input', task)
|
||||
this.showSavedMessage = true
|
||||
setTimeout(() => {
|
||||
this.saved = false
|
||||
this.showSavedMessage = false
|
||||
}, 2000)
|
||||
})
|
||||
.catch(e => {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export function findIndexById(array : [], id : string | number) {
|
||||
return array.findIndex(({id: currentId}) => currentId === id)
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"UNKNOWN": 0,
|
||||
"USER": 1,
|
||||
"LINK_SHARE": 2
|
||||
}
|
|
@ -15,6 +15,7 @@ export default class TaskModel extends AbstractModel {
|
|||
super(data)
|
||||
|
||||
this.id = Number(this.id)
|
||||
this.title = this.title?.trim()
|
||||
this.listId = Number(this.listId)
|
||||
|
||||
// Make date objects from timestamps
|
||||
|
|
|
@ -39,6 +39,8 @@ export default class TaskService extends AbstractService {
|
|||
|
||||
processModel(model) {
|
||||
|
||||
model.title = model.title?.trim()
|
||||
|
||||
// Ensure that listId is an int
|
||||
model.listId = Number(model.listId)
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
import {findIndexById} from '@/helpers/find'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: () => ({
|
||||
|
@ -15,13 +17,9 @@ export default {
|
|||
state.attachments.push(attachment)
|
||||
},
|
||||
removeById(state, id) {
|
||||
for (const a in state.attachments) {
|
||||
if (state.attachments[a].id === id) {
|
||||
state.attachments.splice(a, 1)
|
||||
const attachmentIndex = findIndexById(state.attachments, id)
|
||||
state.attachments.splice(attachmentIndex, 1)
|
||||
console.debug('Remove attachement', id)
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
|
@ -3,6 +3,12 @@ import {ERROR_MESSAGE, LOADING} from '../mutation-types'
|
|||
import UserModel from '../../models/user'
|
||||
import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
|
||||
|
||||
const AUTH_TYPES = {
|
||||
'UNKNOWN': 0,
|
||||
'USER': 1,
|
||||
'LINK_SHARE': 2,
|
||||
}
|
||||
|
||||
const defaultSettings = settings => {
|
||||
if (typeof settings.weekStart === 'undefined' || settings.weekStart === '') {
|
||||
settings.weekStart = 0
|
||||
|
@ -21,6 +27,20 @@ export default {
|
|||
lastUserInfoRefresh: null,
|
||||
settings: {},
|
||||
}),
|
||||
getters: {
|
||||
authUser(state) {
|
||||
return state.authenticated && (
|
||||
state.info &&
|
||||
state.info.type === AUTH_TYPES.USER
|
||||
)
|
||||
},
|
||||
authLinkShare(state) {
|
||||
return state.authenticated && (
|
||||
state.info &&
|
||||
state.info.type === AUTH_TYPES.LINK_SHARE
|
||||
)
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
info(state, info) {
|
||||
state.info = info
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
@import "../../../node_modules/bulma/bulma";
|
||||
// utilities are imported in variables.scss
|
||||
@import "../../../node_modules/bulma/sass/base/_all";
|
||||
@import "../../../node_modules/bulma/sass/elements/_all";
|
||||
@import "../../../node_modules/bulma/sass/form/_all";
|
||||
@import "../../../node_modules/bulma/sass/components/_all";
|
||||
@import "../../../node_modules/bulma/sass/grid/_all";
|
||||
@import "../../../node_modules/bulma/sass/helpers/_all";
|
||||
@import "../../../node_modules/bulma/sass/layout/_all";
|
||||
|
||||
|
||||
@import "fonts";
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import "../../../node_modules/bulma/sass/utilities/_all";
|
||||
|
||||
@import 'colors';
|
||||
@import 'shadows';
|
||||
@import 'variables';
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable no-console */
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
const workboxVersion = 'v6.2.4'
|
||||
const workboxVersion = 'v6.3.0'
|
||||
importScripts( `/workbox-${workboxVersion}/workbox-sw.js`)
|
||||
workbox.setConfig({modulePathPrefix: `/workbox-${workboxVersion}`})
|
||||
|
||||
|
|
|
@ -33,8 +33,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import authTypes from '@/models/authTypes.json'
|
||||
import {mapGetters} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'LinkSharingAuth',
|
||||
|
@ -54,9 +53,9 @@ export default {
|
|||
mounted() {
|
||||
this.setTitle(this.$t('sharing.authenticating'))
|
||||
},
|
||||
computed: mapState({
|
||||
authLinkShare: state => state.auth.authenticated && (state.auth.info && state.auth.info.type === authTypes.LINK_SHARE),
|
||||
}),
|
||||
computed: mapGetters('auth', [
|
||||
'authLinkShare',
|
||||
]),
|
||||
methods: {
|
||||
auth() {
|
||||
this.errorMessage = ''
|
||||
|
|
Reference in New Issue