diff options
author | Raimund Schlüßler <raimund.schluessler@mailbox.org> | 2020-02-11 00:42:58 +0300 |
---|---|---|
committer | Raimund Schlüßler <raimund.schluessler@mailbox.org> | 2020-02-11 01:09:59 +0300 |
commit | a103fd7a840d4c5c4ba531235b6276857d535ec9 (patch) | |
tree | 07c3f3896a7ed708a215a4a0bfa33b34eb62df26 | |
parent | 00618b4e3f49ad05829b46a68812043abb8af171 (diff) |
Don't edit not public tasks in lists shared to me
Signed-off-by: Raimund Schlüßler <raimund.schluessler@mailbox.org>
-rw-r--r-- | css/src/style.scss | 5 | ||||
-rw-r--r-- | src/App.vue | 45 | ||||
-rw-r--r-- | src/components/AppNavigation/ListItemCalendar.vue | 53 | ||||
-rw-r--r-- | src/components/TheDetails.vue | 56 | ||||
-rw-r--r-- | src/models/principal.js | 67 | ||||
-rw-r--r-- | src/store/calendars.js | 24 | ||||
-rw-r--r-- | src/store/principals.js | 148 | ||||
-rw-r--r-- | src/store/store.js | 2 | ||||
-rw-r--r-- | src/store/tasks.js | 29 | ||||
-rw-r--r-- | tests/javascript/unit/setupStore.js | 2 |
10 files changed, 376 insertions, 55 deletions
diff --git a/css/src/style.scss b/css/src/style.scss index ee6c1060..4ea8c378 100644 --- a/css/src/style.scss +++ b/css/src/style.scss @@ -31,6 +31,11 @@ display: none; } } + + .app-navigation-entry__utils .icon-loading { + height: 32px; + width: 32px; + } } &.edit { diff --git a/src/App.vue b/src/App.vue index c1add7b6..c1c6ccbc 100644 --- a/src/App.vue +++ b/src/App.vue @@ -61,27 +61,34 @@ export default { calendars: state => state.calendars.calendars, }), }, - beforeMount() { + async beforeMount() { // get calendars then get tasks - client.connect({ enableCalDAV: true }).then(() => { - this.$store.dispatch('getCalendars') - .then((calendars) => { - // No calendars? Create a new one! - if (calendars.length === 0) { - let color = '#0082C9' - if (this.$OCA.Theming) { - color = this.$OCA.Theming.color - } - this.$store.dispatch('appendCalendar', { displayName: this.$t('tasks', 'Tasks'), color }) - .then(() => { - this.fetchTasks() - }) - // else, let's get those tasks! - } else { - this.fetchTasks() - } - }) + await client.connect({ enableCalDAV: true }) + await this.$store.dispatch('fetchCurrentUserPrincipal') + const calendars = await this.$store.dispatch('getCalendars') + const owners = [] + calendars.forEach((calendar) => { + if (owners.indexOf(calendar.owner) === -1) { + owners.push(calendar.owner) + } + }) + owners.forEach((owner) => { + this.$store.dispatch('fetchPrincipalByUrl', { + url: owner, + }) }) + // No calendars? Create a new one! + if (calendars.length === 0) { + let color = '#0082C9' + if (this.$OCA.Theming) { + color = this.$OCA.Theming.color + } + await this.$store.dispatch('appendCalendar', { displayName: this.$t('tasks', 'Tasks'), color }) + this.fetchTasks() + // else, let's get those tasks! + } else { + this.fetchTasks() + } }, methods: { /** diff --git a/src/components/AppNavigation/ListItemCalendar.vue b/src/components/AppNavigation/ListItemCalendar.vue index 05dc7c21..48b1f2d2 100644 --- a/src/components/AppNavigation/ListItemCalendar.vue +++ b/src/components/AppNavigation/ListItemCalendar.vue @@ -37,18 +37,18 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. <AppNavigationIconBullet slot="icon" :color="calendar.color" /> <template v-if="!deleteTimeout" slot="counter"> - <AppNavigationCounter> - {{ calendarCount(calendar.id) | counterFormatter }} - </AppNavigationCounter> - <Actions v-if="!calendar.readOnly"> + <Actions v-if="calendar.canBeShared"> <ActionButton :icon="sharingIconClass" @click="toggleShare"> {{ sharedWithTooltip }} </ActionButton> </Actions> - <Avatar v-if="calendar.isSharedWithMe && calendar.loadedOwnerPrincipal" :user="calendar.ownerUserId" :display-name="calendar.ownerDisplayname" /> - <div v-if="calendar.isSharedWithMe && !calendar.loadedOwnerPrincipal" class="icon icon-loading" /> + <Avatar v-if="calendar.isSharedWithMe && loadedOwnerPrincipal" :user="ownerUserId" :display-name="ownerDisplayname" /> + <div v-if="calendar.isSharedWithMe && !loadedOwnerPrincipal" class="icon icon-loading" /> + <AppNavigationCounter v-if="calendarCount"> + {{ calendarCount | counterFormatter }} + </AppNavigationCounter> </template> <template v-if="!deleteTimeout" slot="actions"> @@ -75,13 +75,12 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. {{ $t('tasks', 'Download') }} </ActionLink> <ActionButton - v-if="!calendar.readOnly" v-tooltip="{ placement: 'left', boundariesElement: 'body', content: deleteMessage }" - icon="icon-delete" + :icon="calendar.isSharedWithMe ? 'icon-close' : 'icon-delete'" @click="scheduleDelete"> {{ !calendar.isSharedWithMe ? $t('tasks', 'Delete') : $t('tasks', 'Unshare') }} </ActionButton> @@ -91,7 +90,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. <ActionButton icon="icon-history" @click.prevent.stop="cancelDelete"> - {{ $n('tasks', 'Deleting the calendar in {countdown} second', 'Deleting the calendar in {countdown} seconds', countdown, { countdown }) }} + {{ undoDeleteMessage }} </ActionButton> </template> @@ -130,6 +129,7 @@ import Colorpicker from './Colorpicker' import ShareCalendar from './CalendarShare' import ClickOutside from 'vue-click-outside' +import Avatar from '@nextcloud/vue/dist/Components/Avatar' import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem' import AppNavigationCounter from '@nextcloud/vue/dist/Components/AppNavigationCounter' import AppNavigationIconBullet from '@nextcloud/vue/dist/Components/AppNavigationIconBullet' @@ -143,6 +143,7 @@ export default { components: { Colorpicker, ShareCalendar, + Avatar, AppNavigationItem, AppNavigationCounter, AppNavigationIconBullet, @@ -190,16 +191,26 @@ export default { }, computed: { ...mapGetters({ - calendarCount: 'getCalendarCount', + getCalendarCount: 'getCalendarCount', isCalendarNameUsed: 'isCalendarNameUsed', getTask: 'getTaskByUri', + getPrincipalByUrl: 'getPrincipalByUrl', }), + calendarCount() { + return this.getCalendarCount(this.calendar.id) + }, + deleteMessage() { return !this.calendar.isSharedWithMe ? this.$t('tasks', 'This will delete the calendar "{calendar}" and all corresponding events and tasks.', { calendar: this.calendar.displayName }) : this.$t('tasks', 'This will unshare the calendar "{calendar}".', { calendar: this.calendar.displayName }) }, + undoDeleteMessage() { + return !this.calendar.isSharedWithMe + ? this.$n('tasks', 'Deleting the calendar in {countdown} second', 'Deleting the calendar in {countdown} seconds', this.countdown, { countdown: this.countdown }) + : this.$n('tasks', 'Unsharing the calendar in {countdown} second', 'Unsharing the calendar in {countdown} seconds', this.countdown, { countdown: this.countdown }) + }, sharingIconClass() { if (this.calendar.shares.length) { return 'icon-shared' @@ -233,6 +244,28 @@ export default { }) : '' // disable the tooltip }, + /** + * Whether or not the information about the owner principal was loaded + * + * @returns {Boolean} + */ + loadedOwnerPrincipal() { + return this.getPrincipalByUrl(this.calendar.owner) !== undefined + }, + ownerUserId() { + const principal = this.getPrincipalByUrl(this.calendar.owner) + if (principal) { + return principal.userId + } + return '' + }, + ownerDisplayname() { + const principal = this.getPrincipalByUrl(this.calendar.owner) + if (principal) { + return principal.displayname + } + return '' + }, }, methods: { ...mapActions([ diff --git a/src/components/TheDetails.vue b/src/components/TheDetails.vue index 67625776..03e4bc5d 100644 --- a/src/components/TheDetails.vue +++ b/src/components/TheDetails.vue @@ -22,7 +22,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. <template> <div class="content-wrapper"> <div v-if="task" - :class="{'disabled': task.calendar.readOnly}" + :class="{'disabled': readOnly}" class="flex-container"> <div :class="{'editing': edit=='summary'}" class="title"> <span class="detail-checkbox"> @@ -30,10 +30,10 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. type="checkbox" class="checkbox" name="detailsToggleCompleted" - :class="{'disabled': task.calendar.readOnly}" + :class="{'disabled': readOnly}" :checked="task.completed" :aria-checked="task.completed" - :disabled="task.calendar.readOnly" + :disabled="readOnly" :aria-label="$t('tasks', 'Task is completed')" @click="toggleCompleted(task)"> <label :for="'detailsToggleCompleted_' + task.uid" /> @@ -56,16 +56,16 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. </div> <TaskStatusDisplay :task="task" /> <button class="reactive inline" @click="togglePinned(task)"> - <span :class="[{'disabled': task.calendar.readOnly}, iconPinned]" class="icon" /> + <span :class="[{'disabled': readOnly}, iconPinned]" class="icon" /> </button> <button class="reactive inline" @click="toggleStarred(task)"> - <span :class="[{'disabled': task.calendar.readOnly}, iconStar]" + <span :class="[{'disabled': readOnly}, iconStar]" class="icon" /> </button> </div> <div class="body"> <ul class="sections"> - <li v-show="!task.calendar.readOnly || task.start" + <li v-show="!readOnly || task.start" :class="{'date': task.startMoment.isValid(), 'editing': edit=='start', 'high': overdue(task.startMoment)}" class="section detail-start"> <div v-click-outside="() => finishEditing('start')" @@ -109,7 +109,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. </button> </div> </li> - <li v-show="!task.calendar.readOnly || task.due" + <li v-show="!readOnly || task.due" :class="{'date': task.dueMoment.isValid(), 'editing': edit=='due', 'high': overdue(task.dueMoment)}" class="section detail-date"> <div v-click-outside="() => finishEditing('due')" @@ -160,10 +160,10 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. type="checkbox" class="checkbox" name="isAllDayPossible" - :class="{'disabled': task.calendar.readOnly}" + :class="{'disabled': readOnly}" :aria-checked="allDay" :checked="allDay" - :disabled="task.calendar.readOnly" + :disabled="readOnly" @click="toggleAllDay(task)"> <label for="isAllDayPossible"> <span>{{ $t('tasks', 'All day') }}</span> @@ -182,7 +182,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. :value="task.calendar" :multiple="false" :allow-empty="false" - :disabled="task.calendar.readOnly" + :disabled="readOnly" track-by="id" :placeholder="$t('tasks', 'Select a calendar')" label="displayName" @@ -206,7 +206,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. :value="classSelect.find( _ => _.type === task.class )" :multiple="false" :allow-empty="false" - :disabled="task.calendar.readOnly" + :disabled="readOnly || task.calendar.isSharedWithMe" track-by="type" :placeholder="$t('tasks', 'Select a classification')" label="displayName" @@ -218,7 +218,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. </div> </div> </li> - <li v-show="!task.calendar.readOnly || task.status" + <li v-show="!readOnly || task.status" class="section detail-class reactive"> <div v-click-outside="() => finishEditing('status')" class="section-content" @@ -231,7 +231,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. :value="statusSelect.find( _ => _.type === task.status )" :multiple="false" :allow-empty="false" - :disabled="task.calendar.readOnly" + :disabled="readOnly" track-by="type" :placeholder="$t('tasks', 'Select a status')" label="displayName" @@ -243,7 +243,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. </div> </div> </li> - <li v-show="!task.calendar.readOnly || task.priority" + <li v-show="!readOnly || task.priority" :class="[{'editing': edit=='priority', 'date': task.priority>0}, priorityClass]" class="section detail-priority"> <div v-click-outside="() => finishEditing('priority')" @@ -279,7 +279,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. </button> </div> </li> - <li v-show="!task.calendar.readOnly || task.complete" + <li v-show="!readOnly || task.complete" :class="{'editing': edit=='complete', 'date': task.complete>0}" class="section detail-complete"> <div v-click-outside="() => finishEditing('complete')" @@ -315,7 +315,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. </button> </div> </li> - <li v-show="!task.calendar.readOnly || task.categories.length>0" :class="{'active': task.categories.length>0}" class="section detail-categories"> + <li v-show="!readOnly || task.categories.length>0" :class="{'active': task.categories.length>0}" class="section detail-categories"> <div class="section-content"> <span class="section-icon"> <span :class="[iconCategories]" class="icon detail-categories" /> @@ -325,7 +325,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. v-model="task.categories" :multiple="true" :searchable="true" - :disabled="task.calendar.readOnly" + :disabled="readOnly" :options="task.categories" :placeholder="$t('tasks', 'Select categories')" :taggable="true" @@ -337,7 +337,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. </div> </div> </li> - <li v-show="!task.calendar.readOnly || task.note" class="section detail-note"> + <li v-show="!readOnly || task.note" class="section detail-note"> <div class="section-content note"> <div v-click-outside="() => finishEditing('note')" class="note-body selectable" @@ -359,7 +359,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>. </ul> </div> <div class="footer"> - <button :style="{visibility: task.calendar.readOnly ? 'hidden' : 'visible'}" + <button :style="{visibility: readOnly ? 'hidden' : 'visible'}" class="close-all reactive inline" @click="removeTask"> <span class="icon icon-bw icon-trash" /> @@ -447,6 +447,16 @@ export default { }, computed: { /** + * Whether we treat the task as read-only. + * We also treat tasks in shared calendars with an access class other than 'PUBLIC' + * as read-only. + * + * @returns {Boolean} Is the task read-only + */ + readOnly() { + return this.task.calendar.readOnly || (this.task.calendar.isSharedWithMe && this.task.class !== 'PUBLIC') + }, + /** * Whether the dates of a task are all-day * When no dates are set, we consider the last used value. * @@ -583,7 +593,7 @@ export default { + (this.task.completed ? ('<br />' + this.$t('tasks', 'Completed {date}', { date: this.task.completedDateMoment.calendar() })) : '') }, isAllDayPossible: function() { - return !this.task.calendar.readOnly && (this.task.due || this.task.start || ['start', 'due'].includes(this.edit)) + return !this.readOnly && (this.task.due || this.task.start || ['start', 'due'].includes(this.edit)) }, priorityClass: function() { if (+this.task.priority > 5) { @@ -763,11 +773,15 @@ export default { if (event && (event.target.classList.contains('mx-datepicker-btn-confirm') || event.target.tagName === 'A')) { return } + // Don't allow to change the access class in calendars shared with me. + if (this.task.calendar.isSharedWithMe && type === 'class') { + return + } // Save possible edits before starting to edit another property. if (this.edit !== type) { this.finishEditing(this.edit) } - if (!this.task.calendar.readOnly && this.edit !== type) { + if (!this.readOnly && this.edit !== type) { this.edit = type this.tmpTask[type] = this.task[type] // If we edit the due or the start date, inintialize it. diff --git a/src/models/principal.js b/src/models/principal.js new file mode 100644 index 00000000..33a8eedb --- /dev/null +++ b/src/models/principal.js @@ -0,0 +1,67 @@ +/** + * Nextcloud - Tasks + * + * @copyright Copyright (c) 2019 Georg Ehrke + * + * @author Georg Ehrke <oc.list@georgehrke.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * Creates a complete principal-object based on given props + * + * @param {Object} props Principal-props already provided + * @returns {any} + */ +export const getDefaultPrincipalObject = (props) => Object.assign({}, { + // Id of the principal + id: '', + // Calendar-user-type. This can be INDIVIDUAL, GROUP, RESOURCE or ROOM + calendarUserType: '', + // E-Mail address of principal used for scheduling + emailAddress: '', + // The principals display-name + displayname: '', + // principalScheme + principalScheme: '', + // The internal user-id in case it is of type INDIVIDUAL and a user + userId: '', + // url to the DAV-principal-resource + url: '', + // The cdav-library object + dav: false, +}, props) + +/** + * converts a dav principal into a vuex object + * + * @param {Object} principal cdav-library Principal object + * @returns {{emailAddress: *, displayname: *, dav: *, id: *, calendarUserType: *, userId: *, url: *}} + */ +export function mapDavToPrincipal(principal) { + return { + id: btoa(principal.url), + calendarUserType: principal.calendarUserType, + principalScheme: principal.principalScheme, + emailAddress: principal.email, + displayname: principal.displayname, + userId: principal.userId, + url: principal.principalUrl, + dav: principal, + } +} diff --git a/src/store/calendars.js b/src/store/calendars.js index 89002afe..bd547bae 100644 --- a/src/store/calendars.js +++ b/src/store/calendars.js @@ -52,6 +52,10 @@ const calendarModel = { dav: false, supportsTasks: true, loadedCompleted: false, + // Whether or not the calendar is shared with me + isSharedWithMe: false, + // Whether or not the calendar can be shared by me + canBeShared: false, } const state = { @@ -62,16 +66,26 @@ const state = { * Maps a dav collection to our calendar object model * * @param {Object} calendar The calendar object from the cdav library + * @param {Object} currentUserPrincipal The principal model of the current user principal * @returns {Object} */ -export function mapDavCollectionToCalendar(calendar) { +export function mapDavCollectionToCalendar(calendar, currentUserPrincipal) { + const owner = calendar.owner + let isSharedWithMe = false + if (!currentUserPrincipal) { + // If the user is not authenticated, the calendar + // will always be marked as shared with them + isSharedWithMe = true + } else { + isSharedWithMe = (owner !== currentUserPrincipal.url) + } return { // get last part of url id: calendar.url.split('/').slice(-2, -1)[0], displayName: calendar.displayname, color: calendar.color, enabled: calendar.enabled !== false, - owner: calendar.owner, + owner, readOnly: !calendar.isWriteable(), tasks: {}, url: calendar.url, @@ -79,6 +93,8 @@ export function mapDavCollectionToCalendar(calendar) { shares: calendar.shares.map(sharee => Object.assign({}, mapDavShareeToSharee(sharee))), supportsTasks: calendar.components.includes('VTODO'), loadedCompleted: false, + isSharedWithMe, + canBeShared: calendar.isShareable(), } } @@ -395,7 +411,7 @@ const actions = { let calendars = await client.calendarHomes[0].findAllCalendars() .then(calendars => { return calendars.map(calendar => { - return mapDavCollectionToCalendar(calendar) + return mapDavCollectionToCalendar(calendar, context.getters.getCurrentUserPrincipal) }) }) @@ -419,7 +435,7 @@ const actions = { async appendCalendar(context, calendar) { return client.calendarHomes[0].createCalendarCollection(calendar.displayName, calendar.color, ['VTODO']) .then((response) => { - calendar = mapDavCollectionToCalendar(response) + calendar = mapDavCollectionToCalendar(response, context.getters.getCurrentUserPrincipal) context.commit('addCalendar', calendar) // Open the calendar router.push({ name: 'calendars', params: { calendarId: calendar.id } }) diff --git a/src/store/principals.js b/src/store/principals.js new file mode 100644 index 00000000..c6de8318 --- /dev/null +++ b/src/store/principals.js @@ -0,0 +1,148 @@ +/** + * Nextcloud - Tasks + * + * @author Georg Ehrke + * @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com> + * + * @author Raimund Schlüßler + * @copyright 2020 Raimund Schlüßler <raimund.schluessler@mailbox.org> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 program. If not, see <http://www.gnu.org/licenses/>. + * + */ +import Vue from 'vue' +import client from '../services/cdav' +import { getDefaultPrincipalObject, mapDavToPrincipal } from '../models/principal' + +const state = { + principals: [], + principalsById: {}, + currentUserPrincipal: null, +} + +const mutations = { + + /** + * Adds a principal to the state + * + * @param {Object} state The vuex state + * @param {Object} data The destructuring object + * @param {Object} data.principal The principal to add + */ + addPrincipal(state, { principal }) { + const object = getDefaultPrincipalObject(principal) + + if (state.principalsById[object.id]) { + return + } + + state.principals.push(object) + Vue.set(state.principalsById, object.id, object) + }, + + /** + * Adds the current user principal to the state + * + * @param {Object} state The vuex state + * @param {Object} data destructuring object + * @param {String} data.principalId principalId of the current-user-principal + */ + setCurrentUserPrincipal(state, { principalId }) { + state.currentUserPrincipal = principalId + }, +} + +const getters = { + + /** + * Gets a principal object by its url + * + * @param {Object} state the store data + * @returns {function({String}): {Object}} + */ + getPrincipalByUrl: (state) => (url) => state.principals.find((principal) => principal.url === url), + + /** + * Gets a principal object by its id + * + * @param {Object} state the store data + * @returns {function({String}): {Object}} + */ + getPrincipalById: (state) => (id) => state.principalsById[id], + + /** + * Gets the principal object of the current-user-principal + * + * @param {Object} state the store data + * @returns {{Object}} + */ + getCurrentUserPrincipal: (state) => state.principalsById[state.currentUserPrincipal], + + /** + * Gets the email-address of the current-user-principal + * + * @param {Object} state the store data + * @returns {String} + */ + getCurrentUserPrincipalEmail: (state) => state.principalsById[state.currentUserPrincipal].emailAddress, +} + +const actions = { + + /** + * Fetches a principal from the DAV server and commits it to the state + * + * @param {Object} context The vuex context + * @param {String} url The URL of the principal + * @returns {Promise<void>} + */ + async fetchPrincipalByUrl(context, { url }) { + // Don't refetch principals we already have + if (context.getters.getPrincipalByUrl(url)) { + return + } + + const principal = await client.findPrincipal(url) + if (!principal) { + // TODO - handle error + return + } + + context.commit('addPrincipal', { + principal: mapDavToPrincipal(principal), + }) + }, + + /** + * Fetches the current-user-principal + * + * @param {Object} context The vuex context + * @returns {Promise<void>} + */ + async fetchCurrentUserPrincipal(context) { + const currentUserPrincipal = client.currentUserPrincipal + if (!currentUserPrincipal) { + // TODO - handle error + return + } + + const principal = mapDavToPrincipal(currentUserPrincipal) + context.commit('addPrincipal', { principal }) + context.commit('setCurrentUserPrincipal', { principalId: principal.id }) + }, +} + +export default { state, mutations, getters, actions } diff --git a/src/store/store.js b/src/store/store.js index f6278e27..3b6dda57 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -26,6 +26,7 @@ import calendars from './calendars' import collections from './collections' import tasks from './tasks' import settings from './settings' +import principals from './principals' Vue.use(Vuex) @@ -35,5 +36,6 @@ export default new Vuex.Store({ collections, tasks, settings, + principals, }, }) diff --git a/src/store/tasks.js b/src/store/tasks.js index a7228a8c..18235d13 100644 --- a/src/store/tasks.js +++ b/src/store/tasks.js @@ -675,6 +675,10 @@ const actions = { if (task.calendar.readOnly) { return } + // Don't delete tasks in shared calendars with access class not PUBLIC + if (task.calendar.isSharedWithMe && task.class !== 'PUBLIC') { + return + } function deleteTaskFromStore() { context.commit('deleteTask', task) @@ -728,6 +732,10 @@ const actions = { if (task.calendar.readOnly) { return } + // Don't edit tasks in shared calendars with access class not PUBLIC + if (task.calendar.isSharedWithMe && task.class !== 'PUBLIC') { + return + } const vCalendar = ICAL.stringify(task.jCal) @@ -847,6 +855,10 @@ const actions = { if (task.calendar.readOnly) { return } + // Don't edit tasks in shared calendars with access class not PUBLIC + if (task.calendar.isSharedWithMe && task.class !== 'PUBLIC') { + return + } if (task.completed) { await context.dispatch('setPercentComplete', { task: task, complete: 0 }) } else { @@ -912,6 +924,10 @@ const actions = { if (task.calendar.readOnly) { return } + // Don't edit tasks in shared calendars with access class not PUBLIC + if (task.calendar.isSharedWithMe && task.class !== 'PUBLIC') { + return + } context.commit('toggleStarred', task) context.dispatch('scheduleTaskUpdate', task) }, @@ -927,6 +943,10 @@ const actions = { if (task.calendar.readOnly) { return } + // Don't edit tasks in shared calendars with access class not PUBLIC + if (task.calendar.isSharedWithMe && task.class !== 'PUBLIC') { + return + } context.commit('togglePinned', task) context.dispatch('scheduleTaskUpdate', task) }, @@ -1085,6 +1105,10 @@ const actions = { if (task.calendar.readOnly) { return task } + // Don't edit tasks in shared calendars with access class not PUBLIC + if (task.calendar.isSharedWithMe && task.class !== 'PUBLIC') { + return + } context.commit('toggleAllDay', task) if (+context.rootState.settings.settings.allDay !== +task.allDay) { context.dispatch('setSetting', { type: 'allDay', value: +task.allDay }) @@ -1159,7 +1183,10 @@ const actions = { if (task.calendar.readOnly) { return task } - + // Don't move tasks in shared calendars with access class not PUBLIC + if (task.calendar.isSharedWithMe && task.class !== 'PUBLIC') { + return + } // Don't move if source and target calendar are the same. if (task.dav && task.calendar !== calendar) { // Move all subtasks first diff --git a/tests/javascript/unit/setupStore.js b/tests/javascript/unit/setupStore.js index a0919123..6c656298 100644 --- a/tests/javascript/unit/setupStore.js +++ b/tests/javascript/unit/setupStore.js @@ -25,6 +25,7 @@ const calendarsData = [ displayname: 'Calendar 1', color: '#123456', isWriteable: () => { return true }, + isShareable: () => { return true }, shares: [], components: ['VTODO'], calendarQuery: () => { return null }, @@ -177,6 +178,7 @@ END:VCALENDAR`], displayname: 'Calendar 2', color: '#123456', isWriteable: () => { return true }, + isShareable: () => { return true }, shares: [], components: ['VTODO', 'VEVENT'], calendarQuery: () => { return null }, |