Chore: add eslint-plugin-react-hooks lib (#4021)
Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
parent
53eb251476
commit
b0d408ebc8
10
.eslintrc.js
10
.eslintrc.js
|
@ -17,7 +17,7 @@ module.exports = {
|
||||||
legacyDecorators: true
|
legacyDecorators: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: ['react', 'jsx-a11y', 'import', 'react-native', '@babel', 'jest'],
|
plugins: ['react', 'jsx-a11y', 'import', 'react-native', '@babel', 'jest', 'react-hooks'],
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
commonjs: true,
|
commonjs: true,
|
||||||
|
@ -148,7 +148,9 @@ module.exports = {
|
||||||
'no-async-promise-executor': [0],
|
'no-async-promise-executor': [0],
|
||||||
'max-classes-per-file': [0],
|
'max-classes-per-file': [0],
|
||||||
'no-multiple-empty-lines': [0],
|
'no-multiple-empty-lines': [0],
|
||||||
'no-sequences': 'off'
|
'no-sequences': 'off',
|
||||||
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'warn'
|
||||||
},
|
},
|
||||||
globals: {
|
globals: {
|
||||||
__DEV__: true
|
__DEV__: true
|
||||||
|
@ -237,7 +239,9 @@ module.exports = {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'new-cap': 'off',
|
'new-cap': 'off',
|
||||||
'lines-between-class-members': 'off'
|
'lines-between-class-members': 'off',
|
||||||
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'warn'
|
||||||
},
|
},
|
||||||
globals: {
|
globals: {
|
||||||
JSX: true
|
JSX: true
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { useContext, memo, useEffect } from 'react';
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import { createStackNavigator } from '@react-navigation/stack';
|
import { createStackNavigator } from '@react-navigation/stack';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
@ -27,21 +27,23 @@ const SetUsernameStack = () => (
|
||||||
|
|
||||||
// App
|
// App
|
||||||
const Stack = createStackNavigator<StackParamList>();
|
const Stack = createStackNavigator<StackParamList>();
|
||||||
const App = React.memo(({ root, isMasterDetail }: { root: string; isMasterDetail: boolean }) => {
|
const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: boolean }) => {
|
||||||
|
const { theme } = useContext(ThemeContext);
|
||||||
|
useEffect(() => {
|
||||||
|
if (root) {
|
||||||
|
const state = Navigation.navigationRef.current?.getRootState();
|
||||||
|
const currentRouteName = getActiveRouteName(state);
|
||||||
|
Navigation.routeNameRef.current = currentRouteName;
|
||||||
|
setCurrentScreen(currentRouteName);
|
||||||
|
}
|
||||||
|
}, [root]);
|
||||||
|
|
||||||
if (!root) {
|
if (!root) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { theme } = React.useContext(ThemeContext);
|
|
||||||
const navTheme = navigationTheme(theme);
|
const navTheme = navigationTheme(theme);
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const state = Navigation.navigationRef.current?.getRootState();
|
|
||||||
const currentRouteName = getActiveRouteName(state);
|
|
||||||
Navigation.routeNameRef.current = currentRouteName;
|
|
||||||
setCurrentScreen(currentRouteName);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationContainer
|
<NavigationContainer
|
||||||
theme={navTheme}
|
theme={navTheme}
|
||||||
|
|
|
@ -30,12 +30,12 @@ const Avatar = React.memo(
|
||||||
borderRadius = 4,
|
borderRadius = 4,
|
||||||
type = SubscriptionType.DIRECT
|
type = SubscriptionType.DIRECT
|
||||||
}: IAvatar) => {
|
}: IAvatar) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if ((!text && !avatar && !emoji && !rid) || !server) {
|
if ((!text && !avatar && !emoji && !rid) || !server) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
const avatarStyle = {
|
const avatarStyle = {
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
|
|
|
@ -15,10 +15,12 @@ interface IMessageBoxCommandsPreview {
|
||||||
|
|
||||||
const CommandsPreview = React.memo(
|
const CommandsPreview = React.memo(
|
||||||
({ commandPreview, showCommandPreview }: IMessageBoxCommandsPreview) => {
|
({ commandPreview, showCommandPreview }: IMessageBoxCommandsPreview) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!showCommandPreview) {
|
if (!showCommandPreview) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
testID='commandbox-container'
|
testID='commandbox-container'
|
||||||
|
|
|
@ -16,10 +16,12 @@ interface IMessageBoxMentions {
|
||||||
|
|
||||||
const Mentions = React.memo(
|
const Mentions = React.memo(
|
||||||
({ mentions, trackingType, loading }: IMessageBoxMentions) => {
|
({ mentions, trackingType, loading }: IMessageBoxMentions) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!trackingType) {
|
if (!trackingType) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<View testID='messagebox-container'>
|
<View testID='messagebox-container'>
|
||||||
<FlatList
|
<FlatList
|
||||||
|
|
|
@ -56,10 +56,12 @@ interface IMessageBoxReplyPreview {
|
||||||
|
|
||||||
const ReplyPreview = React.memo(
|
const ReplyPreview = React.memo(
|
||||||
({ message, Message_TimeFormat, replying, close, useRealName }: IMessageBoxReplyPreview) => {
|
({ message, Message_TimeFormat, replying, close, useRealName }: IMessageBoxReplyPreview) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!replying) {
|
if (!replying) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { theme } = useTheme();
|
|
||||||
const time = moment(message.ts).format(Message_TimeFormat);
|
const time = moment(message.ts).format(Message_TimeFormat);
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: themes[theme].messageboxBackground }]}>
|
<View style={[styles.container, { backgroundColor: themes[theme].messageboxBackground }]}>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* eslint-disable class-methods-use-this */
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { StyleSheet, Text } from 'react-native';
|
import { StyleSheet, Text } from 'react-native';
|
||||||
import { BLOCK_CONTEXT, UiKitParserMessage, UiKitParserModal, uiKitMessage, uiKitModal } from '@rocket.chat/ui-kit';
|
import { BLOCK_CONTEXT, UiKitParserMessage, UiKitParserModal, uiKitMessage, uiKitModal } from '@rocket.chat/ui-kit';
|
||||||
|
|
|
@ -17,12 +17,12 @@ interface IMarkdownPreview {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MarkdownPreview = ({ msg, numberOfLines = 1, testID, style = [] }: IMarkdownPreview) => {
|
const MarkdownPreview = ({ msg, numberOfLines = 1, testID, style = [] }: IMarkdownPreview) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
let m = formatText(msg);
|
let m = formatText(msg);
|
||||||
m = formatHyperlink(m);
|
m = formatHyperlink(m);
|
||||||
m = shortnameToUnicode(m);
|
m = shortnameToUnicode(m);
|
||||||
|
|
|
@ -24,11 +24,12 @@ export type TElement = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
|
const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
|
||||||
|
const { onAnswerButtonPress } = useContext(MessageContext);
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!attachment.actions) {
|
if (!attachment.actions) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { onAnswerButtonPress } = useContext(MessageContext);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
const attachedButtons = attachment.actions.map((element: TElement) => {
|
const attachedButtons = attachment.actions.map((element: TElement) => {
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
|
@ -57,12 +58,12 @@ const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
|
||||||
|
|
||||||
const Attachments: React.FC<IMessageAttachments> = React.memo(
|
const Attachments: React.FC<IMessageAttachments> = React.memo(
|
||||||
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply }: IMessageAttachments) => {
|
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply }: IMessageAttachments) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!attachments || attachments.length === 0) {
|
if (!attachments || attachments.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
const attachmentsElements = attachments.map((file: IAttachment, index: number) => {
|
const attachmentsElements = attachments.map((file: IAttachment, index: number) => {
|
||||||
if (file && file.image_url) {
|
if (file && file.image_url) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -81,11 +81,13 @@ interface IMessageReply {
|
||||||
|
|
||||||
const Fields = React.memo(
|
const Fields = React.memo(
|
||||||
({ attachment, getCustomEmoji }: IMessageFields) => {
|
({ attachment, getCustomEmoji }: IMessageFields) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
|
|
||||||
if (!attachment.fields) {
|
if (!attachment.fields) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{attachment.fields.map(field => (
|
{attachment.fields.map(field => (
|
||||||
|
@ -111,11 +113,12 @@ const Fields = React.memo(
|
||||||
|
|
||||||
const CollapsibleQuote = React.memo(
|
const CollapsibleQuote = React.memo(
|
||||||
({ attachment, index, getCustomEmoji }: IMessageReply) => {
|
({ attachment, index, getCustomEmoji }: IMessageReply) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const [collapsed, setCollapsed] = useState(attachment?.collapsed);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const [collapsed, setCollapsed] = useState(attachment.collapsed);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
setCollapsed(!collapsed);
|
setCollapsed(!collapsed);
|
||||||
|
|
|
@ -16,6 +16,8 @@ import { E2E_MESSAGE_TYPE, themes } from '../../lib/constants';
|
||||||
const Content = React.memo(
|
const Content = React.memo(
|
||||||
(props: IMessageContent) => {
|
(props: IMessageContent) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const { baseUrl, user, onLinkPress } = useContext(MessageContext);
|
||||||
|
|
||||||
if (props.isInfo) {
|
if (props.isInfo) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const infoMessage = getInfoMessage({ ...props });
|
const infoMessage = getInfoMessage({ ...props });
|
||||||
|
@ -49,7 +51,6 @@ const Content = React.memo(
|
||||||
} else if (isPreview) {
|
} else if (isPreview) {
|
||||||
content = <MarkdownPreview msg={props.msg} />;
|
content = <MarkdownPreview msg={props.msg} />;
|
||||||
} else {
|
} else {
|
||||||
const { baseUrl, user, onLinkPress } = useContext(MessageContext);
|
|
||||||
content = (
|
content = (
|
||||||
<Markdown
|
<Markdown
|
||||||
msg={props.msg}
|
msg={props.msg}
|
||||||
|
|
|
@ -10,11 +10,12 @@ import { E2E_MESSAGE_TYPE, themes } from '../../lib/constants';
|
||||||
|
|
||||||
const Encrypted = React.memo(({ type }: { type: string }) => {
|
const Encrypted = React.memo(({ type }: { type: string }) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const { onEncryptedPress } = useContext(MessageContext);
|
||||||
|
|
||||||
if (type !== E2E_MESSAGE_TYPE) {
|
if (type !== E2E_MESSAGE_TYPE) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { onEncryptedPress } = useContext(MessageContext);
|
|
||||||
return (
|
return (
|
||||||
<Touchable onPress={onEncryptedPress} style={styles.encrypted} hitSlop={BUTTON_HIT_SLOP}>
|
<Touchable onPress={onEncryptedPress} style={styles.encrypted} hitSlop={BUTTON_HIT_SLOP}>
|
||||||
<CustomIcon name='encrypted' size={16} color={themes[theme].auxiliaryText} />
|
<CustomIcon name='encrypted' size={16} color={themes[theme].auxiliaryText} />
|
||||||
|
|
|
@ -110,6 +110,9 @@ const Message = React.memo((props: IMessage) => {
|
||||||
Message.displayName = 'Message';
|
Message.displayName = 'Message';
|
||||||
|
|
||||||
const MessageTouchable = React.memo((props: IMessageTouchable & IMessage) => {
|
const MessageTouchable = React.memo((props: IMessageTouchable & IMessage) => {
|
||||||
|
const { onPress, onLongPress } = useContext(MessageContext);
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (props.hasError) {
|
if (props.hasError) {
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
|
@ -117,8 +120,6 @@ const MessageTouchable = React.memo((props: IMessageTouchable & IMessage) => {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const { onPress, onLongPress } = useContext(MessageContext);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Touchable
|
<Touchable
|
||||||
|
|
|
@ -11,11 +11,12 @@ import { useTheme } from '../../theme';
|
||||||
const MessageError = React.memo(
|
const MessageError = React.memo(
|
||||||
({ hasError }: { hasError: boolean }) => {
|
({ hasError }: { hasError: boolean }) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const { onErrorPress } = useContext(MessageContext);
|
||||||
|
|
||||||
if (!hasError) {
|
if (!hasError) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { onErrorPress } = useContext(MessageContext);
|
|
||||||
return (
|
return (
|
||||||
<Touchable onPress={onErrorPress} style={styles.errorButton} hitSlop={BUTTON_HIT_SLOP}>
|
<Touchable onPress={onErrorPress} style={styles.errorButton} hitSlop={BUTTON_HIT_SLOP}>
|
||||||
<CustomIcon name='warning' color={themes[theme].dangerColor} size={18} />
|
<CustomIcon name='warning' color={themes[theme].dangerColor} size={18} />
|
||||||
|
|
|
@ -11,16 +11,7 @@ import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const RepliedThread = memo(({ tmid, tmsg, isHeader, fetchThreadName, id, isEncrypted }: IMessageRepliedThread) => {
|
const RepliedThread = memo(({ tmid, tmsg, isHeader, fetchThreadName, id, isEncrypted }: IMessageRepliedThread) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!tmid || !isHeader) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [msg, setMsg] = useState(isEncrypted ? I18n.t('Encrypted_message') : tmsg);
|
const [msg, setMsg] = useState(isEncrypted ? I18n.t('Encrypted_message') : tmsg);
|
||||||
const fetch = async () => {
|
|
||||||
const threadName = fetchThreadName ? await fetchThreadName(tmid, id) : '';
|
|
||||||
setMsg(threadName);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
|
@ -28,6 +19,15 @@ const RepliedThread = memo(({ tmid, tmsg, isHeader, fetchThreadName, id, isEncry
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
if (!tmid || !isHeader) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
const threadName = fetchThreadName ? await fetchThreadName(tmid, id) : '';
|
||||||
|
setMsg(threadName);
|
||||||
|
};
|
||||||
|
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,11 +113,13 @@ const Title = React.memo(({ attachment, timeFormat, theme }: { attachment: IAtta
|
||||||
|
|
||||||
const Description = React.memo(
|
const Description = React.memo(
|
||||||
({ attachment, getCustomEmoji, theme }: { attachment: IAttachment; getCustomEmoji: TGetCustomEmoji; theme: string }) => {
|
({ attachment, getCustomEmoji, theme }: { attachment: IAttachment; getCustomEmoji: TGetCustomEmoji; theme: string }) => {
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
const text = attachment.text || attachment.title;
|
const text = attachment.text || attachment.title;
|
||||||
|
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
|
||||||
return (
|
return (
|
||||||
<Markdown
|
<Markdown
|
||||||
msg={text}
|
msg={text}
|
||||||
|
@ -145,10 +147,12 @@ const Description = React.memo(
|
||||||
|
|
||||||
const UrlImage = React.memo(
|
const UrlImage = React.memo(
|
||||||
({ image }: { image?: string }) => {
|
({ image }: { image?: string }) => {
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
|
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
|
||||||
image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`;
|
image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`;
|
||||||
return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />;
|
return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />;
|
||||||
},
|
},
|
||||||
|
@ -157,11 +161,12 @@ const UrlImage = React.memo(
|
||||||
|
|
||||||
const Fields = React.memo(
|
const Fields = React.memo(
|
||||||
({ attachment, theme, getCustomEmoji }: { attachment: IAttachment; theme: string; getCustomEmoji: TGetCustomEmoji }) => {
|
({ attachment, theme, getCustomEmoji }: { attachment: IAttachment; theme: string; getCustomEmoji: TGetCustomEmoji }) => {
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
|
|
||||||
if (!attachment.fields) {
|
if (!attachment.fields) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.fieldsContainer}>
|
<View style={styles.fieldsContainer}>
|
||||||
{attachment.fields.map(field => (
|
{attachment.fields.map(field => (
|
||||||
|
@ -187,13 +192,12 @@ const Reply = React.memo(
|
||||||
({ attachment, timeFormat, index, getCustomEmoji }: IMessageReply) => {
|
({ attachment, timeFormat, index, getCustomEmoji }: IMessageReply) => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
|
|
||||||
|
|
||||||
const onPress = async () => {
|
const onPress = async () => {
|
||||||
let url = attachment.title_link || attachment.author_link;
|
let url = attachment.title_link || attachment.author_link;
|
||||||
if (attachment.message_link) {
|
if (attachment.message_link) {
|
||||||
|
|
|
@ -12,12 +12,12 @@ import { useTheme } from '../../theme';
|
||||||
const Thread = React.memo(
|
const Thread = React.memo(
|
||||||
({ msg, tcount, tlm, isThreadRoom, id }: IMessageThread) => {
|
({ msg, tcount, tlm, isThreadRoom, id }: IMessageThread) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const { threadBadgeColor, toggleFollowThread, user, replies } = useContext(MessageContext);
|
||||||
|
|
||||||
if (!tlm || isThreadRoom || tcount === 0) {
|
if (!tlm || isThreadRoom || tcount === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { threadBadgeColor, toggleFollowThread, user, replies } = useContext(MessageContext);
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
<View style={[styles.button, { backgroundColor: themes[theme].tintColor }]} testID={`message-thread-button-${msg}`}>
|
<View style={[styles.button, { backgroundColor: themes[theme].tintColor }]} testID={`message-thread-button-${msg}`}>
|
||||||
|
|
|
@ -53,10 +53,12 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
const UrlImage = React.memo(
|
const UrlImage = React.memo(
|
||||||
({ image }: { image: string }) => {
|
({ image }: { image: string }) => {
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
|
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
|
||||||
image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`;
|
image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`;
|
||||||
return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />;
|
return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />;
|
||||||
},
|
},
|
||||||
|
|
|
@ -57,9 +57,10 @@ interface IMessageUser {
|
||||||
|
|
||||||
const User = React.memo(
|
const User = React.memo(
|
||||||
({ isHeader, useRealName, author, alias, ts, timeFormat, hasError, navToRoomInfo, type, ...props }: IMessageUser) => {
|
({ isHeader, useRealName, author, alias, ts, timeFormat, hasError, navToRoomInfo, type, ...props }: IMessageUser) => {
|
||||||
|
const { user } = useContext(MessageContext);
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (isHeader || hasError) {
|
if (isHeader || hasError) {
|
||||||
const { user } = useContext(MessageContext);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
const username = (useRealName && author?.name) || author?.username;
|
const username = (useRealName && author?.name) || author?.username;
|
||||||
const aliasUsername = alias ? (
|
const aliasUsername = alias ? (
|
||||||
<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>
|
<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Message from './Message';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
|
import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
|
||||||
import { useTheme, withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||||
import { IAttachment, TAnyMessageModel } from '../../definitions';
|
import { IAttachment, TAnyMessageModel } from '../../definitions';
|
||||||
|
@ -57,6 +57,7 @@ interface IMessageContainerProps {
|
||||||
toggleFollowThread?: (isFollowingThread: boolean, tmid?: string) => Promise<void>;
|
toggleFollowThread?: (isFollowingThread: boolean, tmid?: string) => Promise<void>;
|
||||||
jumpToMessage?: (link: string) => void;
|
jumpToMessage?: (link: string) => void;
|
||||||
onPress?: () => void;
|
onPress?: () => void;
|
||||||
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMessageContainerState {
|
interface IMessageContainerState {
|
||||||
|
@ -292,8 +293,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
||||||
};
|
};
|
||||||
|
|
||||||
onLinkPress = (link: string): void => {
|
onLinkPress = (link: string): void => {
|
||||||
const { theme } = useTheme();
|
const { item, jumpToMessage, theme } = this.props;
|
||||||
const { item, jumpToMessage } = this.props;
|
|
||||||
const isMessageLink = item?.attachments?.findIndex((att: IAttachment) => att?.message_link === link) !== -1;
|
const isMessageLink = item?.attachments?.findIndex((att: IAttachment) => att?.message_link === link) !== -1;
|
||||||
if (isMessageLink && jumpToMessage) {
|
if (isMessageLink && jumpToMessage) {
|
||||||
return jumpToMessage(link);
|
return jumpToMessage(link);
|
||||||
|
|
|
@ -20,16 +20,20 @@ interface IOmnichannelStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
const OmnichannelStatus = memo(({ searching, goQueue, queueSize, inquiryEnabled, user }: IOmnichannelStatus) => {
|
const OmnichannelStatus = memo(({ searching, goQueue, queueSize, inquiryEnabled, user }: IOmnichannelStatus) => {
|
||||||
if (searching || !(RocketChat.isOmnichannelModuleAvailable() && user?.roles?.includes('livechat-agent'))) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const [status, setStatus] = useState(isOmnichannelStatusAvailable(user));
|
const [status, setStatus] = useState<boolean>(false);
|
||||||
|
const canUseOmnichannel = RocketChat.isOmnichannelModuleAvailable() && user?.roles?.includes('livechat-agent');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setStatus(isOmnichannelStatusAvailable(user));
|
if (canUseOmnichannel) {
|
||||||
|
setStatus(isOmnichannelStatusAvailable(user));
|
||||||
|
}
|
||||||
}, [user.statusLivechat]);
|
}, [user.statusLivechat]);
|
||||||
|
|
||||||
|
if (searching || !canUseOmnichannel) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const toggleLivechat = async () => {
|
const toggleLivechat = async () => {
|
||||||
try {
|
try {
|
||||||
setStatus(v => !v);
|
setStatus(v => !v);
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { getDeviceToken } from '../../notifications/push';
|
||||||
import { extractHostname } from '../../utils/server';
|
import { extractHostname } from '../../utils/server';
|
||||||
import { BASIC_AUTH_KEY } from '../../utils/fetch';
|
import { BASIC_AUTH_KEY } from '../../utils/fetch';
|
||||||
import database, { getDatabase } from '../database';
|
import database, { getDatabase } from '../database';
|
||||||
import { useSsl } from '../../utils/url';
|
import { isSsl } from '../../utils/url';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import { ICertificate, IRocketChat } from '../../definitions';
|
import { ICertificate, IRocketChat } from '../../definitions';
|
||||||
import sdk from '../services/sdk';
|
import sdk from '../services/sdk';
|
||||||
|
@ -80,7 +80,7 @@ export async function removeServer({ server }: { server: string }): Promise<void
|
||||||
if (userId) {
|
if (userId) {
|
||||||
const resume = UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${userId}`);
|
const resume = UserPreferences.getString(`${RocketChat.TOKEN_KEY}-${userId}`);
|
||||||
|
|
||||||
const sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) });
|
const sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: isSsl(server) });
|
||||||
await sdk.login({ resume });
|
await sdk.login({ resume });
|
||||||
|
|
||||||
const token = getDeviceToken();
|
const token = getDeviceToken();
|
||||||
|
|
|
@ -760,7 +760,7 @@ export const validateInviteToken = (token: string): any =>
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
sdk.post('validateInviteToken', { token });
|
sdk.post('validateInviteToken', { token });
|
||||||
|
|
||||||
export const useInviteToken = (token: string): any =>
|
export const inviteToken = (token: string): any =>
|
||||||
// RC 2.4.0
|
// RC 2.4.0
|
||||||
// TODO: missing definitions from server
|
// TODO: missing definitions from server
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
@ -3,7 +3,7 @@ import EJSON from 'ejson';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
import { twoFactor } from '../../utils/twoFactor';
|
import { twoFactor } from '../../utils/twoFactor';
|
||||||
import { useSsl } from '../../utils/url';
|
import { isSsl } from '../../utils/url';
|
||||||
import { store as reduxStore } from '../store/auxStore';
|
import { store as reduxStore } from '../store/auxStore';
|
||||||
import { Serialized, MatchPathPattern, OperationParams, PathFor, ResultFor } from '../../definitions/rest/helpers';
|
import { Serialized, MatchPathPattern, OperationParams, PathFor, ResultFor } from '../../definitions/rest/helpers';
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ class Sdk {
|
||||||
|
|
||||||
private initializeSdk(server: string): typeof Rocketchat {
|
private initializeSdk(server: string): typeof Rocketchat {
|
||||||
// The app can't reconnect if reopen interval is 5s while in development
|
// The app can't reconnect if reopen interval is 5s while in development
|
||||||
return new Rocketchat({ host: server, protocol: 'ddp', useSsl: useSsl(server), reopen: __DEV__ ? 20000 : 5000 });
|
return new Rocketchat({ host: server, protocol: 'ddp', useSsl: isSsl(server), reopen: __DEV__ ? 20000 : 5000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: We need to stop returning the SDK after all methods are dehydrated
|
// TODO: We need to stop returning the SDK after all methods are dehydrated
|
||||||
|
|
|
@ -16,7 +16,7 @@ const handleRequest = function* handleRequest({ token }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = yield RocketChat.useInviteToken(token);
|
const result = yield RocketChat.inviteToken(token);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
yield put(inviteLinksFailure());
|
yield put(inviteLinksFailure());
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -11,5 +11,5 @@ export const isValidURL = (url: string): boolean => {
|
||||||
return !!pattern.test(url);
|
return !!pattern.test(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use useSsl: false only if server url starts with http://
|
// Use checkUseSsl: false only if server url starts with http://
|
||||||
export const useSsl = (url: string): boolean => !/http:\/\//.test(url);
|
export const isSsl = (url: string): boolean => !/http:\/\//.test(url);
|
||||||
|
|
|
@ -201,6 +201,7 @@ class MessagesView extends React.Component<IMessagesViewProps, any> {
|
||||||
renderItem: (item: any) => (
|
renderItem: (item: any) => (
|
||||||
<Message
|
<Message
|
||||||
{...renderItemCommonProps(item)}
|
{...renderItemCommonProps(item)}
|
||||||
|
theme={theme}
|
||||||
item={{
|
item={{
|
||||||
...item,
|
...item,
|
||||||
u: item.user,
|
u: item.user,
|
||||||
|
|
|
@ -40,6 +40,8 @@ const LeftButtons = ({
|
||||||
goRoomActionsView,
|
goRoomActionsView,
|
||||||
isMasterDetail
|
isMasterDetail
|
||||||
}: ILeftButtonsProps): React.ReactElement | null => {
|
}: ILeftButtonsProps): React.ReactElement | null => {
|
||||||
|
const onPress = useCallback(() => goRoomActionsView(), []);
|
||||||
|
|
||||||
if (!isMasterDetail || tmid) {
|
if (!isMasterDetail || tmid) {
|
||||||
const onPress = () => navigation.goBack();
|
const onPress = () => navigation.goBack();
|
||||||
let label = ' ';
|
let label = ' ';
|
||||||
|
@ -61,7 +63,6 @@ const LeftButtons = ({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const onPress = useCallback(() => goRoomActionsView(), []);
|
|
||||||
|
|
||||||
if (baseUrl && userId && token) {
|
if (baseUrl && userId && token) {
|
||||||
return <Avatar text={title} size={30} type={t} style={styles.avatar} onPress={onPress} />;
|
return <Avatar text={title} size={30} type={t} style={styles.avatar} onPress={onPress} />;
|
||||||
|
|
|
@ -1144,7 +1144,8 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
|
|
||||||
renderItem = (item: TAnyMessageModel, previousItem: TAnyMessageModel, highlightedMessage?: string) => {
|
renderItem = (item: TAnyMessageModel, previousItem: TAnyMessageModel, highlightedMessage?: string) => {
|
||||||
const { room, lastOpen, canAutoTranslate } = this.state;
|
const { room, lastOpen, canAutoTranslate } = this.state;
|
||||||
const { user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, Message_Read_Receipt_Enabled } = this.props;
|
const { user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, Message_Read_Receipt_Enabled, theme } =
|
||||||
|
this.props;
|
||||||
let dateSeparator = null;
|
let dateSeparator = null;
|
||||||
let showUnreadSeparator = false;
|
let showUnreadSeparator = false;
|
||||||
|
|
||||||
|
@ -1207,6 +1208,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
toggleFollowThread={this.toggleFollowThread}
|
toggleFollowThread={this.toggleFollowThread}
|
||||||
jumpToMessage={this.jumpToMessageByUrl}
|
jumpToMessage={this.jumpToMessageByUrl}
|
||||||
highlighted={highlightedMessage === item.id}
|
highlighted={highlightedMessage === item.id}
|
||||||
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,12 @@ interface IRoomListHeader {
|
||||||
|
|
||||||
const ListHeader = React.memo(
|
const ListHeader = React.memo(
|
||||||
({ searching, goEncryption, goQueue, queueSize, inquiryEnabled, encryptionBanner, user }: IRoomListHeader) => {
|
({ searching, goEncryption, goQueue, queueSize, inquiryEnabled, encryptionBanner, user }: IRoomListHeader) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (searching) {
|
if (searching) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{encryptionBanner ? (
|
{encryptionBanner ? (
|
||||||
|
|
|
@ -174,6 +174,7 @@
|
||||||
"eslint-plugin-jest": "24.7.0",
|
"eslint-plugin-jest": "24.7.0",
|
||||||
"eslint-plugin-jsx-a11y": "6.3.1",
|
"eslint-plugin-jsx-a11y": "6.3.1",
|
||||||
"eslint-plugin-react": "7.20.3",
|
"eslint-plugin-react": "7.20.3",
|
||||||
|
"eslint-plugin-react-hooks": "^4.4.0",
|
||||||
"eslint-plugin-react-native": "3.8.1",
|
"eslint-plugin-react-native": "3.8.1",
|
||||||
"husky": "^6.0.0",
|
"husky": "^6.0.0",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
|
|
|
@ -8570,6 +8570,11 @@ eslint-plugin-jsx-a11y@6.3.1:
|
||||||
jsx-ast-utils "^2.4.1"
|
jsx-ast-utils "^2.4.1"
|
||||||
language-tags "^1.0.5"
|
language-tags "^1.0.5"
|
||||||
|
|
||||||
|
eslint-plugin-react-hooks@^4.4.0:
|
||||||
|
version "4.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.4.0.tgz#71c39e528764c848d8253e1aa2c7024ed505f6c4"
|
||||||
|
integrity sha512-U3RVIfdzJaeKDQKEJbz5p3NW8/L80PCATJAfuojwbaEL+gBjfGdhUcGde+WGUW46Q5sr/NgxevsIiDtNXrvZaQ==
|
||||||
|
|
||||||
eslint-plugin-react-native-globals@^0.1.1:
|
eslint-plugin-react-native-globals@^0.1.1:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz#ee1348bc2ceb912303ce6bdbd22e2f045ea86ea2"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz#ee1348bc2ceb912303ce6bdbd22e2f045ea86ea2"
|
||||||
|
|
Loading…
Reference in New Issue