Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/tasks.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaimund Schlüßler <raimund.schluessler@mailbox.org>2020-02-11 00:42:58 +0300
committerRaimund Schlüßler <raimund.schluessler@mailbox.org>2020-02-11 01:09:59 +0300
commita103fd7a840d4c5c4ba531235b6276857d535ec9 (patch)
tree07c3f3896a7ed708a215a4a0bfa33b34eb62df26
parent00618b4e3f49ad05829b46a68812043abb8af171 (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.scss5
-rw-r--r--src/App.vue45
-rw-r--r--src/components/AppNavigation/ListItemCalendar.vue53
-rw-r--r--src/components/TheDetails.vue56
-rw-r--r--src/models/principal.js67
-rw-r--r--src/store/calendars.js24
-rw-r--r--src/store/principals.js148
-rw-r--r--src/store/store.js2
-rw-r--r--src/store/tasks.js29
-rw-r--r--tests/javascript/unit/setupStore.js2
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 },