diff --git a/src/__mocks__/capabilities.ts b/src/__mocks__/capabilities.ts index ae7f47262a4..48fc9036bca 100644 --- a/src/__mocks__/capabilities.ts +++ b/src/__mocks__/capabilities.ts @@ -93,7 +93,11 @@ export const mockedCapabilities: Capabilities = { 'schedule-meeting', 'edit-draft-poll', 'conversation-creation-all', + 'important-conversations', + 'unbind-conversation', + 'sip-direct-dialin', 'dashboard-event-rooms', + 'mutual-calendar-events', 'upcoming-reminders', // Conditional features 'message-expiration', @@ -115,6 +119,11 @@ export const mockedCapabilities: Capabilities = { 'chat-summary-api', 'call-notification-state-api', 'schedule-meeting', + 'conversation-creation-all', + 'important-conversations', + 'sip-direct-dialin', + 'dashboard-event-rooms', + 'mutual-calendar-events', 'upcoming-reminders', ], config: { diff --git a/src/components/CalendarEventsDialog.vue b/src/components/CalendarEventsDialog.vue index 3248d782fb8..49f79b9c00f 100644 --- a/src/components/CalendarEventsDialog.vue +++ b/src/components/CalendarEventsDialog.vue @@ -31,6 +31,7 @@ import { useIsMobile } from '@nextcloud/vue/composables/useIsMobile' import usernameToColor from '@nextcloud/vue/functions/usernameToColor' import SelectableParticipant from './BreakoutRoomsEditor/SelectableParticipant.vue' +import CalendarEventSmall from './UIShared/CalendarEventSmall.vue' import ContactSelectionBubble from './UIShared/ContactSelectionBubble.vue' import SearchBox from './UIShared/SearchBox.vue' import TransitionWrapper from './UIShared/TransitionWrapper.vue' @@ -339,24 +340,13 @@ async function submitNewMeeting() { @@ -404,6 +451,29 @@ function joinFields(firstSubstring?: string | null, secondSubstring?: string | n } } + &__events { + // To align with NcAppSidebarTab content width + margin-inline: 10px; + + &-list { + --item-height: calc(2lh + var(--default-grid-baseline) * 3); + display: flex; + flex-direction: column; + margin: calc(var(--default-grid-baseline) / 2); + line-height: 20px; + max-height: calc(4.5 * var(--item-height) + 4 * var(--default-grid-baseline)); + overflow-y: auto; + + & > * { + margin-inline: calc(var(--default-grid-baseline) / 2); + + &:not(:last-child) { + border-bottom: 1px solid var(--color-border-dark); + } + } + } + } + &__profile-action { // Override NcActionLink styles :deep(.action-link__longtext) { diff --git a/src/components/UIShared/CalendarEventSmall.vue b/src/components/UIShared/CalendarEventSmall.vue new file mode 100644 index 00000000000..ea40edcec90 --- /dev/null +++ b/src/components/UIShared/CalendarEventSmall.vue @@ -0,0 +1,95 @@ + + + + + + + diff --git a/src/services/groupwareService.ts b/src/services/groupwareService.ts index e5d53751ec3..e462ce36aea 100644 --- a/src/services/groupwareService.ts +++ b/src/services/groupwareService.ts @@ -7,6 +7,7 @@ import axios from '@nextcloud/axios' import { generateOcsUrl } from '@nextcloud/router' import type { + getMutualEventsResponse, OutOfOfficeResponse, UpcomingEventsResponse, scheduleMeetingParams, @@ -33,6 +34,15 @@ const getUserAbsence = async (userId: string): OutOfOfficeResponse => { return axios.get(generateOcsUrl('/apps/dav/api/v1/outOfOffice/{userId}/now', { userId })) } +/** + * Get information about mutual events for a given 1-1 conversation. + * + * @param token The conversation token + */ +const getMutualEvents = async function(token: string): getMutualEventsResponse { + return axios.get(generateOcsUrl('apps/spreed/api/v4/room/{token}/mutual-events', { token })) +} + /** * Schedule a new meeting for a given conversation. * @@ -58,6 +68,7 @@ const scheduleMeeting = async function(token: string, { calendarUri, start, end, } export { + getMutualEvents, getUpcomingEvents, getUserAbsence, scheduleMeeting, diff --git a/src/stores/groupware.ts b/src/stores/groupware.ts index c0e0d99e3a1..edfb8a16599 100644 --- a/src/stores/groupware.ts +++ b/src/stores/groupware.ts @@ -16,8 +16,10 @@ import { getDefaultCalendarUri, convertUrlToUri, } from '../services/CalDavClient.ts' +import { hasTalkFeature } from '../services/CapabilitiesManager.ts' import { getUserProfile } from '../services/coreService.ts' import { + getMutualEvents, getUpcomingEvents, getUserAbsence, scheduleMeeting, @@ -26,6 +28,7 @@ import type { ApiErrorResponse, Conversation, DavCalendar, + DashboardEvent, OutOfOfficeResult, UpcomingEvent, UserProfileData, @@ -37,16 +40,20 @@ type State = { calendars: Record, defaultCalendarUri: string | null, upcomingEvents: Record, + mutualEvents: Record, supportProfileInfo: boolean, profileInfo: Record, } +const supportsMutualEvents = hasTalkFeature('local', 'mutual-calendar-events') + export const useGroupwareStore = defineStore('groupware', { state: (): State => ({ absence: {}, calendars: {}, defaultCalendarUri: null, upcomingEvents: {}, + mutualEvents: {}, supportProfileInfo: true, profileInfo: {}, }), @@ -174,6 +181,25 @@ export const useGroupwareStore = defineStore('groupware', { } }, + /** + * Request and parse profile information + * @param conversation The conversation object + */ + async getUserMutualEvents(conversation: Conversation) { + if (!supportsMutualEvents || !conversation.token + || conversation.type !== CONVERSATION.TYPE.ONE_TO_ONE) { + return + } + + // FIXME cache results for 6/24 hours and do not fetch again + try { + const response = await getMutualEvents(conversation.token) + Vue.set(this.mutualEvents, conversation.token, response.data.ocs.data) + } catch (error) { + console.error(error) + } + }, + /** * Clears store for a deleted conversation * @param token The conversation token diff --git a/src/types/index.ts b/src/types/index.ts index e202e903f3f..26da0280231 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -335,8 +335,11 @@ export type { UpcomingEventsResponse, } from './openapi/core/index.ts' +export type DashboardEvent = components['schemas']['DashboardEvent'] + export type scheduleMeetingParams = Required['requestBody']['content']['application/json'] export type scheduleMeetingResponse = ApiResponse +export type getMutualEventsResponse = ApiResponse export type EventTimeRange = { start: number | null