Merge branch 'main' into feature/simplify-taskList-mixin
continuous-integration/drone/pr Build is passing Details

# Conflicts:
#	src/components/tasks/mixins/taskList.js
This commit is contained in:
kolaente 2021-09-10 16:21:10 +02:00
commit 7a4b5d6449
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
18 changed files with 320 additions and 934 deletions

View File

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

View File

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

View File

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

View File

@ -190,6 +190,35 @@ import ListService from '@/services/list'
import NamespaceService from '@/services/namespace'
import EditLabels from '@/components/tasks/partials/editLabels.vue'
// FIXME: merge with DEFAULT_PARAMS in taskList.js
const DEFAULT_PARAMS = {
sort_by: [],
order_by: [],
filter_by: [],
filter_value: [],
filter_comparator: [],
filter_include_nulls: true,
filter_concat: 'or',
s: '',
}
const DEFAULT_FILTERS = {
done: false,
dueDate: '',
requireAllFilters: false,
priority: 0,
usePriority: false,
startDate: '',
endDate: '',
percentDone: 0,
usePercentDone: false,
reminders: '',
assignees: '',
labels: '',
list_id: '',
namespace: '',
}
export default {
name: 'filters',
components: {
@ -202,32 +231,8 @@ export default {
},
data() {
return {
params: {
sort_by: [],
order_by: [],
filter_by: [],
filter_value: [],
filter_comparator: [],
filter_include_nulls: true,
filter_concat: 'or',
s: '',
},
filters: {
done: false,
dueDate: '',
requireAllFilters: false,
priority: 0,
usePriority: false,
startDate: '',
endDate: '',
percentDone: 0,
usePercentDone: false,
reminders: '',
assignees: '',
labels: '',
list_id: '',
namespace: '',
},
params: DEFAULT_PARAMS,
filters: DEFAULT_FILTERS,
usersService: UserService,
foundusers: [],

View File

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

View File

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

View File

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

3
src/helpers/find.ts Normal file
View File

@ -0,0 +1,3 @@
export function findIndexById(array : [], id : string | number) {
return array.findIndex(({id: currentId}) => currentId === id)
}

View File

@ -1,5 +0,0 @@
{
"UNKNOWN": 0,
"USER": 1,
"LINK_SHARE": 2
}

View File

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

View File

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

View File

@ -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)
console.debug('Remove attachement', id)
break
}
}
const attachmentIndex = findIndexById(state.attachments, id)
state.attachments.splice(attachmentIndex, 1)
console.debug('Remove attachement', id)
},
},
}

View File

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

View File

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

View File

@ -1,3 +1,5 @@
@import "../../../node_modules/bulma/sass/utilities/_all";
@import 'colors';
@import 'shadows';
@import 'variables';

View File

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

View File

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

1003
yarn.lock

File diff suppressed because it is too large Load Diff