Merge branch 'develop' into chore.dehydrate-login-methods-from-rocketchatjs

This commit is contained in:
Gerzon Z 2022-02-26 11:44:45 -04:00 committed by GitHub
commit 5a4bfc8f54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 298 additions and 207 deletions

View File

@ -24,7 +24,7 @@ interface IMultiSelect {
multiselect?: boolean; multiselect?: boolean;
onSearch?: () => void; onSearch?: () => void;
onClose?: () => void; onClose?: () => void;
inputStyle: object; inputStyle?: object;
value?: any[]; value?: any[];
disabled?: boolean | object; disabled?: boolean | object;
theme: string; theme: string;

View File

@ -3,7 +3,7 @@ import { MarkdownAST } from '@rocket.chat/message-parser';
import { IAttachment } from './IAttachment'; import { IAttachment } from './IAttachment';
import { IReaction } from './IReaction'; import { IReaction } from './IReaction';
import { IUrl } from './IUrl'; import { IUrlFromServer } from './IUrl';
export type MessageType = 'jitsi_call_started' | 'discussion-created' | 'e2e' | 'load_more' | 'rm' | 'uj'; export type MessageType = 'jitsi_call_started' | 'discussion-created' | 'e2e' | 'load_more' | 'rm' | 'uj';
@ -59,22 +59,61 @@ export interface ILastMessage {
status: boolean; status: boolean;
} }
export interface IMessage { interface IMessageFile {
_id: string;
name: string;
type: string;
}
interface IMessageAttachment {
ts: string;
title: string;
title_link: string;
title_link_download: true;
image_dimensions: {
width: number;
height: number;
};
image_preview: string;
image_url: string;
image_type: string;
image_size: number;
type: string;
description: string;
}
export interface IMessageFromServer {
_id: string; _id: string;
rid: string; rid: string;
msg?: string; msg: string;
ts: string | Date; // wm date issue
u: IUserMessage;
_updatedAt: string | Date;
urls: IUrlFromServer[];
mentions: IUserMention[];
channels: IUserChannel[];
md: MarkdownAST;
file: IMessageFile;
files: IMessageFile[];
groupable: false;
attachments: IMessageAttachment[];
}
export interface ILoadMoreMessage {
_id: string;
rid: string;
ts: string;
t: string;
msg: string;
}
export interface IMessage extends IMessageFromServer {
id: string; id: string;
t?: MessageType; t?: MessageType;
ts: string | Date;
u: IUserMessage;
alias?: string; alias?: string;
parseUrls?: boolean; parseUrls?: boolean;
groupable?: boolean;
avatar?: string; avatar?: string;
emoji?: string; emoji?: string;
attachments?: IAttachment[];
urls?: IUrl[];
_updatedAt: string | Date;
status?: number; status?: number;
pinned?: boolean; pinned?: boolean;
starred?: boolean; starred?: boolean;
@ -88,8 +127,6 @@ export interface IMessage {
tcount?: number; tcount?: number;
tlm?: string | Date; tlm?: string | Date;
replies?: string[]; replies?: string[];
mentions?: IUserMention[];
channels?: IUserChannel[];
unread?: boolean; unread?: boolean;
autoTranslate?: boolean; autoTranslate?: boolean;
translations?: ITranslations[]; translations?: ITranslations[];
@ -97,8 +134,9 @@ export interface IMessage {
blocks?: any; blocks?: any;
e2e?: string; e2e?: string;
tshow?: boolean; tshow?: boolean;
md?: MarkdownAST;
subscription?: { id: string }; subscription?: { id: string };
} }
export type TMessageModel = IMessage & Model; export type TMessageModel = IMessage & Model;
export type TTypeMessages = IMessageFromServer | ILoadMoreMessage | IMessage;

View File

@ -30,6 +30,7 @@ export interface IThreadResult {
channels?: IUserChannel[]; channels?: IUserChannel[];
replies?: string[]; replies?: string[];
tcount?: number; tcount?: number;
status?: string;
tlm?: string | Date; tlm?: string | Date;
} }

View File

@ -1,11 +1,3 @@
export interface IUrl {
_id: number;
title: string;
description: string;
image: string;
url: string;
}
export interface IUrlFromServer { export interface IUrlFromServer {
url: string; url: string;
meta: { meta: {
@ -47,3 +39,11 @@ export interface IUrlFromServer {
}; };
ignoreParse: boolean; ignoreParse: boolean;
} }
export interface IUrl extends IUrlFromServer {
_id: number;
title: string;
description: string;
image: string;
url: string;
}

View File

@ -1,10 +1,16 @@
import type { IMessage } from '../../IMessage'; import type { IMessage, IMessageFromServer } from '../../IMessage';
import type { IRoom } from '../../IRoom'; import type { IRoom } from '../../IRoom';
import type { IUser } from '../../IUser'; import type { IUser } from '../../IUser';
export type ChannelsEndpoints = { export type ChannelsEndpoints = {
'channels.files': { 'channels.files': {
GET: (params: { roomId: IRoom['_id']; offset: number; count: number; sort: string; query: string }) => { GET: (params: {
roomId: IRoom['_id'];
offset: number;
count: number;
sort: string | { uploadedAt: number };
query: string;
}) => {
files: IMessage[]; files: IMessage[];
total: number; total: number;
}; };
@ -17,4 +23,9 @@ export type ChannelsEndpoints = {
total: number; total: number;
}; };
}; };
'channels.history': {
GET: (params: { roomId: string; count: number; latest?: string }) => {
messages: IMessageFromServer[];
};
};
}; };

View File

@ -1,10 +1,10 @@
import type { IMessage } from '../../IMessage'; import type { IMessage, IMessageFromServer } from '../../IMessage';
import type { IRoom } from '../../IRoom'; import type { IRoom } from '../../IRoom';
import type { IUser } from '../../IUser'; import type { IUser } from '../../IUser';
export type GroupsEndpoints = { export type GroupsEndpoints = {
'groups.files': { 'groups.files': {
GET: (params: { roomId: IRoom['_id']; count: number; sort: string; query: string }) => { GET: (params: { roomId: IRoom['_id']; count: number; sort: string | { uploadedAt: number }; query: string }) => {
files: IMessage[]; files: IMessage[];
total: number; total: number;
}; };
@ -17,4 +17,9 @@ export type GroupsEndpoints = {
total: number; total: number;
}; };
}; };
'groups.history': {
GET: (params: { roomId: string; count: number; latest?: string }) => {
messages: IMessageFromServer[];
};
};
}; };

View File

@ -1,4 +1,4 @@
import type { IMessage } from '../../IMessage'; import type { IMessage, IMessageFromServer } from '../../IMessage';
import type { IRoom } from '../../IRoom'; import type { IRoom } from '../../IRoom';
import type { IUser } from '../../IUser'; import type { IUser } from '../../IUser';
@ -20,7 +20,7 @@ export type ImEndpoints = {
}; };
}; };
'im.files': { 'im.files': {
GET: (params: { roomId: IRoom['_id']; count: number; sort: string; query: string }) => { GET: (params: { roomId: IRoom['_id']; count: number; sort: string | { uploadedAt: number }; query: string }) => {
files: IMessage[]; files: IMessage[];
total: number; total: number;
}; };
@ -33,4 +33,9 @@ export type ImEndpoints = {
total: number; total: number;
}; };
}; };
'im.history': {
GET: (params: { roomId: string; count: number; latest?: string }) => {
messages: IMessageFromServer[];
};
};
}; };

View File

@ -179,7 +179,7 @@ export type OmnichannelEndpoints = {
departmentId?: ILivechatAgent['_id']; departmentId?: ILivechatAgent['_id'];
offset: number; offset: number;
count: number; count: number;
sort: string; sort: string | { uploadedAt: number };
}) => { }) => {
queue: { queue: {
chats: number; chats: number;

View File

@ -244,7 +244,7 @@ class Encryption {
const threadMessagesToDecrypt = await threadMessagesCollection.query(...whereClause).fetch(); const threadMessagesToDecrypt = await threadMessagesCollection.query(...whereClause).fetch();
// Concat messages/threads/threadMessages // Concat messages/threads/threadMessages
let toDecrypt: (TThreadModel | TThreadMessageModel)[] = [ let toDecrypt: (TThreadModel | TThreadMessageModel | TMessageModel)[] = [
...messagesToDecrypt, ...messagesToDecrypt,
...threadsToDecrypt, ...threadsToDecrypt,
...threadMessagesToDecrypt ...threadMessagesToDecrypt
@ -259,7 +259,7 @@ class Encryption {
newMessage = await this.decryptMessage({ newMessage = await this.decryptMessage({
t, t,
rid, rid,
msg, msg: msg as string,
tmsg tmsg
}); });
} }
@ -464,12 +464,13 @@ class Encryption {
} }
const { rid } = message; const { rid } = message;
const roomE2E = await this.getRoomInstance(rid as string); const roomE2E = await this.getRoomInstance(rid);
return roomE2E.decrypt(message); return roomE2E.decrypt(message);
}; };
// Decrypt multiple messages // Decrypt multiple messages
decryptMessages = (messages: IMessage[]) => Promise.all(messages.map((m: IMessage) => this.decryptMessage(m))); decryptMessages = (messages: Partial<IMessage>[]) =>
Promise.all(messages.map((m: Partial<IMessage>) => this.decryptMessage(m as IMessage)));
// Decrypt multiple subscriptions // Decrypt multiple subscriptions
decryptSubscriptions = (subscriptions: ISubscription[]) => Promise.all(subscriptions.map(s => this.decryptSubscription(s))); decryptSubscriptions = (subscriptions: ISubscription[]) => Promise.all(subscriptions.map(s => this.decryptSubscription(s)));

View File

@ -6,9 +6,9 @@ import { getThreadById } from '../database/services/Thread';
import log from '../../utils/log'; import log from '../../utils/log';
import { Encryption } from '../encryption'; import { Encryption } from '../encryption';
import getSingleMessage from './getSingleMessage'; import getSingleMessage from './getSingleMessage';
import { IThread, TThreadModel } from '../../definitions'; import { IMessage, IThread, TThreadModel } from '../../definitions';
const buildThreadName = (thread: IThread): string | undefined => thread.msg || thread?.attachments?.[0]?.title; const buildThreadName = (thread: IThread | IMessage): string | undefined => thread.msg || thread?.attachments?.[0]?.title;
const getThreadName = async (rid: string, tmid: string, messageId: string): Promise<string | undefined> => { const getThreadName = async (rid: string, tmid: string, messageId: string): Promise<string | undefined> => {
let tmsg: string | undefined; let tmsg: string | undefined;

View File

@ -1,8 +1,8 @@
import { IMessage } from '../../../definitions'; import { IMessage, IThreadResult } from '../../../definitions';
import messagesStatus from '../../../constants/messagesStatus'; import messagesStatus from '../../../constants/messagesStatus';
import normalizeMessage from './normalizeMessage'; import normalizeMessage from './normalizeMessage';
export default (message: IMessage): IMessage => { export default (message: Partial<IMessage> | IThreadResult): Partial<IMessage> | IThreadResult => {
message.status = messagesStatus.SENT; message.status = messagesStatus.SENT;
return normalizeMessage(message); return normalizeMessage(message);
}; };

View File

@ -1,13 +1,12 @@
import moment from 'moment'; import moment from 'moment';
import { MESSAGE_TYPE_LOAD_MORE } from '../../constants/messageTypeLoad'; import { IMessage, MessageType, TMessageModel } from '../../definitions';
import log from '../../utils/log'; import log from '../../utils/log';
import { getMessageById } from '../database/services/Message'; import { getMessageById } from '../database/services/Message';
import roomTypeToApiType, { RoomTypes } from '../rocketchat/methods/roomTypeToApiType';
import sdk from '../rocketchat/services/sdk';
import { generateLoadMoreId } from '../utils'; import { generateLoadMoreId } from '../utils';
import updateMessages from './updateMessages'; import updateMessages from './updateMessages';
import { IMessage, TMessageModel } from '../../definitions';
import sdk from '../rocketchat/services/sdk';
import roomTypeToApiType, { RoomTypes } from '../rocketchat/methods/roomTypeToApiType';
const COUNT = 50; const COUNT = 50;
@ -23,9 +22,8 @@ async function load({ rid: roomId, latest, t }: { rid: string; latest?: string;
} }
// RC 0.48.0 // RC 0.48.0
// @ts-ignore const data = await sdk.get(`${apiType}.history`, params);
const data: any = await sdk.get(`${apiType}.history`, params); if (!data.success) {
if (!data || data.status === 'error') {
return []; return [];
} }
return data.messages; return data.messages;
@ -36,22 +34,22 @@ export default function loadMessagesForRoom(args: {
t: RoomTypes; t: RoomTypes;
latest: string; latest: string;
loaderItem: TMessageModel; loaderItem: TMessageModel;
}): Promise<IMessage[] | []> { }): Promise<Partial<IMessage>[]> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const data = await load(args); const data: Partial<IMessage>[] = await load(args);
if (data?.length) { if (data?.length) {
const lastMessage = data[data.length - 1]; const lastMessage = data[data.length - 1];
const lastMessageRecord = await getMessageById(lastMessage._id); const lastMessageRecord = await getMessageById(lastMessage._id as string);
if (!lastMessageRecord && data.length === COUNT) { if (!lastMessageRecord && data.length === COUNT) {
const loadMoreItem = { const loadMoreMessage = {
_id: generateLoadMoreId(lastMessage._id), _id: generateLoadMoreId(lastMessage._id as string),
rid: lastMessage.rid, rid: lastMessage.rid,
ts: moment(lastMessage.ts).subtract(1, 'millisecond'), ts: moment(lastMessage.ts).subtract(1, 'millisecond').toString(),
t: MESSAGE_TYPE_LOAD_MORE, t: 'load_more' as MessageType,
msg: lastMessage.msg msg: lastMessage.msg
}; };
data.push(loadMoreItem); data.push(loadMoreMessage);
} }
await updateMessages({ rid: args.rid, update: data, loaderItem: args.loaderItem }); await updateMessages({ rid: args.rid, update: data, loaderItem: args.loaderItem });
return resolve(data); return resolve(data);

View File

@ -12,8 +12,8 @@ import { getSubscriptionByRoomId } from '../database/services/Subscription';
interface IUpdateMessages { interface IUpdateMessages {
rid: string; rid: string;
update: IMessage[]; update: Partial<IMessage>[];
remove?: IMessage[]; remove?: Partial<IMessage>[];
loaderItem?: TMessageModel; loaderItem?: TMessageModel;
} }
@ -37,7 +37,7 @@ export default async function updateMessages({
// Decrypt these messages // Decrypt these messages
update = await Encryption.decryptMessages(update); update = await Encryption.decryptMessages(update);
const messagesIds: string[] = [...update.map(m => m._id), ...remove.map(m => m._id)]; const messagesIds: string[] = [...update.map(m => m._id as string), ...remove.map(m => m._id as string)];
const msgCollection = db.get('messages'); const msgCollection = db.get('messages');
const threadCollection = db.get('threads'); const threadCollection = db.get('threads');
const threadMessagesCollection = db.get('thread_messages'); const threadMessagesCollection = db.get('thread_messages');
@ -49,11 +49,11 @@ export default async function updateMessages({
.query(Q.where('subscription_id', rid), Q.where('id', Q.oneOf(messagesIds))) .query(Q.where('subscription_id', rid), Q.where('id', Q.oneOf(messagesIds)))
.fetch(); .fetch();
update = update.map(m => buildMessage(m)); update = update.map(m => buildMessage(m)) as IMessage[];
// filter loaders to delete // filter loaders to delete
let loadersToDelete: TMessageModel[] = allMessagesRecords.filter(i1 => let loadersToDelete: TMessageModel[] = allMessagesRecords.filter(i1 =>
update.find(i2 => i1.id === generateLoadMoreId(i2._id)) update.find(i2 => i1.id === generateLoadMoreId(i2._id as string))
); );
// Delete // Delete

View File

@ -1,8 +1,14 @@
const types = { enum ETypes {
c: 'channels', Channels = 'channels',
d: 'im', Im = 'im',
p: 'groups', Groups = 'groups'
l: 'channels' }
export const types = {
c: ETypes.Channels,
d: ETypes.Im,
p: ETypes.Groups,
l: ETypes.Channels
}; };
// TODO: refactor this // TODO: refactor this

View File

@ -1,7 +1,7 @@
import sdk from './sdk'; import { SubscriptionType } from '../../../definitions';
import { TEAM_TYPE } from '../../../definitions/ITeam'; import { TEAM_TYPE } from '../../../definitions/ITeam';
import roomTypeToApiType, { RoomTypes } from '../methods/roomTypeToApiType'; import roomTypeToApiType, { RoomTypes } from '../methods/roomTypeToApiType';
import { SubscriptionType } from '../../../definitions'; import sdk from './sdk';
export const createChannel = ({ export const createChannel = ({
name, name,
@ -575,7 +575,7 @@ export const ignoreUser = ({ rid, userId, ignore }: { rid: string; userId: strin
// @ts-ignore // @ts-ignore
sdk.get('chat.ignoreUser', { rid, userId, ignore }); sdk.get('chat.ignoreUser', { rid, userId, ignore });
export const toggleArchiveRoom = (roomId: string, t: RoomTypes, archive: boolean): any => { export const toggleArchiveRoom = (roomId: string, t: SubscriptionType, archive: boolean): any => {
if (archive) { if (archive) {
// RC 0.48.0 // RC 0.48.0
// TODO: missing definitions from server // TODO: missing definitions from server

View File

@ -45,7 +45,7 @@ export type ModalStackParamList = {
title: string; title: string;
infoText: string; infoText: string;
nextAction: Function; nextAction: Function;
showAlert: () => void | boolean; showAlert?: () => void | boolean;
isSearch?: boolean; isSearch?: boolean;
onSearch?: Function; onSearch?: Function;
isRadio?: boolean; isRadio?: boolean;

View File

@ -1,11 +1,24 @@
import React from 'react'; import React from 'react';
import { Switch, Text, View } from 'react-native'; import { Switch, Text, TextStyle, View, ViewStyle } from 'react-native';
import PropTypes from 'prop-types';
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors'; import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
import styles from './styles'; import styles from './styles';
const SwitchContainer = React.memo( interface ISwitchContainer {
value: boolean;
disabled?: boolean;
leftLabelPrimary: string;
leftLabelSecondary: string;
rightLabelPrimary?: string;
rightLabelSecondary?: string;
onValueChange: (value: any) => void;
theme: string;
testID: string;
labelContainerStyle?: ViewStyle;
leftLabelStyle?: TextStyle;
}
const SwitchContainer: React.FC<ISwitchContainer> = React.memo(
({ ({
children, children,
value, value,
@ -21,7 +34,7 @@ const SwitchContainer = React.memo(
leftLabelStyle leftLabelStyle
}) => ( }) => (
<> <>
<View key='switch-container' style={[styles.switchContainer, children && styles.switchMargin]}> <View key='switch-container' style={[styles.switchContainer, !!children && styles.switchMargin]}>
{leftLabelPrimary && ( {leftLabelPrimary && (
<View style={[styles.switchLabelContainer, labelContainerStyle]}> <View style={[styles.switchLabelContainer, labelContainerStyle]}>
<Text style={[styles.switchLabelPrimary, { color: themes[theme].titleText }, leftLabelStyle]}> <Text style={[styles.switchLabelPrimary, { color: themes[theme].titleText }, leftLabelStyle]}>
@ -57,19 +70,4 @@ const SwitchContainer = React.memo(
) )
); );
SwitchContainer.propTypes = {
value: PropTypes.bool,
disabled: PropTypes.bool,
leftLabelPrimary: PropTypes.string,
leftLabelSecondary: PropTypes.string,
rightLabelPrimary: PropTypes.string,
rightLabelSecondary: PropTypes.string,
onValueChange: PropTypes.func,
theme: PropTypes.string,
testID: PropTypes.string,
labelContainerStyle: PropTypes.object,
leftLabelStyle: PropTypes.object,
children: PropTypes.any
};
export default SwitchContainer; export default SwitchContainer;

View File

@ -1,75 +1,98 @@
import React from 'react'; import { Q } from '@nozbe/watermelondb';
import PropTypes from 'prop-types';
import { Alert, Keyboard, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { connect } from 'react-redux';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import ImagePicker from 'react-native-image-crop-picker';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import { Q } from '@nozbe/watermelondb'; import React from 'react';
import { Alert, Keyboard, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native';
import ImagePicker, { Image } from 'react-native-image-crop-picker';
import { connect } from 'react-redux';
import { compareServerVersion } from '../../lib/utils'; import { deleteRoom } from '../../actions/room';
import database from '../../lib/database';
import { deleteRoom as deleteRoomAction } from '../../actions/room';
import KeyboardView from '../../presentation/KeyboardView';
import sharedStyles from '../Styles';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import { showConfirmationAlert, showErrorAlert } from '../../utils/info';
import { LISTENER } from '../../containers/Toast';
import EventEmitter from '../../utils/events';
import RocketChat from '../../lib/rocketchat';
import RCTextInput from '../../containers/TextInput';
import Loading from '../../containers/Loading';
import random from '../../utils/random';
import log, { events, logEvent } from '../../utils/log';
import I18n from '../../i18n';
import StatusBar from '../../containers/StatusBar';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
import { MessageTypeValues } from '../../utils/messageTypes';
import SafeAreaView from '../../containers/SafeAreaView';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
import Loading from '../../containers/Loading';
import SafeAreaView from '../../containers/SafeAreaView';
import StatusBar from '../../containers/StatusBar';
import RCTextInput from '../../containers/TextInput';
import { LISTENER } from '../../containers/Toast';
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
import { IApplicationState, IBaseScreen, ISubscription, TSubscriptionModel } from '../../definitions';
import { ERoomType } from '../../definitions/ERoomType';
import I18n from '../../i18n';
import database from '../../lib/database';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import SwitchContainer from './SwitchContainer'; import RocketChat from '../../lib/rocketchat';
import { compareServerVersion } from '../../lib/utils';
import KeyboardView from '../../presentation/KeyboardView';
import { TSupportedPermissions } from '../../reducers/permissions';
import { ModalStackParamList } from '../../stacks/MasterDetailStack/types';
import { ChatsStackParamList } from '../../stacks/types';
import { withTheme } from '../../theme';
import EventEmitter from '../../utils/events';
import { showConfirmationAlert, showErrorAlert } from '../../utils/info';
import log, { events, logEvent } from '../../utils/log';
import { MessageTypeValues } from '../../utils/messageTypes';
import random from '../../utils/random';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import { IAvatar } from '../ProfileView/interfaces';
import sharedStyles from '../Styles';
import styles from './styles'; import styles from './styles';
import SwitchContainer from './SwitchContainer';
const PERMISSION_SET_READONLY = 'set-readonly'; interface IRoomInfoEditViewState {
const PERMISSION_SET_REACT_WHEN_READONLY = 'set-react-when-readonly'; room: ISubscription;
const PERMISSION_ARCHIVE = 'archive-room'; avatar: IAvatar;
const PERMISSION_UNARCHIVE = 'unarchive-room'; permissions: Record<TSupportedPermissions, string>;
const PERMISSION_DELETE_C = 'delete-c'; name: string;
const PERMISSION_DELETE_P = 'delete-p'; description?: string;
const PERMISSION_DELETE_TEAM = 'delete-team'; topic?: string;
announcement?: string;
joinCode: string;
nameError: any;
saving: boolean;
t: boolean;
ro: boolean;
reactWhenReadOnly?: boolean;
archived: boolean;
systemMessages?: boolean | string[];
enableSysMes?: boolean | string[];
encrypted?: boolean;
}
interface IRoomInfoEditViewProps extends IBaseScreen<ChatsStackParamList | ModalStackParamList, 'RoomInfoEditView'> {
serverVersion?: string;
encryptionEnabled: boolean;
theme: string;
setReadOnlyPermission: string[];
setReactWhenReadOnlyPermission: string[];
archiveRoomPermission: string[];
unarchiveRoomPermission: string[];
deleteCPermission: string[];
deletePPermission: string[];
deleteTeamPermission: string[];
isMasterDetail: boolean;
}
class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfoEditViewState> {
randomValue = random(15);
private querySubscription: any; // Observable dont have unsubscribe prop
private room!: TSubscriptionModel;
private name!: TextInput | null;
private description!: TextInput | null;
private topic!: TextInput | null;
private announcement!: TextInput | null;
private joinCode!: TextInput | null;
class RoomInfoEditView extends React.Component {
static navigationOptions = () => ({ static navigationOptions = () => ({
title: I18n.t('Room_Info_Edit') title: I18n.t('Room_Info_Edit')
}); });
static propTypes = { constructor(props: IRoomInfoEditViewProps) {
navigation: PropTypes.object,
route: PropTypes.object,
deleteRoom: PropTypes.func,
serverVersion: PropTypes.string,
encryptionEnabled: PropTypes.bool,
theme: PropTypes.string,
setReadOnlyPermission: PropTypes.array,
setReactWhenReadOnlyPermission: PropTypes.array,
archiveRoomPermission: PropTypes.array,
unarchiveRoomPermission: PropTypes.array,
deleteCPermission: PropTypes.array,
deletePPermission: PropTypes.array,
deleteTeamPermission: PropTypes.array,
isMasterDetail: PropTypes.bool
};
constructor(props) {
super(props); super(props);
this.state = { this.state = {
room: {}, room: {} as ISubscription,
avatar: {}, avatar: {} as IAvatar,
permissions: {}, permissions: {} as Record<TSupportedPermissions, string>,
name: '', name: '',
description: '', description: '',
topic: '', topic: '',
@ -134,14 +157,15 @@ class RoomInfoEditView extends React.Component {
); );
this.setState({ this.setState({
// @ts-ignore - Solved by migrating the hasPermission function
permissions: { permissions: {
[PERMISSION_SET_READONLY]: result[0], 'set-readonly': result[0],
[PERMISSION_SET_REACT_WHEN_READONLY]: result[1], 'set-react-when-readonly': result[1],
[PERMISSION_ARCHIVE]: result[2], 'archive-room': result[2],
[PERMISSION_UNARCHIVE]: result[3], 'unarchive-room': result[3],
[PERMISSION_DELETE_C]: result[4], 'delete-c': result[4],
[PERMISSION_DELETE_P]: result[5], 'delete-p': result[5],
...(this.room.teamMain && { [PERMISSION_DELETE_TEAM]: result[6] }) ...(this.room.teamMain && { 'delete-team': result[6] })
} }
}); });
} catch (e) { } catch (e) {
@ -149,10 +173,10 @@ class RoomInfoEditView extends React.Component {
} }
}; };
init = room => { init = (room: ISubscription) => {
const { description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired, sysMes, encrypted } = room; const { description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired, encrypted } = room;
const sysMes = room.sysMes as string[];
// fake password just to user knows about it // fake password just to user knows about it
this.randomValue = random(15);
this.setState({ this.setState({
room, room,
name: RocketChat.getRoomTitle(room), name: RocketChat.getRoomTitle(room),
@ -160,7 +184,7 @@ class RoomInfoEditView extends React.Component {
topic, topic,
announcement, announcement,
t: t === 'p', t: t === 'p',
avatar: {}, avatar: {} as IAvatar,
ro, ro,
reactWhenReadOnly, reactWhenReadOnly,
joinCode: joinCodeRequired ? this.randomValue : '', joinCode: joinCodeRequired ? this.randomValue : '',
@ -200,6 +224,7 @@ class RoomInfoEditView extends React.Component {
avatar avatar
} = this.state; } = this.state;
const { joinCodeRequired } = room; const { joinCodeRequired } = room;
const sysMes = room.sysMes as string[];
return !( return !(
room.name === name && room.name === name &&
room.description === description && room.description === description &&
@ -209,8 +234,8 @@ class RoomInfoEditView extends React.Component {
(room.t === 'p') === t && (room.t === 'p') === t &&
room.ro === ro && room.ro === ro &&
room.reactWhenReadOnly === reactWhenReadOnly && room.reactWhenReadOnly === reactWhenReadOnly &&
dequal(room.sysMes, systemMessages) && dequal(sysMes, systemMessages) &&
enableSysMes === (room.sysMes && room.sysMes.length > 0) && enableSysMes === (sysMes && sysMes.length > 0) &&
room.encrypted === encrypted && room.encrypted === encrypted &&
isEmpty(avatar) isEmpty(avatar)
); );
@ -245,7 +270,7 @@ class RoomInfoEditView extends React.Component {
// Clear error objects // Clear error objects
await this.clearErrors(); await this.clearErrors();
const params = {}; const params = {} as any;
// Name // Name
if (room.name !== name) { if (room.name !== name) {
@ -268,7 +293,8 @@ class RoomInfoEditView extends React.Component {
params.roomAnnouncement = announcement; params.roomAnnouncement = announcement;
} }
// Room Type // Room Type
if (room.t !== t) { // This logic is strange to me, since in the code t is boolean, but room.t is string
if (!!room.t !== t) {
params.roomType = t ? 'p' : 'c'; params.roomType = t ? 'p' : 'c';
} }
// Read Only // Read Only
@ -296,7 +322,7 @@ class RoomInfoEditView extends React.Component {
try { try {
await RocketChat.saveRoomSettings(room.rid, params); await RocketChat.saveRoomSettings(room.rid, params);
} catch (e) { } catch (e: any) {
if (e.error === 'error-invalid-room-name') { if (e.error === 'error-invalid-room-name') {
this.setState({ nameError: e }); this.setState({ nameError: e });
} }
@ -317,20 +343,26 @@ class RoomInfoEditView extends React.Component {
deleteTeam = async () => { deleteTeam = async () => {
const { room } = this.state; const { room } = this.state;
const { navigation, deleteCPermission, deletePPermission, deleteRoom } = this.props; const { navigation, deleteCPermission, deletePPermission, dispatch } = this.props;
try { try {
const db = database.active; const db = database.active;
const subCollection = db.get('subscriptions'); const subCollection = db.get('subscriptions');
const teamChannels = await subCollection.query(Q.where('team_id', room.teamId), Q.where('team_main', Q.notEq(true))); const teamChannels = await subCollection.query(
Q.where('team_id', room.teamId as string),
Q.where('team_main', Q.notEq(true))
);
const teamChannelOwner = []; const teamChannelOwner = [];
// @ts-ignore - wm schema type error dont including array
for (let i = 0; i < teamChannels.length; i += 1) { for (let i = 0; i < teamChannels.length; i += 1) {
// @ts-ignore - wm schema type error dont including array
const permissionType = teamChannels[i].t === 'c' ? deleteCPermission : deletePPermission; const permissionType = teamChannels[i].t === 'c' ? deleteCPermission : deletePPermission;
// @ts-ignore - wm schema type error dont including array
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
const permissions = await RocketChat.hasPermission([permissionType], teamChannels[i].rid); const permissions = await RocketChat.hasPermission([permissionType], teamChannels[i].rid);
if (permissions[0]) { if (permissions[0]) {
// @ts-ignore - wm schema type error dont including array
teamChannelOwner.push(teamChannels[i]); teamChannelOwner.push(teamChannels[i]);
} }
} }
@ -340,11 +372,11 @@ class RoomInfoEditView extends React.Component {
title: 'Delete_Team', title: 'Delete_Team',
data: teamChannelOwner, data: teamChannelOwner,
infoText: 'Select_channels_to_delete', infoText: 'Select_channels_to_delete',
nextAction: selected => { nextAction: (selected: Record<string, string>) => {
showConfirmationAlert({ showConfirmationAlert({
message: I18n.t('You_are_deleting_the_team', { team: RocketChat.getRoomTitle(room) }), message: I18n.t('You_are_deleting_the_team', { team: RocketChat.getRoomTitle(room) }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('delete') }), confirmationText: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
onPress: () => deleteRoom('team', room, selected) onPress: () => deleteRoom(ERoomType.t, room, selected)
}); });
} }
}); });
@ -352,10 +384,10 @@ class RoomInfoEditView extends React.Component {
showConfirmationAlert({ showConfirmationAlert({
message: I18n.t('You_are_deleting_the_team', { team: RocketChat.getRoomTitle(room) }), message: I18n.t('You_are_deleting_the_team', { team: RocketChat.getRoomTitle(room) }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('delete') }), confirmationText: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
onPress: () => deleteRoom('team', room) onPress: () => dispatch(deleteRoom(ERoomType.t, room))
}); });
} }
} catch (e) { } catch (e: any) {
log(e); log(e);
showErrorAlert( showErrorAlert(
e.data.error ? I18n.t(e.data.error) : I18n.t('There_was_an_error_while_action', { action: I18n.t('deleting_team') }), e.data.error ? I18n.t(e.data.error) : I18n.t('There_was_an_error_while_action', { action: I18n.t('deleting_team') }),
@ -366,7 +398,7 @@ class RoomInfoEditView extends React.Component {
delete = () => { delete = () => {
const { room } = this.state; const { room } = this.state;
const { deleteRoom } = this.props; const { dispatch } = this.props;
Alert.alert( Alert.alert(
I18n.t('Are_you_sure_question_mark'), I18n.t('Are_you_sure_question_mark'),
@ -379,7 +411,7 @@ class RoomInfoEditView extends React.Component {
{ {
text: I18n.t('Yes_action_it', { action: I18n.t('delete') }), text: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
style: 'destructive', style: 'destructive',
onPress: () => deleteRoom('channel', room) onPress: () => dispatch(deleteRoom(ERoomType.c, room))
} }
], ],
{ cancelable: false } { cancelable: false }
@ -421,14 +453,14 @@ class RoomInfoEditView extends React.Component {
const { room, permissions } = this.state; const { room, permissions } = this.state;
if (room.teamMain) { if (room.teamMain) {
return permissions[PERMISSION_DELETE_TEAM]; return permissions['delete-team'];
} }
if (room.t === 'p') { if (room.t === 'p') {
return permissions[PERMISSION_DELETE_P]; return permissions['delete-p'];
} }
return permissions[PERMISSION_DELETE_C]; return permissions['delete-c'];
}; };
renderSystemMessages = () => { renderSystemMessages = () => {
@ -445,9 +477,9 @@ class RoomInfoEditView extends React.Component {
value: m.value, value: m.value,
text: { text: I18n.t('Hide_type_messages', { type: I18n.t(m.text) }) } text: { text: I18n.t('Hide_type_messages', { type: I18n.t(m.text) }) }
}))} }))}
onChange={({ value }) => this.setState({ systemMessages: value })} onChange={({ value }: { value: boolean }) => this.setState({ systemMessages: value })}
placeholder={{ text: I18n.t('Hide_System_Messages') }} placeholder={{ text: I18n.t('Hide_System_Messages') }}
value={systemMessages} value={systemMessages as string[]}
context={BLOCK_CONTEXT.FORM} context={BLOCK_CONTEXT.FORM}
multiselect multiselect
theme={theme} theme={theme}
@ -466,7 +498,7 @@ class RoomInfoEditView extends React.Component {
}; };
try { try {
const response = await ImagePicker.openPicker(options); const response: Image = await ImagePicker.openPicker(options);
this.setState({ avatar: { url: response.path, data: `data:image/jpeg;base64,${response.data}`, service: 'upload' } }); this.setState({ avatar: { url: response.path, data: `data:image/jpeg;base64,${response.data}`, service: 'upload' } });
} catch (e) { } catch (e) {
console.log(e); console.log(e);
@ -477,27 +509,27 @@ class RoomInfoEditView extends React.Component {
this.setState({ avatar: { data: null } }); this.setState({ avatar: { data: null } });
}; };
toggleRoomType = value => { toggleRoomType = (value: boolean) => {
logEvent(events.RI_EDIT_TOGGLE_ROOM_TYPE); logEvent(events.RI_EDIT_TOGGLE_ROOM_TYPE);
this.setState(({ encrypted }) => ({ t: value, encrypted: value && encrypted })); this.setState(({ encrypted }) => ({ t: value, encrypted: value && encrypted }));
}; };
toggleReadOnly = value => { toggleReadOnly = (value: boolean) => {
logEvent(events.RI_EDIT_TOGGLE_READ_ONLY); logEvent(events.RI_EDIT_TOGGLE_READ_ONLY);
this.setState({ ro: value }); this.setState({ ro: value });
}; };
toggleReactions = value => { toggleReactions = (value: boolean) => {
logEvent(events.RI_EDIT_TOGGLE_REACTIONS); logEvent(events.RI_EDIT_TOGGLE_REACTIONS);
this.setState({ reactWhenReadOnly: value }); this.setState({ reactWhenReadOnly: value });
}; };
toggleHideSystemMessages = value => { toggleHideSystemMessages = (value: boolean) => {
logEvent(events.RI_EDIT_TOGGLE_SYSTEM_MSG); logEvent(events.RI_EDIT_TOGGLE_SYSTEM_MSG);
this.setState(({ systemMessages }) => ({ enableSysMes: value, systemMessages: value ? systemMessages : [] })); this.setState(({ systemMessages }) => ({ enableSysMes: value, systemMessages: value ? systemMessages : [] }));
}; };
toggleEncrypted = value => { toggleEncrypted = (value: boolean) => {
logEvent(events.RI_EDIT_TOGGLE_ENCRYPTED); logEvent(events.RI_EDIT_TOGGLE_ENCRYPTED);
this.setState({ encrypted: value }); this.setState({ encrypted: value });
}; };
@ -538,15 +570,15 @@ class RoomInfoEditView extends React.Component {
<TouchableOpacity <TouchableOpacity
style={styles.avatarContainer} style={styles.avatarContainer}
onPress={this.changeAvatar} onPress={this.changeAvatar}
disabled={compareServerVersion(serverVersion, 'lowerThan', '3.6.0')}> disabled={compareServerVersion(serverVersion || '', 'lowerThan', '3.6.0')}>
<Avatar <Avatar
type={room.t} type={room.t}
text={room.name} text={room.name}
avatar={avatar?.url} avatar={avatar?.url}
isStatic={avatar?.url} isStatic={avatar?.url}
rid={isEmpty(avatar) && room.rid} rid={isEmpty(avatar) ? room.rid : undefined}
size={100}> size={100}>
{compareServerVersion(serverVersion, 'lowerThan', '3.6.0') ? null : ( {serverVersion && compareServerVersion(serverVersion, 'lowerThan', '3.6.0') ? undefined : (
<TouchableOpacity <TouchableOpacity
style={[styles.resetButton, { backgroundColor: themes[theme].dangerColor }]} style={[styles.resetButton, { backgroundColor: themes[theme].dangerColor }]}
onPress={this.resetAvatar}> onPress={this.resetAvatar}>
@ -563,7 +595,7 @@ class RoomInfoEditView extends React.Component {
value={name} value={name}
onChangeText={value => this.setState({ name: value })} onChangeText={value => this.setState({ name: value })}
onSubmitEditing={() => { onSubmitEditing={() => {
this.description.focus(); this.description?.focus();
}} }}
error={nameError} error={nameError}
theme={theme} theme={theme}
@ -577,7 +609,7 @@ class RoomInfoEditView extends React.Component {
value={description} value={description}
onChangeText={value => this.setState({ description: value })} onChangeText={value => this.setState({ description: value })}
onSubmitEditing={() => { onSubmitEditing={() => {
this.topic.focus(); this.topic?.focus();
}} }}
theme={theme} theme={theme}
testID='room-info-edit-view-description' testID='room-info-edit-view-description'
@ -590,7 +622,7 @@ class RoomInfoEditView extends React.Component {
value={topic} value={topic}
onChangeText={value => this.setState({ topic: value })} onChangeText={value => this.setState({ topic: value })}
onSubmitEditing={() => { onSubmitEditing={() => {
this.announcement.focus(); this.announcement?.focus();
}} }}
theme={theme} theme={theme}
testID='room-info-edit-view-topic' testID='room-info-edit-view-topic'
@ -603,7 +635,7 @@ class RoomInfoEditView extends React.Component {
value={announcement} value={announcement}
onChangeText={value => this.setState({ announcement: value })} onChangeText={value => this.setState({ announcement: value })}
onSubmitEditing={() => { onSubmitEditing={() => {
this.joinCode.focus(); this.joinCode?.focus();
}} }}
theme={theme} theme={theme}
testID='room-info-edit-view-announcement' testID='room-info-edit-view-announcement'
@ -647,19 +679,19 @@ class RoomInfoEditView extends React.Component {
rightLabelPrimary={I18n.t('Read_Only')} rightLabelPrimary={I18n.t('Read_Only')}
rightLabelSecondary={I18n.t('Only_authorized_users_can_write_new_messages')} rightLabelSecondary={I18n.t('Only_authorized_users_can_write_new_messages')}
onValueChange={this.toggleReadOnly} onValueChange={this.toggleReadOnly}
disabled={!permissions[PERMISSION_SET_READONLY] || room.broadcast} disabled={!permissions['set-readonly'] || room.broadcast}
theme={theme} theme={theme}
testID='room-info-edit-view-ro' testID='room-info-edit-view-ro'
/> />
{ro && !room.broadcast ? ( {ro && !room.broadcast ? (
<SwitchContainer <SwitchContainer
value={reactWhenReadOnly} value={reactWhenReadOnly as boolean}
leftLabelPrimary={I18n.t('No_Reactions')} leftLabelPrimary={I18n.t('No_Reactions')}
leftLabelSecondary={I18n.t('Reactions_are_disabled')} leftLabelSecondary={I18n.t('Reactions_are_disabled')}
rightLabelPrimary={I18n.t('Allow_Reactions')} rightLabelPrimary={I18n.t('Allow_Reactions')}
rightLabelSecondary={I18n.t('Reactions_are_enabled')} rightLabelSecondary={I18n.t('Reactions_are_enabled')}
onValueChange={this.toggleReactions} onValueChange={this.toggleReactions}
disabled={!permissions[PERMISSION_SET_REACT_WHEN_READONLY]} disabled={!permissions['set-react-when-readonly']}
theme={theme} theme={theme}
testID='room-info-edit-view-react-when-ro' testID='room-info-edit-view-react-when-ro'
/> />
@ -670,9 +702,9 @@ class RoomInfoEditView extends React.Component {
<View style={[styles.divider, { borderColor: themes[theme].separatorColor }]} /> <View style={[styles.divider, { borderColor: themes[theme].separatorColor }]} />
] ]
: null} : null}
{!compareServerVersion(serverVersion, 'lowerThan', '3.0.0') ? ( {serverVersion && !compareServerVersion(serverVersion, 'lowerThan', '3.0.0') ? (
<SwitchContainer <SwitchContainer
value={enableSysMes} value={enableSysMes as boolean}
leftLabelPrimary={I18n.t('Hide_System_Messages')} leftLabelPrimary={I18n.t('Hide_System_Messages')}
leftLabelSecondary={ leftLabelSecondary={
enableSysMes enableSysMes
@ -689,7 +721,7 @@ class RoomInfoEditView extends React.Component {
) : null} ) : null}
{encryptionEnabled ? ( {encryptionEnabled ? (
<SwitchContainer <SwitchContainer
value={encrypted} value={encrypted as boolean}
disabled={!t} disabled={!t}
leftLabelPrimary={I18n.t('Encrypted')} leftLabelPrimary={I18n.t('Encrypted')}
leftLabelSecondary={I18n.t('End_to_end_encrypted_room')} leftLabelSecondary={I18n.t('End_to_end_encrypted_room')}
@ -735,12 +767,12 @@ class RoomInfoEditView extends React.Component {
styles.buttonInverted, styles.buttonInverted,
styles.buttonContainer_inverted, styles.buttonContainer_inverted,
archived archived
? !permissions[PERMISSION_UNARCHIVE] && sharedStyles.opacity5 ? !permissions['unarchive-room'] && sharedStyles.opacity5
: !permissions[PERMISSION_ARCHIVE] && sharedStyles.opacity5, : !permissions['archive-room'] && sharedStyles.opacity5,
{ flex: 1, marginLeft: 10, borderColor: dangerColor } { flex: 1, marginLeft: 10, borderColor: dangerColor }
]} ]}
onPress={this.toggleArchive} onPress={this.toggleArchive}
disabled={archived ? !permissions[PERMISSION_UNARCHIVE] : !permissions[PERMISSION_ARCHIVE]} disabled={archived ? !permissions['unarchive-room'] : !permissions['archive-room']}
testID={archived ? 'room-info-edit-view-unarchive' : 'room-info-edit-view-archive'}> testID={archived ? 'room-info-edit-view-unarchive' : 'room-info-edit-view-archive'}>
<Text style={[styles.button, styles.button_inverted, { color: dangerColor }]}> <Text style={[styles.button, styles.button_inverted, { color: dangerColor }]}>
{archived ? I18n.t('UNARCHIVE') : I18n.t('ARCHIVE')} {archived ? I18n.t('UNARCHIVE') : I18n.t('ARCHIVE')}
@ -771,21 +803,17 @@ class RoomInfoEditView extends React.Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: IApplicationState) => ({
serverVersion: state.server.version, serverVersion: state.server.version as string,
encryptionEnabled: state.encryption.enabled, encryptionEnabled: state.encryption.enabled,
setReadOnlyPermission: state.permissions[PERMISSION_SET_READONLY], setReadOnlyPermission: state.permissions['set-readonly'] as string[],
setReactWhenReadOnlyPermission: state.permissions[PERMISSION_SET_REACT_WHEN_READONLY], setReactWhenReadOnlyPermission: state.permissions['set-react-when-readonly'] as string[],
archiveRoomPermission: state.permissions[PERMISSION_ARCHIVE], archiveRoomPermission: state.permissions['archive-room'] as string[],
unarchiveRoomPermission: state.permissions[PERMISSION_UNARCHIVE], unarchiveRoomPermission: state.permissions['unarchive-room'] as string[],
deleteCPermission: state.permissions[PERMISSION_DELETE_C], deleteCPermission: state.permissions['delete-c'] as string[],
deletePPermission: state.permissions[PERMISSION_DELETE_P], deletePPermission: state.permissions['delete-p'] as string[],
deleteTeamPermission: state.permissions[PERMISSION_DELETE_TEAM], deleteTeamPermission: state.permissions['delete-team'] as string[],
isMasterDetail: state.app.isMasterDetail isMasterDetail: state.app.isMasterDetail
}); });
const mapDispatchToProps = dispatch => ({ export default connect(mapStateToProps)(withTheme(RoomInfoEditView));
deleteRoom: (roomType, room, selected) => dispatch(deleteRoomAction(roomType, room, selected))
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(RoomInfoEditView));

View File

@ -287,7 +287,7 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
} }
if (update && update.length) { if (update && update.length) {
update = update.map(m => buildMessage(m)); update = update.map(m => buildMessage(m)) as IThreadResult[];
// filter threads // filter threads
threadsToCreate = update.filter(i1 => !allThreadsRecords.find((i2: { id: string }) => i1._id === i2.id)); threadsToCreate = update.filter(i1 => !allThreadsRecords.find((i2: { id: string }) => i1._id === i2.id));
threadsToUpdate = allThreadsRecords.filter((i1: { id: string }) => update.find(i2 => i1.id === i2._id)); threadsToUpdate = allThreadsRecords.filter((i1: { id: string }) => update.find(i2 => i1.id === i2._id));