diff --git a/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Settings.ts b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Settings.ts index 31126a71b20e..eb0ed192a282 100644 --- a/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Settings.ts +++ b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Settings.ts @@ -128,6 +128,7 @@ export class RocketChatSettingsAdapter { i18nLabel: 'Federation_Matrix_enabled', i18nDescription: 'Federation_Matrix_enabled_desc', alert: 'Federation_Matrix_Enabled_Alert', + public: true, }); const uniqueId = settings.get('uniqueID') || uuidv4().slice(0, 15).replace(new RegExp('-', 'g'), '_'); diff --git a/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/slash-commands/index.ts b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/slash-commands/index.ts index cb7c734479d6..46b7e4425b89 100644 --- a/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/slash-commands/index.ts +++ b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/slash-commands/index.ts @@ -32,7 +32,7 @@ const executeSlashCommand = async ( return; } - const [command, ...params] = stringParams.split(' '); + const [command, ...params] = stringParams.trim().split(' '); const [rawUserId] = params; const currentUserId = Meteor.userId(); if (!currentUserId || !commands[command]) { diff --git a/apps/meteor/app/lib/server/functions/createDirectRoom.ts b/apps/meteor/app/lib/server/functions/createDirectRoom.ts index 830a17467f1d..0e3c6c12d6fb 100644 --- a/apps/meteor/app/lib/server/functions/createDirectRoom.ts +++ b/apps/meteor/app/lib/server/functions/createDirectRoom.ts @@ -2,8 +2,9 @@ import { AppsEngineException } from '@rocket.chat/apps-engine/definition/excepti import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import type { IUser } from '@rocket.chat/core-typings'; -import { Users, Subscriptions, Rooms } from '@rocket.chat/models'; +import { Subscriptions } from '@rocket.chat/models'; +import { Users, Rooms } from '../../../models/server'; import { Apps } from '../../../apps/server'; import { callbacks } from '../../../../lib/callbacks'; import { settings } from '../../../settings/server'; diff --git a/apps/meteor/client/components/RoomIcon/RoomIcon.tsx b/apps/meteor/client/components/RoomIcon/RoomIcon.tsx index 22bcd3cde2d3..888dcb6d3866 100644 --- a/apps/meteor/client/components/RoomIcon/RoomIcon.tsx +++ b/apps/meteor/client/components/RoomIcon/RoomIcon.tsx @@ -1,8 +1,8 @@ -import { IRoom, isDirectMessageRoom, isOmnichannelRoom } from '@rocket.chat/core-typings'; +import { IRoom, isOmnichannelRoom } from '@rocket.chat/core-typings'; import { Icon } from '@rocket.chat/fuselage'; -import React, { ComponentProps, ReactElement } from 'react'; +import React, { ComponentProps, ReactElement, isValidElement } from 'react'; -import { ReactiveUserStatus } from '../UserStatus'; +import { useRoomIcon } from '../../hooks/useRoomIcon'; import { OmnichannelRoomIcon } from './OmnichannelRoomIcon'; export const RoomIcon = ({ @@ -14,33 +14,19 @@ export const RoomIcon = ({ size?: ComponentProps['size']; placement: 'sidebar' | 'default'; }): ReactElement | null => { - if (room.prid) { - return ; - } - - if (room.teamMain) { - return ; - } + const iconPropsOrReactNode = useRoomIcon(room); if (isOmnichannelRoom(room)) { return ; } - if (isDirectMessageRoom(room)) { - if (room.uids && room.uids.length > 2) { - return ; - } - if (room.uids && room.uids.length > 0) { - return uid !== room.u._id)[0] || room.u._id} />; - } - return ; + + if (isValidElement(iconPropsOrReactNode)) { + return iconPropsOrReactNode; } - switch (room.t) { - case 'p': - return ; - case 'c': - return ; - default: - return null; + if (!iconPropsOrReactNode) { + return null; } + + return ; }; diff --git a/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleFederated.tsx b/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleFederated.tsx new file mode 100644 index 000000000000..470331f3803c --- /dev/null +++ b/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleFederated.tsx @@ -0,0 +1,89 @@ +import { MultiSelectFiltered, Icon, Box, Chip } from '@rocket.chat/fuselage'; +import type { Options } from '@rocket.chat/fuselage'; +import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import React, { memo, ReactElement, useState, ComponentProps } from 'react'; +import { useQuery } from 'react-query'; + +import UserAvatar from '../avatar/UserAvatar'; +import renderOptions from './UserAutoCompleteMultipleOptions'; + +type UserAutoCompleteMultipleFederatedProps = { + onChange: (value: Array) => void; + value: Array; + placeholder?: string; +}; + +export type UserAutoCompleteOptionType = { + name: string; + username: string; + _federated?: boolean; +}; + +type UserAutoCompleteOptions = { + [k: string]: UserAutoCompleteOptionType; +}; + +const matrixRegex = new RegExp('(.*:.*)', 'gi'); + +const UserAutoCompleteMultipleFederated = ({ + onChange, + value, + placeholder, + ...props +}: UserAutoCompleteMultipleFederatedProps): ReactElement => { + const [filter, setFilter] = useState(''); + const [selectedCache, setSelectedCache] = useState({}); + + const debouncedFilter = useDebouncedValue(filter, 1000); + const getUsers = useEndpoint('GET', '/v1/users.autocomplete'); + + const { data } = useQuery(['users.autocomplete', debouncedFilter], async () => { + const users = await getUsers({ selector: JSON.stringify({ term: debouncedFilter }) }); + const options = users.items.map((item): [string, UserAutoCompleteOptionType] => [item.username, item]); + + // Add extra option if filter text matches `username:server` + // Used to add federated users that do not exist yet + if (matrixRegex.test(debouncedFilter)) { + options.unshift([debouncedFilter, { name: debouncedFilter, username: debouncedFilter, _federated: true }]); + } + + return options; + }); + + const options = data || []; + + const onAddSelected: ComponentProps['onSelect'] = ([value]) => { + const cachedOption = options.find(([curVal]) => curVal === value)?.[1]; + if (!cachedOption) { + throw new Error('UserAutoCompleteMultiple - onAddSelected - failed to cache option'); + } + setSelectedCache({ ...selectedCache, [value]: cachedOption }); + }; + + return ( + void }): ReactElement => { + const currentCachedOption = selectedCache[value]; + + return ( + + {currentCachedOption._federated ? : } + + {currentCachedOption.name || currentCachedOption.username} + + + ); + }} + renderOptions={renderOptions(options, onAddSelected)} + options={options.concat(Object.entries(selectedCache)).map(([, item]) => [item.username, item.name || item.username])} + /> + ); +}; + +export default memo(UserAutoCompleteMultipleFederated); diff --git a/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleOption.tsx b/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleOption.tsx new file mode 100644 index 000000000000..e8be27f28c34 --- /dev/null +++ b/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultipleOption.tsx @@ -0,0 +1,31 @@ +import { IUser } from '@rocket.chat/core-typings'; +import { Option, OptionDescription } from '@rocket.chat/fuselage'; +import React, { ReactElement } from 'react'; + +import UserAvatar from '../avatar/UserAvatar'; + +type UserAutoCompleteMultipleOptionProps = { + label: { + _federated?: boolean; + } & Pick; +}; + +const UserAutoCompleteMultipleOption = ({ label, ...props }: UserAutoCompleteMultipleOptionProps): ReactElement => { + const { name, username, _federated } = label; + + return ( +