diff --git a/lib/Chat/AutoComplete/SearchPlugin.php b/lib/Chat/AutoComplete/SearchPlugin.php index 32e5eb6f3e6..59863819732 100644 --- a/lib/Chat/AutoComplete/SearchPlugin.php +++ b/lib/Chat/AutoComplete/SearchPlugin.php @@ -67,7 +67,7 @@ public function search($search, $limit, $offset, ISearchResult $searchResult): b $groupIds = []; /** @var array $cloudIds */ $cloudIds = []; - /** @var array $emailAttendees */ + /** @var array $emailAttendees */ $emailAttendees = []; /** @var list $guestAttendees */ $guestAttendees = []; @@ -85,7 +85,7 @@ public function search($search, $limit, $offset, ISearchResult $searchResult): b if ($attendee->getActorType() === Attendee::ACTOR_GUESTS) { $guestAttendees[] = $attendee; } elseif ($attendee->getActorType() === Attendee::ACTOR_EMAILS) { - $emailAttendees[$attendee->getActorId()] = $attendee->getDisplayName(); + $emailAttendees[$attendee->getActorId()] = $attendee; } elseif ($attendee->getActorType() === Attendee::ACTOR_USERS) { $userIds[$attendee->getActorId()] = $attendee->getDisplayName(); } elseif ($attendee->getActorType() === Attendee::ACTOR_FEDERATED_USERS) { @@ -307,7 +307,7 @@ protected function searchGuests(string $search, array $attendees, ISearchResult /** * @param string $search - * @param array $attendees + * @param array $attendees * @param ISearchResult $searchResult */ protected function searchEmails(string $search, array $attendees, ISearchResult $searchResult): void { @@ -325,25 +325,25 @@ protected function searchEmails(string $search, array $attendees, ISearchResult } $matches = $exactMatches = []; - foreach ($attendees as $actorId => $displayName) { + foreach ($attendees as $actorId => $attendee) { if ($currentSessionHash === $actorId) { // Do not suggest the current guest continue; } - $displayName = $displayName ?: $this->l->t('Guest'); + $displayName = $attendee->getDisplayName() ?: $this->l->t('Guest'); if ($search === '') { - $matches[] = $this->createEmailResult($actorId, $displayName); + $matches[] = $this->createEmailResult($actorId, $displayName, $attendee->getInvitedCloudId()); continue; } if (strtolower($displayName) === $search) { - $exactMatches[] = $this->createEmailResult($actorId, $displayName); + $exactMatches[] = $this->createEmailResult($actorId, $displayName, $attendee->getInvitedCloudId()); continue; } if (stripos($displayName, $search) !== false) { - $matches[] = $this->createEmailResult($actorId, $displayName); + $matches[] = $this->createEmailResult($actorId, $displayName, $attendee->getInvitedCloudId()); continue; } } @@ -386,13 +386,19 @@ protected function createGuestResult(string $actorId, string $name): array { ]; } - protected function createEmailResult(string $actorId, string $name): array { - return [ + protected function createEmailResult(string $actorId, string $name, ?string $email): array { + $data = [ 'label' => $name, 'value' => [ 'shareType' => 'email', 'shareWith' => 'email/' . $actorId, ], ]; + + if ($email) { + $data['details'] = ['email' => $email]; + } + + return $data; } } diff --git a/lib/Controller/ChatController.php b/lib/Controller/ChatController.php index 3c0c10b3cd5..032b3c96d32 100644 --- a/lib/Controller/ChatController.php +++ b/lib/Controller/ChatController.php @@ -1479,6 +1479,10 @@ protected function prepareResultArray(array $results, array $statuses): array { $data['statusClearAt'] = $statuses[$data['id']]->getClearAt()?->getTimestamp(); } + if ($type === Attendee::ACTOR_EMAILS && isset($result['details']) && $this->participant->hasModeratorPermissions()) { + $data['details'] = $result['details']['email']; + } + $output[] = $data; } } diff --git a/src/components/MessagesList/MessagesGroup/Message/Message.vue b/src/components/MessagesList/MessagesGroup/Message/Message.vue index 69e9a71d415..a67cba9ce2b 100644 --- a/src/components/MessagesList/MessagesGroup/Message/Message.vue +++ b/src/components/MessagesList/MessagesGroup/Message/Message.vue @@ -109,7 +109,7 @@ import MessageBody from './MessagePart/MessageBody.vue' import Poll from './MessagePart/Poll.vue' import Reactions from './MessagePart/Reactions.vue' -import { CONVERSATION, PARTICIPANT } from '../../../../constants.js' +import { CONVERSATION, MENTION, PARTICIPANT } from '../../../../constants.js' import { getTalkConfig } from '../../../../services/CapabilitiesManager.ts' import { EventBus } from '../../../../services/EventBus.ts' import { useChatExtrasStore } from '../../../../stores/chatExtras.js' @@ -262,7 +262,7 @@ export default { messageParameters: this.message.messageParameters, messageType: this.message.messageType }) - if (type === 'user' || type === 'call' || type === 'guest' || type === 'user-group' || type === 'group') { + if (Object.values(MENTION.TYPE).includes(type)) { richParameters[p] = { component: Mention, props: { diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/Mention.vue b/src/components/MessagesList/MessagesGroup/Message/MessagePart/Mention.vue index a67292531b2..d7567c8937d 100644 --- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/Mention.vue +++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/Mention.vue @@ -21,6 +21,7 @@ import { loadState } from '@nextcloud/initial-state' import NcUserBubble from '@nextcloud/vue/dist/Components/NcUserBubble.js' import { useIsDarkTheme } from '@nextcloud/vue/dist/Composables/useIsDarkTheme.js' +import { MENTION } from '../../../../../constants.js' import { getConversationAvatarOcsUrl, getUserProxyAvatarOcsUrl } from '../../../../../services/avatarService.ts' export default { @@ -69,16 +70,16 @@ export default { computed: { isMentionToAll() { - return this.type === 'call' + return this.type === MENTION.TYPE.CALL }, isGroupMention() { - return this.type === 'user-group' || this.type === 'group' + return [MENTION.TYPE.USERGROUP, MENTION.TYPE.GROUP].includes(this.type) }, isMentionToGuest() { - return this.type === 'guest' + return this.type === MENTION.TYPE.GUEST || this.type === MENTION.TYPE.EMAIL }, isRemoteUser() { - return this.type === 'user' && this.server !== '' + return [MENTION.TYPE.USER, MENTION.TYPE.FEDERATED_USER].includes(this.type) && this.server !== '' }, isCurrentGuest() { // On mention bubbles the id is actually "guest/ACTOR_ID" for guests @@ -86,8 +87,10 @@ export default { // while storing them as "… @id …" in chat messages. // So when comparing a guest we have to prefix "guest/" // when comparing the id + // However we do not prefix email accounts, so simply compare id return this.$store.getters.isActorGuest() - && this.id === ('guest/' + this.$store.getters.getActorId()) + && (this.id === ('guest/' + this.$store.getters.getActorId()) + || this.id === this.$store.getters.getActorId()) }, isCurrentUser() { if (this.isRemoteUser) { diff --git a/src/composables/useChatMentions.ts b/src/composables/useChatMentions.ts index bb184995d15..38e0a5139bb 100644 --- a/src/composables/useChatMentions.ts +++ b/src/composables/useChatMentions.ts @@ -69,6 +69,9 @@ function useChatMentionsComposable(token: Ref): ReturnType { } else if (possibleMention.source === ATTENDEE.ACTOR_TYPE.GUESTS) { chatMention.icon = 'icon-user-forced-white' chatMention.subline = t('spreed', 'Guest') + } else if (possibleMention.source === ATTENDEE.ACTOR_TYPE.EMAILS) { + chatMention.icon = 'icon-user-forced-white' + chatMention.subline = possibleMention?.details ?? t('spreed', 'Guest') } else if (possibleMention.source === ATTENDEE.ACTOR_TYPE.FEDERATED_USERS) { chatMention.icon = 'icon-user-forced-white' chatMention.iconUrl = getUserProxyAvatarOcsUrl(token, possibleMention.id, isDarkTheme, 64) diff --git a/src/constants.js b/src/constants.js index c79743fb5c1..384bb111fd8 100644 --- a/src/constants.js +++ b/src/constants.js @@ -306,3 +306,16 @@ export const FEDERATION = { ACCEPTED: 1, }, } + +export const MENTION = { + TYPE: { + CALL: 'call', + USER: 'user', + GUEST: 'guest', + EMAIL: 'email', + USERGROUP: 'user-group', + // Parsed to another types + FEDERATED_USER: 'federated_user', + GROUP: 'group', + }, +} diff --git a/src/utils/textParse.ts b/src/utils/textParse.ts index e342592c0c3..f3ffad3b86e 100644 --- a/src/utils/textParse.ts +++ b/src/utils/textParse.ts @@ -5,6 +5,7 @@ import { getBaseUrl } from '@nextcloud/router' +import { MENTION } from '../constants.js' import type { ChatMessage, Mention } from '../types/index.ts' /** @@ -18,14 +19,20 @@ function parseMentions(text: string, parameters: ChatMessage['messageParameters' const value: Mention = parameters[key] as Mention let mention = '' - if (key.startsWith('mention-call') && value.type === 'call') { + if (key.startsWith('mention-call') && value.type === MENTION.TYPE.CALL) { mention = '@all' - } else if (key.startsWith('mention-federated-user') && value.type === 'user') { - const server = value?.server ?? getBaseUrl().replace('https://', '') + } else if (key.startsWith('mention-federated-user') + && [MENTION.TYPE.USER, MENTION.TYPE.FEDERATED_USER].includes(value.type)) { + const server = (value?.server ?? getBaseUrl()).replace('https://', '') mention = `@"federated_user/${value.id}@${server}"` - } else if (key.startsWith('mention-group') && value.type === 'user-group') { + } else if (key.startsWith('mention-group') + && [MENTION.TYPE.USERGROUP, MENTION.TYPE.GROUP].includes(value.type)) { mention = `@"group/${value.id}"` - } else if (key.startsWith('mention-user') && value.type === 'user') { + } else if (key.startsWith('mention-guest') && value.type === MENTION.TYPE.GUEST) { + mention = `@"${value.id}"` + } else if (key.startsWith('mention-email') && value.type === MENTION.TYPE.EMAIL) { + mention = `@"email/${value.id}"` + } else if (key.startsWith('mention-user') && value.type === MENTION.TYPE.USER) { mention = value.id.includes(' ') ? `@"${value.id}"` : `@${value.id}` }