From 3fa4240a2411143facb244e274c39f10994997fb Mon Sep 17 00:00:00 2001 From: Grigory Vodyanov Date: Thu, 12 Mar 2026 10:25:01 +0100 Subject: [PATCH] feat: add calendar delegation feature Signed-off-by: Grigory Vodyanov Signed-off-by: Hamza Assisted-by: claude:Opus 4.7 (1M context) --- src/components/AppNavigation/CalendarList.vue | 56 ++- .../CalendarList/CalendarListItem.vue | 61 ++- src/components/AppNavigation/Settings.vue | 13 + .../Settings/SettingsDelegationSection.vue | 440 ++++++++++++++++++ .../AppointmentConfigModal/DurationInput.vue | 2 +- .../Editor/CalendarPickerHeader.vue | 171 ++++++- src/components/Shared/CalendarPicker.vue | 4 + .../Shared/CalendarPickerOption.vue | 45 +- src/models/calendar.js | 5 + src/services/caldavService.js | 14 + src/store/delegation.ts | 231 +++++++++ src/store/principals.js | 12 +- src/views/Calendar.vue | 9 + tests/javascript/unit/models/calendar.test.js | 26 ++ 14 files changed, 1073 insertions(+), 16 deletions(-) create mode 100644 src/components/AppNavigation/Settings/SettingsDelegationSection.vue create mode 100644 src/store/delegation.ts diff --git a/src/components/AppNavigation/CalendarList.vue b/src/components/AppNavigation/CalendarList.vue index 7e32e374dc..ed37317380 100644 --- a/src/components/AppNavigation/CalendarList.vue +++ b/src/components/AppNavigation/CalendarList.vue @@ -101,6 +101,19 @@ :calendar="calendar" /> + + @@ -122,6 +135,9 @@ import CalendarListItemLoadingPlaceholder from './CalendarList/CalendarListItemL import CalendarListNew from './CalendarList/CalendarListNew.vue' import PublicCalendarListItem from './CalendarList/PublicCalendarListItem.vue' import useCalendarsStore from '../../store/calendars.js' +import useDelegationStore from '../../store/delegation.ts' +import usePrincipalsStore from '../../store/principals.js' +import { isAfterVersion } from '../../utils/nextcloudVersion.ts' const limit = pLimit(1) @@ -153,13 +169,14 @@ export default { return { calendars: [], /** - * Calendars sorted by personal, shared, deck, and tasks + * Calendars sorted by personal, shared, deck, and delegated */ sortedCalendars: { personal: [], shared: [], deck: [], tasks: [], + delegated: [], }, disableDragging: false, @@ -168,7 +185,7 @@ export default { }, computed: { - ...mapStores(useCalendarsStore), + ...mapStores(useCalendarsStore, useDelegationStore, usePrincipalsStore), ...mapState(useCalendarsStore, { serverCalendars: 'sortedCalendarsSubscriptions', }), @@ -176,6 +193,35 @@ export default { loadingKeyCalendars() { return this._uid + '-loading-placeholder-calendars' }, + + isDelegationSupported() { + return isAfterVersion(34) + }, + + /** + * Delegated calendars grouped by the delegator (the user who granted + * proxy access), which may differ from each calendar's owner when the + * delegator only has access via a regular share. + * + * @return {Array<{delegatorUrl: string, displayname: string, calendars: object[]}>} + */ + delegatedGroups() { + const groups = new Map() + for (const calendar of this.sortedCalendars.delegated) { + const delegatorUrl = calendar.delegatorUrl || calendar.owner || '' + if (!groups.has(delegatorUrl)) { + const principal = this.principalsStore.getPrincipalByUrl(delegatorUrl) + groups.set(delegatorUrl, { + delegatorUrl, + displayname: principal?.displayname || principal?.userId || '', + readOnly: !!calendar.readOnly, + calendars: [], + }) + } + groups.get(delegatorUrl).calendars.push(calendar) + } + return Array.from(groups.values()) + }, }, watch: { @@ -208,9 +254,15 @@ export default { shared: [], deck: [], tasks: [], + delegated: [], } this.calendars.forEach((calendar) => { + if (calendar.isDelegated) { + this.sortedCalendars.delegated.push(calendar) + return + } + if (calendar.isSharedWithMe) { this.sortedCalendars.shared.push(calendar) return diff --git a/src/components/AppNavigation/CalendarList/CalendarListItem.vue b/src/components/AppNavigation/CalendarList/CalendarListItem.vue index b8cc504872..c2501a76ea 100644 --- a/src/components/AppNavigation/CalendarList/CalendarListItem.vue +++ b/src/components/AppNavigation/CalendarList/CalendarListItem.vue @@ -26,16 +26,36 @@