Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/Editor/InvitationResponseButtons.vue
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export default {
// TODO: What about recurring events? Add new buttons like "Accept this and all future"?
// Currently, this will only accept a single occurrence.
await this.calendarObjectInstanceStore.saveCalendarObjectInstance({
thisAndAllFuture: false,
mode: 'all',
calendarId: this.calendarId,
})
} catch (error) {
Expand Down
27 changes: 22 additions & 5 deletions src/components/Editor/SaveButtons.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,19 @@
</template>
{{ $t('calendar', 'Update') }}
</NcButton>
<NcButton
v-if="showUpdateThisAndFutureButton && !showUpdateOnlyThisButton"
variant="primary"
:disabled="disabled"
@click="saveSeries">
{{ $t('calendar', 'Update all occurrences') }}
</NcButton>
<NcButton
v-if="showUpdateThisAndFutureButton && !showUpdateOnlyThisButton"
variant="primary"
:disabled="disabled"
@click="saveThisAndAllFuture">
{{ $t('calendar', 'Update this and all future') }}
{{ $t('calendar', 'Update this and all future occurrences') }}
</NcButton>
<NcButton
v-if="showUpdateOnlyThisButton && !showUpdateThisAndFutureButton"
Expand All @@ -51,11 +58,17 @@
<template #icon>
<CheckIcon :size="20" />
</template>
<NcActionButton @click="saveSeries">
<template #icon>
<CheckIcon :size="20" />
</template>
{{ $t('calendar', 'Update all occurrences') }}
</NcActionButton>
<NcActionButton @click="saveThisAndAllFuture">
<template #icon>
<CheckAllIcon :size="20" />
</template>
{{ $t('calendar', 'Update this and all future') }}
{{ $t('calendar', 'Update this and all future occurrences') }}
</NcActionButton>
<NcActionButton @click="saveThisOnly">
<template #icon>
Expand Down Expand Up @@ -142,15 +155,19 @@

methods: {
saveThisOnly() {
this.$emit('saveThisOnly')
this.$emit('save-this-only')

Check failure on line 158 in src/components/Editor/SaveButtons.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Custom event name 'save-this-only' must be camelCase
},

saveThisAndAllFuture() {
this.$emit('saveThisAndAllFuture')
this.$emit('save-this-and-all-future')

Check failure on line 162 in src/components/Editor/SaveButtons.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Custom event name 'save-this-and-all-future' must be camelCase
},

saveSeries() {
this.$emit('save-series')

Check failure on line 166 in src/components/Editor/SaveButtons.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Custom event name 'save-series' must be camelCase
},

showMore() {
this.$emit('showMore')
this.$emit('show-more')

Check failure on line 170 in src/components/Editor/SaveButtons.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Custom event name 'show-more' must be camelCase
},
},
}
Expand Down
30 changes: 15 additions & 15 deletions src/mixins/EditorMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -570,12 +570,12 @@ export default {
},
keyboardSaveEvent(event) {
if (event.key === 'Enter' && event.ctrlKey === true && !this.isReadOnly && !this.canCreateRecurrenceException) {
this.saveAndLeave(false)
this.saveAndLeave('single')
}
},
keyboardDeleteEvent(event) {
if (event.key === 'Delete' && event.ctrlKey === true && this.canDelete && !this.canCreateRecurrenceException) {
this.deleteAndLeave(false)
this.deleteAndLeave('single')
}
},
keyboardDuplicateEvent(event) {
Expand All @@ -589,10 +589,10 @@ export default {
/**
* Saves a calendar-object
*
* @param {boolean} thisAndAllFuture Whether to modify only this or this and all future occurrences
* @param {string} mode Modification mode: 'all', 'future', or 'single'
* @return {Promise<void>}
*/
async save(thisAndAllFuture = false) {
async save(mode = 'single') {
if (!this.calendarObject) {
logger.error('Calendar-object not found')
return
Expand All @@ -601,14 +601,14 @@ export default {
return
}
if (this.forceThisAndAllFuture) {
thisAndAllFuture = true
mode = 'future'
}

this.isLoading = true
this.isSaving = true
try {
await this.calendarObjectInstanceStore.saveCalendarObjectInstance({
thisAndAllFuture,
mode,
calendarId: this.calendarId,
})
} catch (error) {
Expand All @@ -627,11 +627,11 @@ export default {
/**
* Saves a calendar-object and closes the editor
*
* @param {boolean} thisAndAllFuture Whether to modify only this or this and all future occurrences
* @param {string} mode Modification mode: 'all', 'future', or 'single'
* @return {Promise<void>}
*/
async saveAndLeave(thisAndAllFuture = false) {
await this.save(thisAndAllFuture)
async saveAndLeave(mode = 'single') {
await this.save(mode)
this.requiresActionOnRouteLeave = false
this.closeEditor()
},
Expand All @@ -648,10 +648,10 @@ export default {
/**
* Deletes a calendar-object
*
* @param {boolean} thisAndAllFuture Whether to delete only this or this and all future occurrences
* @param {string} mode Deletion mode: 'all', 'future', or 'single'
* @return {Promise<void>}
*/
async delete(thisAndAllFuture = false) {
async delete(mode = 'single') {
if (!this.calendarObject) {
logger.error('Calendar-object not found')
return
Expand All @@ -661,17 +661,17 @@ export default {
}

this.isLoading = true
await this.calendarObjectInstanceStore.deleteCalendarObjectInstance({ thisAndAllFuture })
await this.calendarObjectInstanceStore.deleteCalendarObjectInstance({ mode })
this.isLoading = false
},
/**
* Deletes a calendar-object and closes the editor
*
* @param {boolean} thisAndAllFuture Whether to delete only this or this and all future occurrences
* @param {string} mode Deletion mode: 'all', 'future', or 'single'
* @return {Promise<void>}
*/
async deleteAndLeave(thisAndAllFuture = false) {
await this.delete(thisAndAllFuture)
async deleteAndLeave(mode = 'single') {
await this.delete(mode)
this.requiresActionOnRouteLeave = false
this.closeEditor()
},
Expand Down
66 changes: 54 additions & 12 deletions src/store/calendarObjectInstance.js
Original file line number Diff line number Diff line change
Expand Up @@ -1502,31 +1502,68 @@ export default defineStore('calendarObjectInstance', {
* Saves changes made to a single calendar-object-instance
*
* @param {object} data The destructuring object
* @param {boolean} data.thisAndAllFuture Whether or not to save changes for all future occurrences or just this one
* @param {string} data.mode Modification mode: 'all', 'future', or 'single'
* @param {string} data.calendarId The new calendar-id to store it in
* @return {Promise<void>}
*/
async saveCalendarObjectInstance({
thisAndAllFuture,
mode,
calendarId,
}) {
const calendarObjectsStore = useCalendarObjectsStore()

const eventComponent = this.calendarObjectInstance.eventComponent
const calendarObject = this.calendarObject
const isForkedItem = eventComponent.primaryItem !== null

updateAlarms(eventComponent)

if (eventComponent.isDirty()) {
const isForkedItem = eventComponent.primaryItem !== null
if (eventComponent.isDirty() && eventComponent.isRecurring() && mode === 'all' && isForkedItem) {
// Find the master component (without RECURRENCE-ID)
let masterComponent = null
for (const component of calendarObject.calendarComponent.getComponentIterator()) {
if (component.name === eventComponent.name && !component.hasProperty('RECURRENCE-ID')) {
masterComponent = component
break
}
}

if (masterComponent) {
// construct list of properties to clone
const propertyNames = []
for (const property of masterComponent.getPropertyIterator()) {
if (property.name === 'UID' || property.name === 'RECURRENCE-ID' || property.name === 'DTSTART' || property.name === 'DTEND') {
continue
}
propertyNames.push(property.name)
masterComponent.deleteAllProperties(property.name)
}
// clone properties from eventComponent
for (const property of eventComponent.getPropertyIterator()) {
if (propertyNames.indexOf(property.name) === -1) {
continue
}
masterComponent.addProperty(property.clone())
}
// clone alarms
masterComponent.deleteAllComponents('VALARM')
for (const alarm of eventComponent.getAlarmIterator()) {
masterComponent.addComponent(alarm.clone())
}
}

await calendarObjectsStore.updateCalendarObject({ calendarObject })
}

if (eventComponent.isDirty() && mode !== 'all') {
let original = null
let fork = null

// We check if two things apply:
// - primaryItem !== null -> Is this a fork or not?
// - eventComponent.canCreateRecurrenceExceptions() - Can we create a recurrence-exception for this item
if (isForkedItem && eventComponent.canCreateRecurrenceExceptions()) {
[original, fork] = eventComponent.createRecurrenceException(thisAndAllFuture)
[original, fork] = eventComponent.createRecurrenceException(mode === 'future')
}

await calendarObjectsStore.updateCalendarObject({ calendarObject })
Expand Down Expand Up @@ -1582,20 +1619,25 @@ export default defineStore('calendarObjectInstance', {
* Deletes a calendar-object-instance
*
* @param {object} data The destructuring object
* @param {boolean} data.thisAndAllFuture Whether or not to delete all future occurrences or just this one
* @param {string} data.mode Deletion mode: 'all', 'future', or 'single'
* @return {Promise<void>}
*/
async deleteCalendarObjectInstance({ thisAndAllFuture }) {
async deleteCalendarObjectInstance({ mode }) {
const calendarObjectsStore = useCalendarObjectsStore()

const eventComponent = this.calendarObjectInstance.eventComponent
const isRecurrenceSetEmpty = eventComponent.removeThisOccurrence(thisAndAllFuture)
const calendarObject = this.calendarObject

// Singleton event or deleting all occurrences - delete the whole calendar-object
if (!eventComponent.isRecurring() || mode === 'all') {
await calendarObjectsStore.deleteCalendarObject({ calendarObject: this.calendarObject })
return
}

// Recurring event - remove this occurrence or this and all future
const isRecurrenceSetEmpty = eventComponent.removeThisOccurrence(mode === 'future')
if (isRecurrenceSetEmpty) {
await calendarObjectsStore.deleteCalendarObject({ calendarObject })
await calendarObjectsStore.deleteCalendarObject({ calendarObject: this.calendarObject })
} else {
await calendarObjectsStore.updateCalendarObject({ calendarObject })
await calendarObjectsStore.updateCalendarObject({ calendarObject: this.calendarObject })
}
},

Expand Down
38 changes: 22 additions & 16 deletions src/views/EditFull.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@
:isNew="isNew"
:isReadOnly="isReadOnly"
:forceThisAndAllFuture="forceThisAndAllFuture"
@saveThisOnly="prepareAccessForAttachments(false)"
@saveThisAndAllFuture="prepareAccessForAttachments(true)" />
@saveThisOnly="prepareAccessForAttachments('single')"
@saveThisAndAllFuture="prepareAccessForAttachments('future')"
@saveSeries="prepareAccessForAttachments('all')" />
<div class="app-full__actions__inner" :class="[{ 'app-full__actions__inner__readonly': isReadOnly }]">
<NcActions>
<NcActionLink v-if="!hideEventExport && hasDownloadURL && !isNew" :href="downloadURL">
Expand All @@ -64,30 +65,35 @@
</template>
{{ $t('calendar', 'Export') }}
</NcActionLink>
<NcActionButton v-if="!canCreateRecurrenceException && !isReadOnly && !isNew" @click="duplicateEvent()">
<NcActionButton v-if="!canCreateRecurrenceException && !isReadOnly && !isNew" type="tertiary" @click="duplicateEvent()">
<template #icon>
<ContentDuplicate :size="20" decorative />
</template>
{{ $t('calendar', 'Duplicate') }}
</NcActionButton>
<NcActionButton v-if="canDelete && !canCreateRecurrenceException && !isNew" @click="deleteAndLeave(false)">
<NcActionButton v-if="canDelete && !canCreateRecurrenceException && !isNew" type="tertiary" @click="deleteAndLeave('single')">
<template #icon>
<Delete :size="20" decorative />
</template>
{{ $t('calendar', 'Delete') }}
</NcActionButton>
<NcActionButton v-if="canDelete && canCreateRecurrenceException && !isNew" @click="deleteAndLeave(false)">
<NcActionButton v-if="canDelete && canCreateRecurrenceException && !isNew" type="tertiary" @click="deleteAndLeave('single')">
<template #icon>
<Delete :size="20" decorative />
</template>
{{ $t('calendar', 'Delete this occurrence') }}
</NcActionButton>
<NcActionSeparator v-if="canDelete && canCreateRecurrenceException && !isNew" />
<NcActionButton v-if="canDelete && canCreateRecurrenceException && !isNew" @click="deleteAndLeave(true)">
<NcActionButton v-if="canDelete && canCreateRecurrenceException && !isNew" type="tertiary" @click="deleteAndLeave('future')">
<template #icon>
<Delete :size="20" decorative />
</template>
{{ $t('calendar', 'Delete this and all future') }}
{{ $t('calendar', 'Delete this and all future occurrences') }}
</NcActionButton>
<NcActionButton v-if="canDelete && canCreateRecurrenceException && !isNew" type="tertiary" @click="deleteAndLeave('all')">
<template #icon>
<Delete :size="20" decorative />
</template>
{{ $t('calendar', 'Delete all occurrences') }}
</NcActionButton>
</NcActions>
</div>
Expand Down Expand Up @@ -291,7 +297,7 @@
<NcButton
variant="primary"
:disabled="showPreloader"
@click="acceptAttachmentsModal(thisAndAllFuture)">
@click="acceptAttachmentsModal()">
{{ t('calendar', 'Invite') }}
</NcButton>
</div>
Expand Down Expand Up @@ -413,7 +419,7 @@
IconVideo,
HelpCircleIcon,
NcActions,
NcActionSeparator,

Check failure on line 422 in src/views/EditFull.vue

View workflow job for this annotation

GitHub Actions / NPM lint

The "NcActionSeparator" component has been registered but not used
Close,
},

Expand All @@ -423,7 +429,7 @@

data() {
return {
thisAndAllFuture: false,
saveMode: 'single',
doNotShare: false,
showModal: false,
showModalNewAttachments: [],
Expand Down Expand Up @@ -687,7 +693,7 @@
this.showModal = false
this.showModalNewAttachments = []
this.showModalUsers = []
this.saveEvent(this.thisAndAllFuture)
this.saveEvent(this.saveMode)
}, 500)
// trigger save event after make each attachment access
// 1) if !isPrivate get attachments NOT SHARED and SharedType is empry -> API ADD SHARE
Expand All @@ -711,8 +717,8 @@
return name.split('/').pop()
},

prepareAccessForAttachments(thisAndAllFuture = false) {
this.thisAndAllFuture = thisAndAllFuture
prepareAccessForAttachments(mode = false) {
this.saveMode = mode
const newAttachments = this.calendarObjectInstance.attachments.filter((attachment) => {
// get only new attachments
// TODO get NOT only new attachments =) Maybe we should filter all attachments without share-type, 'cause event can be private and AFTER save owner could add new participant
Expand All @@ -732,14 +738,14 @@
return false
})
} else {
this.saveEvent(thisAndAllFuture)
this.saveEvent(this.saveMode)
}
},

saveEvent(thisAndAllFuture = false) {
saveEvent(mode = 'single') {
// if there is new attachments and !private, then make modal with users and files/
// maybe check shared access before add file
this.saveAndLeave(thisAndAllFuture)
this.saveAndLeave(mode)
this.calendarObjectInstance.attachments = this.calendarObjectInstance.attachments.map((attachment) => {
if (attachment.isNew) {
delete attachment.isNew
Expand Down
Loading
Loading