diff --git a/src/__mocks__/capabilities.ts b/src/__mocks__/capabilities.ts index 4e188fbfaeb..fc2f6fe0998 100644 --- a/src/__mocks__/capabilities.ts +++ b/src/__mocks__/capabilities.ts @@ -2,7 +2,7 @@ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -import type { Capabilities } from '../types' +import type { Capabilities } from '../types/index.ts' export const mockedCapabilities: Capabilities = { spreed: { diff --git a/src/components/BreakoutRoomsEditor/SelectableParticipant.vue b/src/components/BreakoutRoomsEditor/SelectableParticipant.vue index f129885b163..49859b689fc 100644 --- a/src/components/BreakoutRoomsEditor/SelectableParticipant.vue +++ b/src/components/BreakoutRoomsEditor/SelectableParticipant.vue @@ -15,6 +15,7 @@ :source="participant.source || participant.actorType" disable-menu disable-tooltip + :preloaded-user-status="preloadedUserStatus" show-user-status /> @@ -36,7 +37,7 @@ import IconCheck from 'vue-material-design-icons/Check.vue' import AvatarWrapper from '../AvatarWrapper/AvatarWrapper.vue' -import { getStatusMessage } from '../../utils/userStatus.js' +import { getPreloadedUserStatus, getStatusMessage } from '../../utils/userStatus.ts' export default { name: 'SelectableParticipant', @@ -73,6 +74,10 @@ export default { }, }, + preloadedUserStatus() { + return getPreloadedUserStatus(this.participant) + }, + participantStatus() { return getStatusMessage(this.participant) }, diff --git a/src/components/ConversationIcon.vue b/src/components/ConversationIcon.vue index 069972eb772..bd61da75aa1 100644 --- a/src/components/ConversationIcon.vue +++ b/src/components/ConversationIcon.vue @@ -66,6 +66,7 @@ import { useIsDarkTheme } from '../composables/useIsDarkTheme.ts' import { AVATAR, CONVERSATION } from '../constants.js' import { getConversationAvatarOcsUrl } from '../services/avatarService.ts' import { hasTalkFeature } from '../services/CapabilitiesManager.ts' +import { getPreloadedUserStatus } from '../utils/userStatus.ts' const supportsAvatar = hasTalkFeature('local', 'avatar') @@ -151,15 +152,11 @@ export default { }, preloadedUserStatus() { - if (!this.hideUserStatus && Object.prototype.hasOwnProperty.call(this.item, 'statusMessage')) { - // We preloaded the status - return { - status: this.item.status || null, - message: this.item.statusMessage || null, - icon: this.item.statusIcon || null, - } + if (this.hideUserStatus) { + return undefined } - return undefined + + return getPreloadedUserStatus(this.item) }, menuContainer() { diff --git a/src/components/RightSidebar/Participants/Participant.vue b/src/components/RightSidebar/Participants/Participant.vue index 06cda434ed8..86803fd541a 100644 --- a/src/components/RightSidebar/Participants/Participant.vue +++ b/src/components/RightSidebar/Participants/Participant.vue @@ -371,7 +371,7 @@ import { import { hasTalkFeature } from '../../../services/CapabilitiesManager.ts' import { formattedTime } from '../../../utils/formattedTime.ts' import { readableNumber } from '../../../utils/readableNumber.ts' -import { getStatusMessage } from '../../../utils/userStatus.js' +import { getPreloadedUserStatus, getStatusMessage } from '../../../utils/userStatus.ts' export default { name: 'Participant', @@ -826,23 +826,7 @@ export default { }, preloadedUserStatus() { - if (Object.prototype.hasOwnProperty.call(this.participant, 'statusMessage')) { - // We preloaded the status when via participants API - return { - status: this.participant.status || null, - message: this.participant.statusMessage || null, - icon: this.participant.statusIcon || null, - } - } - if (Object.prototype.hasOwnProperty.call(this.participant, 'status')) { - // We preloaded the status when via search API - return { - status: this.participant.status.status || null, - message: this.participant.status.message || null, - icon: this.participant.status.icon || null, - } - } - return undefined + return getPreloadedUserStatus(this.participant) }, attendeePermissions() { diff --git a/src/components/TopBar/TopBar.vue b/src/components/TopBar/TopBar.vue index 2688c4ad369..b11be197388 100644 --- a/src/components/TopBar/TopBar.vue +++ b/src/components/TopBar/TopBar.vue @@ -141,7 +141,7 @@ import { AVATAR, CONVERSATION } from '../../constants.js' import BrowserStorage from '../../services/BrowserStorage.js' import { getTalkConfig } from '../../services/CapabilitiesManager.ts' import { useChatExtrasStore } from '../../stores/chatExtras.js' -import { getStatusMessage } from '../../utils/userStatus.js' +import { getStatusMessage } from '../../utils/userStatus.ts' import { localCallParticipantModel, localMediaModel } from '../../utils/webrtc/index.js' export default { diff --git a/src/composables/useSortParticipants.js b/src/composables/useSortParticipants.js index c7a9e9a01b9..d78dff8b4da 100644 --- a/src/composables/useSortParticipants.js +++ b/src/composables/useSortParticipants.js @@ -8,7 +8,7 @@ import { computed } from 'vue' import { useStore } from './useStore.js' import { ATTENDEE, PARTICIPANT } from '../constants.js' -import { isDoNotDisturb } from '../utils/userStatus.js' +import { isDoNotDisturb } from '../utils/userStatus.ts' const MODERATOR_TYPES = [PARTICIPANT.TYPE.OWNER, PARTICIPANT.TYPE.MODERATOR, PARTICIPANT.TYPE.GUEST_MODERATOR] diff --git a/src/services/CapabilitiesManager.ts b/src/services/CapabilitiesManager.ts index bea0524b00b..660c6fe9e06 100644 --- a/src/services/CapabilitiesManager.ts +++ b/src/services/CapabilitiesManager.ts @@ -10,7 +10,7 @@ import { t } from '@nextcloud/l10n' import { getRemoteCapabilities } from './federationService.ts' import BrowserStorage from '../services/BrowserStorage.js' import { useTalkHashStore } from '../stores/talkHash.js' -import type { Capabilities, Conversation, JoinRoomFullResponse } from '../types' +import type { Capabilities, Conversation, JoinRoomFullResponse } from '../types/index.ts' type Config = Capabilities['spreed']['config'] type RemoteCapability = Capabilities & Partial<{ hash: string }> diff --git a/src/services/avatarService.ts b/src/services/avatarService.ts index 29008ea1510..35a7a14e819 100644 --- a/src/services/avatarService.ts +++ b/src/services/avatarService.ts @@ -10,8 +10,8 @@ import type { deleteAvatarResponse, setEmojiAvatarParams, setEmojiAvatarResponse, - setFileAvatarResponse -} from '../types' + setFileAvatarResponse, +} from '../types/index.ts' const getConversationAvatarOcsUrl = function(token: string, isDarkTheme: boolean, avatarVersion?: string): string { return generateOcsUrl('apps/spreed/api/v1/room/{token}/avatar' + (isDarkTheme ? '/dark' : '') + (avatarVersion ? '?v={avatarVersion}' : ''), { token, avatarVersion }) diff --git a/src/services/banService.ts b/src/services/banService.ts index 2649ed51e2f..d6919404d1f 100644 --- a/src/services/banService.ts +++ b/src/services/banService.ts @@ -11,7 +11,7 @@ import type { banActorParams, banActorResponse, unbanActorResponse, -} from '../types' +} from '../types/index.ts' /** * Get information about configured bans for this conversation diff --git a/src/services/botsService.ts b/src/services/botsService.ts index de62bf0adc7..e247b7a7734 100644 --- a/src/services/botsService.ts +++ b/src/services/botsService.ts @@ -6,7 +6,7 @@ import axios from '@nextcloud/axios' import { generateOcsUrl } from '@nextcloud/router' -import type { Bot, getBotsResponse, getBotsAdminResponse, enableBotResponse, disableBotResponse } from '../types' +import type { Bot, getBotsResponse, getBotsAdminResponse, enableBotResponse, disableBotResponse } from '../types/index.ts' /** * Get information about available bots for this instance diff --git a/src/services/breakoutRoomsService.ts b/src/services/breakoutRoomsService.ts index a084bc39d36..730d59dccb8 100644 --- a/src/services/breakoutRoomsService.ts +++ b/src/services/breakoutRoomsService.ts @@ -21,7 +21,7 @@ import type { stopBreakoutRoomsResponse, switchToBreakoutRoomParams, switchToBreakoutRoomResponse, -} from '../types' +} from '../types/index.ts' /** * Create breakout rooms for a given conversation diff --git a/src/services/federationService.ts b/src/services/federationService.ts index 754ae52755d..574ce43e693 100644 --- a/src/services/federationService.ts +++ b/src/services/federationService.ts @@ -6,7 +6,7 @@ import axios from '@nextcloud/axios' import { generateOcsUrl } from '@nextcloud/router' -import type { acceptShareResponse, getSharesResponse, rejectShareResponse, getCapabilitiesResponse } from '../types' +import type { acceptShareResponse, getSharesResponse, rejectShareResponse, getCapabilitiesResponse } from '../types/index.ts' /** * Fetches list of shares for a current user diff --git a/src/services/messagesService.ts b/src/services/messagesService.ts index 21b4def34e0..2387efb8a5f 100644 --- a/src/services/messagesService.ts +++ b/src/services/messagesService.ts @@ -25,8 +25,8 @@ import type { receiveMessagesParams, receiveMessagesResponse, setReadMarkerParams, - setReadMarkerResponse -} from '../types' + setReadMarkerResponse, +} from '../types/index.ts' type ReceiveMessagesPayload = Partial & { token: string } type GetMessageContextPayload = getMessageContextParams & { token: string, messageId: number } diff --git a/src/services/reactionsService.ts b/src/services/reactionsService.ts index c7c634d306c..96e14343580 100644 --- a/src/services/reactionsService.ts +++ b/src/services/reactionsService.ts @@ -11,8 +11,8 @@ import type { addReactionResponse, deleteReactionParams, deleteReactionResponse, - getReactionsResponse -} from '../types' + getReactionsResponse, +} from '../types/index.ts' const addReactionToMessage = async function(token: string, messageId: number, selectedEmoji: addReactionParams['reaction'], options: object): addReactionResponse { return axios.post(generateOcsUrl('apps/spreed/api/v1/reaction/{token}/{messageId}', { diff --git a/src/stores/bots.ts b/src/stores/bots.ts index e3efa746837..1df2efe4683 100644 --- a/src/stores/bots.ts +++ b/src/stores/bots.ts @@ -8,7 +8,7 @@ import Vue from 'vue' import { BOT } from '../constants.js' import { disableBotForConversation, enableBotForConversation, getConversationBots } from '../services/botsService.ts' -import type { Bot } from '../types' +import type { Bot } from '../types/index.ts' type State = { bots: Record> diff --git a/src/stores/breakoutRooms.ts b/src/stores/breakoutRooms.ts index d5ddcf067d8..337fc9cbcaa 100644 --- a/src/stores/breakoutRooms.ts +++ b/src/stores/breakoutRooms.ts @@ -31,8 +31,8 @@ import type { broadcastChatMessageParams, configureBreakoutRoomsParams, reorganizeAttendeesParams, - switchToBreakoutRoomParams -} from '../types' + switchToBreakoutRoomParams, +} from '../types/index.ts' type Payload = T & { token: string } type State = { diff --git a/src/stores/federation.ts b/src/stores/federation.ts index dbb9ae67788..1d1ead616d7 100644 --- a/src/stores/federation.ts +++ b/src/stores/federation.ts @@ -12,7 +12,7 @@ import { getBaseUrl } from '@nextcloud/router' import { FEDERATION } from '../constants.js' import { getShares, acceptShare, rejectShare } from '../services/federationService.ts' -import type { Conversation, FederationInvite, NotificationInvite } from '../types' +import type { Conversation, FederationInvite, NotificationInvite } from '../types/index.ts' type State = { pendingShares: Record, diff --git a/src/types/index.ts b/src/types/index.ts index 71147a5dd9e..d9b706301a8 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -55,7 +55,22 @@ export type JoinRoomFullResponse = { } // Participants +export type ParticipantStatus = { + status?: string | null, + message?: string | null, + icon?: string | null, + clearAt?: number | null, +} export type Participant = components['schemas']['Participant'] +export type ParticipantSearchResult = { + id: string, + label: string, + icon: string, + source: string, + subline: string, + shareWithDisplayNameUnique: string, + status: ParticipantStatus | '', +} // Chats export type Mention = RichObject<'server'|'call-type'|'icon-url'> diff --git a/src/utils/getItemTypeFromMessage.ts b/src/utils/getItemTypeFromMessage.ts index f4f0b29545d..9ae3516df10 100644 --- a/src/utils/getItemTypeFromMessage.ts +++ b/src/utils/getItemTypeFromMessage.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ import { SHARED_ITEM } from '../constants.js' -import type { ChatMessage } from '../types' +import type { ChatMessage } from '../types/index.ts' export const getItemTypeFromMessage = function(message: ChatMessage): string { if (message.messageParameters?.object) { diff --git a/src/utils/textParse.ts b/src/utils/textParse.ts index e3ed3be53ff..e342592c0c3 100644 --- a/src/utils/textParse.ts +++ b/src/utils/textParse.ts @@ -5,7 +5,7 @@ import { getBaseUrl } from '@nextcloud/router' -import type { ChatMessage, Mention } from '../types' +import type { ChatMessage, Mention } from '../types/index.ts' /** * Parse message text to return proper formatting for mentions diff --git a/src/utils/userStatus.js b/src/utils/userStatus.js deleted file mode 100644 index c455294cbb9..00000000000 --- a/src/utils/userStatus.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import { t } from '@nextcloud/l10n' -/** - * Generate full status message for user according to its status data - * - * @param {object} userData user data - * @param {string} [userData.status] status of user - * @param {string} [userData.statusIcon] status icon of user - * @param {string} [userData.statusMessage] status message of user - * @return {string} - */ -export function getStatusMessage(userData) { - let status = userData.statusIcon - ? userData.statusIcon + ' ' - : '' - - if (userData.statusMessage) { - status += userData.statusMessage - } else if (userData.status === 'dnd') { - status += t('spreed', 'Do not disturb') - } else if (userData.status === 'away') { - status += t('spreed', 'Away') - } else { - status += '' - } - - return status -} - -/** - * Check if current status is "Do not disturb" - * - * @param {object} userData user data - * @param {string} [userData.status] status of user - * @return {boolean} - */ -export function isDoNotDisturb(userData) { - return userData?.status === 'dnd' -} diff --git a/src/utils/userStatus.ts b/src/utils/userStatus.ts new file mode 100644 index 00000000000..f7e6e8ae8a9 --- /dev/null +++ b/src/utils/userStatus.ts @@ -0,0 +1,80 @@ +/** + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { t } from '@nextcloud/l10n' + +import type { + Conversation, + Participant, + ParticipantSearchResult, + ParticipantStatus, +} from '../types/index.ts' + +/** + * Generate user status object to use as preloaded status with NcAvatar + * + * @param userData user data (from conversation, participant, search result) + */ +export function getPreloadedUserStatus(userData?: Conversation | Participant | ParticipantSearchResult): ParticipantStatus | undefined { + if (!userData || typeof userData !== 'object') { + return undefined + } + + if ('statusMessage' in userData) { + // We preloaded the status when via participants API + return { + status: userData.status || null, + message: userData.statusMessage || null, + icon: userData.statusIcon || null, + } + } + if ('status' in userData && typeof userData.status === 'object') { + // We preloaded the status when via search API + return { + status: userData.status.status || null, + message: userData.status.message || null, + icon: userData.status.icon || null, + } + } + return undefined +} + +/** + * Generate full status message for user according to its status data + * + * @param userData user data + */ +export function getStatusMessage(userData?: Conversation | Participant | ParticipantSearchResult | ''): string { + if (!userData) { + return '' + } + + const userStatus = getPreloadedUserStatus(userData) + + if (!userStatus) { + return '' + } + + let status = userStatus.icon ?? '' + + if (userStatus.message) { + status += ' ' + userStatus.message + } else if (userStatus.status === 'dnd') { + status += ' ' + t('spreed', 'Do not disturb') + } else if (userStatus.status === 'away') { + status += ' ' + t('spreed', 'Away') + } + + return status +} + +/** + * Check if current status is "Do not disturb" + * + * @param userData user data + */ +export function isDoNotDisturb(userData: Conversation | Participant | ParticipantSearchResult): boolean { + return userData?.status === 'dnd' +}