From f9c98968c758faab813a47c417d8244735a66564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimund=20Schl=C3=BC=C3=9Fler?= Date: Mon, 5 Nov 2018 20:58:06 +0100 Subject: [PATCH] Load tasks from the server --- package-lock.json | 38 +++---- package.json | 3 +- src/app.vue | 2 +- src/components/Task.vue | 4 +- src/components/TheDetails.vue | 2 - src/models/task.js | 206 +++++++++++++++++++++++++++++++++- src/store/calendars.js | 14 +-- src/store/tasks.js | 3 + 8 files changed, 229 insertions(+), 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7b53113d..cad8e751 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4629,8 +4629,8 @@ "dev": true }, "cdav-library": { - "version": "github:nextcloud/cdav-library#ff47e205ef07cd38c174a3d0582f371cd4bc93f9", - "from": "github:nextcloud/cdav-library", + "version": "github:nextcloud/cdav-library#0da67555ae708c903fb11d025397a6f6465b4c39", + "from": "github:nextcloud/cdav-library#bugfix/noid/fix_addressbook_calendar_queries", "requires": { "@babel/polyfill": "^7.0.0" } @@ -5705,7 +5705,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -7484,14 +7484,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7506,20 +7504,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -7636,8 +7631,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -7649,7 +7643,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7664,7 +7657,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -7776,8 +7768,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -7789,7 +7780,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -7911,7 +7901,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8178,7 +8167,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -8212,7 +8201,7 @@ "dependencies": { "minimist": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz", "integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=", "dev": true } @@ -16139,7 +16128,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -16766,8 +16755,7 @@ "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, "v-tooltip": { "version": "2.0.0-rc.33", diff --git a/package.json b/package.json index 3d160f03..8b75cc01 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,12 @@ "dependencies": { "@babel/polyfill": "^7.0.0", "axios": "^0.18.0", - "cdav-library": "github:nextcloud/cdav-library", + "cdav-library": "github:nextcloud/cdav-library#bugfix/noid/fix_addressbook_calendar_queries", "ical.js": "~1.2.2", "jstimezonedetect": "", "nextcloud-vue": "^0.3.1", "p-limit": "^2.0.0", + "uuid": "^3.3.2", "v-tooltip": "2.0.0-rc.33", "vue": "^2.5.17", "vue-router": "3.0.1", diff --git a/src/app.vue b/src/app.vue index 6b4152fd..b4a06ed8 100644 --- a/src/app.vue +++ b/src/app.vue @@ -82,7 +82,7 @@ export default { Promise.all(this.calendars.map(calendar => this.$store.dispatch('getTasksFromCalendar', { calendar }))) .then(results => { this.loading = false - console.log(results) + // console.log(results) }) } } diff --git a/src/components/Task.vue b/src/components/Task.vue index 9fce8bf9..73a210c7 100644 --- a/src/components/Task.vue +++ b/src/components/Task.vue @@ -72,8 +72,8 @@ License along with this library. If not, see . {{ task.summary }}
- - {{ category.name }} + + {{ category }}
diff --git a/src/components/TheDetails.vue b/src/components/TheDetails.vue index b172dbad..1c6611e8 100644 --- a/src/components/TheDetails.vue +++ b/src/components/TheDetails.vue @@ -199,8 +199,6 @@ License along with this library. If not, see . :tag-placeholder="t('tasks', 'Add this as a new category')" :close-on-select="false" class="multiselect-vue" - track-by="id" - label="name" @input="updateCategories" @tag="addCategory" /> diff --git a/src/models/task.js b/src/models/task.js index 81484fa7..ffd3deba 100644 --- a/src/models/task.js +++ b/src/models/task.js @@ -1,2 +1,206 @@ -export default class { +/** + * Nextcloud - Tasks + * + * @author John Molakvoæ + * @copyright 2018 John Molakvoæ + * + * @author Raimund Schlüßler + * @copyright 2018 Raimund Schlüßler + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see . + * + */ + +import uuid from 'uuid' +import ICAL from 'ical.js' + +export default class Task { + + /** + * Creates an instance of Task + * + * @param {string} vcalendar the vcalendar data as string with proper new lines + * @param {object} calendar the calendar which the task belongs to + * @memberof Task + */ + constructor(vcalendar, calendar, raw) { + if (typeof vcalendar !== 'string' || vcalendar.length === 0) { + throw new Error('Invalid vCalendar') + } + this.vcalendar = vcalendar + + let jCal = ICAL.parse(vcalendar) + if (jCal[0] !== 'vcalendar') { + throw new Error('Only one task is allowed in the vCalendar data') + } + + this.jCal = jCal + this.calendar = calendar + this.vCalendar = new ICAL.Component(this.jCal) + + // used to state a task is not up to date with + // the server and cannot be pushed (etag) + this.conflict = false + + // if no uid set, create one + var vtodo = this.vCalendar.getFirstSubcomponent('vtodo') + if (!vtodo.hasProperty('uid')) { + console.debug('This task did not have a proper uid. Setting a new one for ', this) + vtodo.addPropertyWithValue('uid', uuid()) + } + + this.uri = raw.url.substr(raw.url.lastIndexOf('/') + 1) + } + + /** + * Update internal data of this task + * + * @param {jCal} jCal jCal object from ICAL.js + * @memberof Task + */ + updateTask(jCal) { + this.jCal = jCal + this.vCalendar = new ICAL.Component(this.jCal) + } + + /** + * Update linked calendar of this task + * + * @param {Object} calendar the calendar + * @memberof Contact + */ + updateCalendar(calendar) { + this.calendar = calendar + } + + /** + * Ensure we're normalizing the possible arrays + * into a string by taking the first element + * e.g. ORG:ABC\, Inc.; will output an array because of the semi-colon + * + * @param {Array|string} data the data to normalize + * @returns {string} + * @memberof Task + */ + firstIfArray(data) { + return Array.isArray(data) ? data[0] : data + } + + /** + * Return the url + * + * @readonly + * @memberof Task + */ + get url() { + if (this.dav) { + return this.dav.url + } + return '' + } + + /** + * Return the uid + * + * @readonly + * @memberof Task + */ + get uid() { + var vtodo = this.vCalendar.getFirstSubcomponent('vtodo') + return vtodo.getFirstPropertyValue('uid') || '' + } + + /** + * Set the uid + * + * @param {string} uid the uid to set + * @memberof Task + */ + set uid(uid) { + this.vCalendar.updatePropertyWithValue('uid', uid) + this.vcalendar = this.vCalendar.toString() + return true + } + + /** + * Return the first summary + * + * @readonly + * @memberof Task + */ + get summary() { + var vtodo = this.vCalendar.getFirstSubcomponent('vtodo') + return vtodo.getFirstPropertyValue('summary'); + } + + /** + * Set the summary + * + * @param {string} summary the summary + * @memberof Task + */ + set summary(summary) { + var vtodo = this.vCalendar.getFirstSubcomponent('vtodo') + vtodo.updatePropertyWithValue('summary', summary) + this.updateLastModified() + this.vcalendar = this.vCalendar.toString() + } + + /** + * Return the categories + * + * @readonly + * @memberof Task + */ + get categories() { + var vtodo = this.vCalendar.getFirstSubcomponent('vtodo') + var categories = vtodo.getFirstProperty('categories') + if (categories) { + return categories.getValues() + } else { + return [] + } + } + + /** + * Set the categories + * + * @param {string} categories the categories + * @memberof Task + */ + set categories(cats) { + var vtodo = this.vCalendar.getFirstSubcomponent('vtodo') + var categories = vtodo.getFirstProperty('categories') + if (cats.length > 0) { + if (categories) { + categories.setValues(cats) + } else { + var prop = new ICAL.Property('categories') + prop.setValues(cats) + categories = vtodo.addProperty(prop) + } + } else { + vtodo.removeProperty('categories') + } + this.updateLastModified() + this.vcalendar = this.vCalendar.toString() + } + + updateLastModified () { + var vtodo = this.vCalendar.getFirstSubcomponent('vtodo') + vtodo.updatePropertyWithValue('last-modified', ICAL.Time.now()) + vtodo.updatePropertyWithValue('dtstamp', ICAL.Time.now()) + } + } diff --git a/src/store/calendars.js b/src/store/calendars.js index ba1a0f51..35ec09bf 100644 --- a/src/store/calendars.js +++ b/src/store/calendars.js @@ -31,7 +31,6 @@ import Vue from 'vue' import ICAL from 'ical.js' -import parseIcs from '../services/parseIcs' import client from '../services/cdav' import Task from '../models/task' import pLimit from 'p-limit' @@ -258,14 +257,7 @@ const mutations = { appendTasksToCalendar(state, { calendar, tasks }) { calendar = state.calendars.find(search => search === calendar) - // convert list into an array and remove duplicate - calendar.tasks = tasks.reduce((list, task) => { - if (list[task.uid]) { - console.debug('Duplicate task overridden', list[task.uid], task) - } - Vue.set(list, task.uid, task) - return list - }, calendar.tasks) + Vue.set(calendar, 'tasks', tasks) }, /** @@ -449,12 +441,12 @@ const actions = { // We don't want to lose the url information // so we need to parse one by one const tasks = response.map(item => { - let task = new Task(item.data, calendar) + let task = new Task(item.data, calendar, item) Vue.set(task, 'dav', item) return task }) context.commit('appendTasksToCalendar', { calendar, tasks }) - context.commit('appendTasks', tasks) + // context.commit('appendTasks', tasks) return tasks }) .catch((error) => { diff --git a/src/store/tasks.js b/src/store/tasks.js index e6de0105..e98bb93b 100644 --- a/src/store/tasks.js +++ b/src/store/tasks.js @@ -80,6 +80,9 @@ const getters = { // If a calendar is given, only search in that calendar. if (rootState.route.params.calendarId) { var calendar = getters.getCalendarById(rootState.route.params.calendarId) + if (!calendar) { + return null + } return calendar.tasks.find(task => { return task.uri === rootState.route.params.taskId })