Merge branch 'develop' into new.add-discusions-roomactionsview
This commit is contained in:
commit
5dc0a0a05f
File diff suppressed because it is too large
Load Diff
|
@ -14,7 +14,6 @@ public class BasePackageList {
|
|||
new expo.modules.imageloader.ImageLoaderPackage(),
|
||||
new expo.modules.keepawake.KeepAwakePackage(),
|
||||
new expo.modules.localauthentication.LocalAuthenticationPackage(),
|
||||
new expo.modules.permissions.PermissionsPackage(),
|
||||
new expo.modules.videothumbnails.VideoThumbnailsPackage(),
|
||||
new expo.modules.webbrowser.WebBrowserPackage()
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { ScrollView, StyleSheet, View } from 'react-native';
|
||||
import { ScrollView, ScrollViewProps, StyleSheet, View } from 'react-native';
|
||||
|
||||
import { themes } from '../constants/colors';
|
||||
import sharedStyles from '../views/Styles';
|
||||
|
@ -10,10 +10,10 @@ import AppVersion from './AppVersion';
|
|||
import { isTablet } from '../utils/deviceInfo';
|
||||
import SafeAreaView from './SafeAreaView';
|
||||
|
||||
interface IFormContainer {
|
||||
interface IFormContainer extends ScrollViewProps {
|
||||
theme: string;
|
||||
testID: string;
|
||||
children: JSX.Element;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import { StyleProp, StyleSheet, Text, TextInputProps, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import Touchable from 'react-native-platform-touchable';
|
||||
|
||||
import sharedStyles from '../views/Styles';
|
||||
|
@ -50,23 +50,21 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
interface IRCTextInputProps {
|
||||
label: string;
|
||||
error: {
|
||||
interface IRCTextInputProps extends TextInputProps {
|
||||
label?: string;
|
||||
error?: {
|
||||
error: any;
|
||||
reason: any;
|
||||
};
|
||||
loading: boolean;
|
||||
secureTextEntry: boolean;
|
||||
containerStyle: any;
|
||||
inputStyle: object;
|
||||
inputRef: any;
|
||||
testID: string;
|
||||
iconLeft: string;
|
||||
iconRight: string;
|
||||
placeholder: string;
|
||||
left: JSX.Element;
|
||||
onIconRightPress(): void;
|
||||
loading?: boolean;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
inputStyle?: TextStyle;
|
||||
inputRef?: React.Ref<unknown>;
|
||||
testID?: string;
|
||||
iconLeft?: string;
|
||||
iconRight?: string;
|
||||
left?: JSX.Element;
|
||||
onIconRightPress?(): void;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
|
@ -148,17 +146,10 @@ export default class RCTextInput extends React.PureComponent<IRCTextInputProps,
|
|||
return (
|
||||
<View style={[styles.inputContainer, containerStyle]}>
|
||||
{label ? (
|
||||
<Text
|
||||
contentDescription={null}
|
||||
// @ts-ignore
|
||||
accessibilityLabel={null}
|
||||
style={[styles.label, { color: themes[theme].titleText }, error.error && { color: dangerColor }]}>
|
||||
{label}
|
||||
</Text>
|
||||
<Text style={[styles.label, { color: themes[theme].titleText }, error?.error && { color: dangerColor }]}>{label}</Text>
|
||||
) : null}
|
||||
<View style={styles.wrap}>
|
||||
<TextInput
|
||||
/* @ts-ignore*/
|
||||
style={[
|
||||
styles.input,
|
||||
iconLeft && styles.inputIconLeft,
|
||||
|
@ -168,14 +159,13 @@ export default class RCTextInput extends React.PureComponent<IRCTextInputProps,
|
|||
borderColor: themes[theme].separatorColor,
|
||||
color: themes[theme].titleText
|
||||
},
|
||||
error.error && {
|
||||
error?.error && {
|
||||
color: dangerColor,
|
||||
borderColor: dangerColor
|
||||
},
|
||||
inputStyle
|
||||
]}
|
||||
ref={inputRef}
|
||||
/* @ts-ignore*/
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
underlineColorAndroid='transparent'
|
||||
|
@ -183,8 +173,6 @@ export default class RCTextInput extends React.PureComponent<IRCTextInputProps,
|
|||
testID={testID}
|
||||
accessibilityLabel={placeholder}
|
||||
placeholder={placeholder}
|
||||
/* @ts-ignore*/
|
||||
contentDescription={placeholder}
|
||||
theme={theme}
|
||||
{...inputProps}
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
import { themes } from '../../constants/colors';
|
||||
import styles from './styles';
|
||||
import { events, logEvent } from '../../utils/log';
|
||||
|
@ -9,20 +10,20 @@ interface IAtMention {
|
|||
mention: string;
|
||||
username: string;
|
||||
navToRoomInfo: Function;
|
||||
style: any;
|
||||
style?: any;
|
||||
useRealName: boolean;
|
||||
theme: string;
|
||||
mentions: any;
|
||||
}
|
||||
|
||||
const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, style = [], useRealName, theme }: IAtMention) => {
|
||||
const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, style = [], useRealName }: IAtMention) => {
|
||||
const { theme } = useTheme();
|
||||
if (mention === 'all' || mention === 'here') {
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
styles.mention,
|
||||
{
|
||||
color: themes[theme].mentionGroupColor
|
||||
color: themes[theme!].mentionGroupColor
|
||||
},
|
||||
...style
|
||||
]}>
|
||||
|
@ -34,11 +35,11 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl
|
|||
let mentionStyle = {};
|
||||
if (mention === username) {
|
||||
mentionStyle = {
|
||||
color: themes[theme].mentionMeColor
|
||||
color: themes[theme!].mentionMeColor
|
||||
};
|
||||
} else {
|
||||
mentionStyle = {
|
||||
color: themes[theme].mentionOtherColor
|
||||
color: themes[theme!].mentionOtherColor
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -61,7 +62,7 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl
|
|||
);
|
||||
}
|
||||
|
||||
return <Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}>{`@${mention}`}</Text>;
|
||||
return <Text style={[styles.text, { color: themes[theme!].bodyText }, ...style]}>{`@${mention}`}</Text>;
|
||||
});
|
||||
|
||||
export default AtMention;
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { Text, TextStyle } from 'react-native';
|
||||
|
||||
import { themes } from '../../constants/colors';
|
||||
import { useTheme } from '../../theme';
|
||||
import styles from './styles';
|
||||
|
||||
interface IHashtag {
|
||||
hashtag: string;
|
||||
navToRoomInfo: Function;
|
||||
style: [];
|
||||
theme: string;
|
||||
style?: TextStyle[];
|
||||
channels: {
|
||||
[index: number]: string | number;
|
||||
name: string;
|
||||
_id: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [], theme }: IHashtag) => {
|
||||
const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IHashtag) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const handlePress = () => {
|
||||
const index = channels.findIndex(channel => channel.name === hashtag);
|
||||
const index = channels?.findIndex(channel => channel.name === hashtag);
|
||||
const navParam = {
|
||||
t: 'c',
|
||||
rid: channels[index]._id
|
||||
|
@ -31,7 +34,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [], them
|
|||
style={[
|
||||
styles.mention,
|
||||
{
|
||||
color: themes[theme].mentionOtherColor
|
||||
color: themes[theme!].mentionOtherColor
|
||||
},
|
||||
...style
|
||||
]}
|
||||
|
@ -40,7 +43,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [], them
|
|||
</Text>
|
||||
);
|
||||
}
|
||||
return <Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}>{`#${hashtag}`}</Text>;
|
||||
return <Text style={[styles.text, { color: themes[theme!].bodyText }, ...style]}>{`#${hashtag}`}</Text>;
|
||||
});
|
||||
|
||||
export default Hashtag;
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Image, Text } from 'react-native';
|
|||
import { Node, Parser } from 'commonmark';
|
||||
import Renderer from 'commonmark-react-renderer';
|
||||
import removeMarkdown from 'remove-markdown';
|
||||
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||
|
||||
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||
import I18n from '../../i18n';
|
||||
|
@ -20,9 +21,20 @@ import MarkdownTableCell from './TableCell';
|
|||
import mergeTextNodes from './mergeTextNodes';
|
||||
import styles from './styles';
|
||||
import { isValidURL } from '../../utils/url';
|
||||
import NewMarkdown from './new';
|
||||
|
||||
interface IUser {
|
||||
_id: string;
|
||||
username: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
type UserMention = Pick<IUser, '_id' | 'username' | 'name'>;
|
||||
|
||||
interface IMarkdownProps {
|
||||
msg: string;
|
||||
md: MarkdownAST;
|
||||
mentions: UserMention[];
|
||||
getCustomEmoji: Function;
|
||||
baseUrl: string;
|
||||
username: string;
|
||||
|
@ -35,7 +47,7 @@ interface IMarkdownProps {
|
|||
name: string;
|
||||
_id: number;
|
||||
}[];
|
||||
mentions: object[];
|
||||
enableMessageParser: boolean;
|
||||
navToRoomInfo: Function;
|
||||
preview: boolean;
|
||||
theme: string;
|
||||
|
@ -97,8 +109,10 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
|
||||
constructor(props: IMarkdownProps) {
|
||||
super(props);
|
||||
if (!this.isNewMarkdown) {
|
||||
this.renderer = this.createRenderer();
|
||||
}
|
||||
}
|
||||
|
||||
createRenderer = () =>
|
||||
new Renderer({
|
||||
|
@ -139,6 +153,11 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
renderParagraphsInLists: true
|
||||
});
|
||||
|
||||
get isNewMarkdown(): boolean {
|
||||
const { md, enableMessageParser } = this.props;
|
||||
return enableMessageParser && !!md;
|
||||
}
|
||||
|
||||
editedMessage = (ast: any) => {
|
||||
const { isEdited } = this.props;
|
||||
if (isEdited) {
|
||||
|
@ -227,12 +246,12 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
};
|
||||
|
||||
renderHashtag = ({ hashtag }: { hashtag: string }) => {
|
||||
const { channels, navToRoomInfo, style, theme } = this.props;
|
||||
return <MarkdownHashtag hashtag={hashtag} channels={channels} navToRoomInfo={navToRoomInfo} theme={theme} style={style} />;
|
||||
const { channels, navToRoomInfo, style } = this.props;
|
||||
return <MarkdownHashtag hashtag={hashtag} channels={channels} navToRoomInfo={navToRoomInfo} style={style} />;
|
||||
};
|
||||
|
||||
renderAtMention = ({ mentionName }: { mentionName: string }) => {
|
||||
const { username, mentions, navToRoomInfo, useRealName, style, theme } = this.props;
|
||||
const { username, mentions, navToRoomInfo, useRealName, style } = this.props;
|
||||
return (
|
||||
<MarkdownAtMention
|
||||
mentions={mentions}
|
||||
|
@ -240,7 +259,6 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
useRealName={useRealName}
|
||||
username={username}
|
||||
navToRoomInfo={navToRoomInfo}
|
||||
theme={theme}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
|
@ -329,12 +347,44 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { msg, numberOfLines, preview = false, theme, style = [], testID } = this.props;
|
||||
const {
|
||||
msg,
|
||||
md,
|
||||
numberOfLines,
|
||||
preview = false,
|
||||
theme,
|
||||
style = [],
|
||||
testID,
|
||||
mentions,
|
||||
channels,
|
||||
navToRoomInfo,
|
||||
useRealName,
|
||||
username,
|
||||
getCustomEmoji,
|
||||
baseUrl,
|
||||
onLinkPress
|
||||
} = this.props;
|
||||
|
||||
if (!msg) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.isNewMarkdown) {
|
||||
return (
|
||||
<NewMarkdown
|
||||
username={username}
|
||||
baseUrl={baseUrl}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
useRealName={useRealName}
|
||||
tokens={md}
|
||||
mentions={mentions}
|
||||
channels={channels}
|
||||
navToRoomInfo={navToRoomInfo}
|
||||
onLinkPress={onLinkPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let m = formatText(msg);
|
||||
|
||||
// Ex: '[ ](https://open.rocket.chat/group/test?msg=abcdef) Test'
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { BigEmoji as BigEmojiProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import Emoji from './Emoji';
|
||||
|
||||
interface IBigEmojiProps {
|
||||
value: BigEmojiProps['value'];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row'
|
||||
}
|
||||
});
|
||||
|
||||
const BigEmoji = ({ value }: IBigEmojiProps): JSX.Element => (
|
||||
<View style={styles.container}>
|
||||
{value.map(block => (
|
||||
<Emoji value={block.value} isBigEmoji />
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
|
||||
export default BigEmoji;
|
|
@ -0,0 +1,40 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, Text } from 'react-native';
|
||||
import { Bold as BoldProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import sharedStyles from '../../../views/Styles';
|
||||
import Strike from './Strike';
|
||||
import Italic from './Italic';
|
||||
import Plain from './Plain';
|
||||
import Link from './Link';
|
||||
|
||||
interface IBoldProps {
|
||||
value: BoldProps['value'];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
...sharedStyles.textBold
|
||||
}
|
||||
});
|
||||
|
||||
const Bold = ({ value }: IBoldProps): JSX.Element => (
|
||||
<Text style={styles.text}>
|
||||
{value.map(block => {
|
||||
switch (block.type) {
|
||||
case 'LINK':
|
||||
return <Link value={block.value} />;
|
||||
case 'PLAIN_TEXT':
|
||||
return <Plain value={block.value} />;
|
||||
case 'STRIKE':
|
||||
return <Strike value={block.value} />;
|
||||
case 'ITALIC':
|
||||
return <Italic value={block.value} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
|
||||
export default Bold;
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { Code as CodeProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import styles from '../styles';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { useTheme } from '../../../theme';
|
||||
import CodeLine from './CodeLine';
|
||||
|
||||
interface ICodeProps {
|
||||
value: CodeProps['value'];
|
||||
}
|
||||
|
||||
const Code = ({ value }: ICodeProps): JSX.Element => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
styles.codeBlock,
|
||||
{
|
||||
color: themes[theme!].bodyText,
|
||||
backgroundColor: themes[theme!].bannerBackground,
|
||||
borderColor: themes[theme!].borderColor
|
||||
}
|
||||
]}>
|
||||
{value.map(block => {
|
||||
switch (block.type) {
|
||||
case 'CODE_LINE':
|
||||
return <CodeLine value={block.value} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
export default Code;
|
|
@ -0,0 +1,17 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { CodeLine as CodeLineProps } from '@rocket.chat/message-parser';
|
||||
|
||||
interface ICodeLineProps {
|
||||
value: CodeLineProps['value'];
|
||||
}
|
||||
|
||||
const CodeLine = ({ value }: ICodeLineProps): JSX.Element | null => {
|
||||
if (value.type !== 'PLAIN_TEXT') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Text>{value.value}</Text>;
|
||||
};
|
||||
|
||||
export default CodeLine;
|
|
@ -0,0 +1,29 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { Emoji as EmojiProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import shortnameToUnicode from '../../../utils/shortnameToUnicode';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { useTheme } from '../../../theme';
|
||||
import styles from '../styles';
|
||||
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
|
||||
import MarkdownContext from './MarkdownContext';
|
||||
|
||||
interface IEmojiProps {
|
||||
value: EmojiProps['value'];
|
||||
isBigEmoji?: boolean;
|
||||
}
|
||||
|
||||
const Emoji = ({ value, isBigEmoji }: IEmojiProps): JSX.Element => {
|
||||
const { theme } = useTheme();
|
||||
const { baseUrl, getCustomEmoji } = useContext(MarkdownContext);
|
||||
const emojiUnicode = shortnameToUnicode(`:${value.value}:`);
|
||||
const emoji = getCustomEmoji?.(value.value);
|
||||
|
||||
if (emoji) {
|
||||
return <CustomEmoji baseUrl={baseUrl} style={[isBigEmoji ? styles.customEmojiBig : styles.customEmoji]} emoji={emoji} />;
|
||||
}
|
||||
return <Text style={[{ color: themes[theme!].bodyText }, isBigEmoji ? styles.textBig : styles.text]}>{emojiUnicode}</Text>;
|
||||
};
|
||||
|
||||
export default Emoji;
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { Heading as HeadingProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import { themes } from '../../../constants/colors';
|
||||
import styles from '../styles';
|
||||
import { useTheme } from '../../../theme';
|
||||
|
||||
interface IHeadingProps {
|
||||
value: HeadingProps['value'];
|
||||
level: HeadingProps['level'];
|
||||
}
|
||||
|
||||
const Heading = ({ value, level }: IHeadingProps): JSX.Element => {
|
||||
const { theme } = useTheme();
|
||||
const textStyle = styles[`heading${level}`];
|
||||
|
||||
return (
|
||||
<Text style={[textStyle, { color: themes[theme!].bodyText }]}>
|
||||
{value.map(block => {
|
||||
switch (block.type) {
|
||||
case 'PLAIN_TEXT':
|
||||
return block.value;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
export default Heading;
|
|
@ -0,0 +1,41 @@
|
|||
import React from 'react';
|
||||
import { Image as ImageProps } from '@rocket.chat/message-parser';
|
||||
import { createImageProgress } from 'react-native-image-progress';
|
||||
import * as Progress from 'react-native-progress';
|
||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||
|
||||
import { useTheme } from '../../../theme';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import styles from '../../message/styles';
|
||||
|
||||
interface IImageProps {
|
||||
value: ImageProps['value'];
|
||||
}
|
||||
|
||||
type TMessageImage = {
|
||||
img: string;
|
||||
theme: string;
|
||||
};
|
||||
|
||||
const ImageProgress = createImageProgress(FastImage);
|
||||
|
||||
const MessageImage = ({ img, theme }: TMessageImage) => (
|
||||
<ImageProgress
|
||||
style={[styles.inlineImage, { borderColor: themes[theme].borderColor }]}
|
||||
source={{ uri: encodeURI(img) }}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
indicator={Progress.Pie}
|
||||
indicatorProps={{
|
||||
color: themes[theme].actionTintColor
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const Image = ({ value }: IImageProps): JSX.Element => {
|
||||
const { theme } = useTheme();
|
||||
const { src } = value;
|
||||
|
||||
return <MessageImage img={src.value} theme={theme!} />;
|
||||
};
|
||||
|
||||
export default Image;
|
|
@ -0,0 +1,62 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { Paragraph as ParagraphProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import Hashtag from '../Hashtag';
|
||||
import AtMention from '../AtMention';
|
||||
import Link from './Link';
|
||||
import Plain from './Plain';
|
||||
import Bold from './Bold';
|
||||
import Strike from './Strike';
|
||||
import Italic from './Italic';
|
||||
import Emoji from './Emoji';
|
||||
import InlineCode from './InlineCode';
|
||||
import Image from './Image';
|
||||
import MarkdownContext from './MarkdownContext';
|
||||
|
||||
interface IParagraphProps {
|
||||
value: ParagraphProps['value'];
|
||||
}
|
||||
|
||||
const Inline = ({ value }: IParagraphProps): JSX.Element => {
|
||||
const { useRealName, username, navToRoomInfo, mentions, channels } = useContext(MarkdownContext);
|
||||
return (
|
||||
<>
|
||||
{value.map(block => {
|
||||
switch (block.type) {
|
||||
case 'IMAGE':
|
||||
return <Image value={block.value} />;
|
||||
case 'PLAIN_TEXT':
|
||||
return <Plain value={block.value} />;
|
||||
case 'BOLD':
|
||||
return <Bold value={block.value} />;
|
||||
case 'STRIKE':
|
||||
return <Strike value={block.value} />;
|
||||
case 'ITALIC':
|
||||
return <Italic value={block.value} />;
|
||||
case 'LINK':
|
||||
return <Link value={block.value} />;
|
||||
case 'MENTION_USER':
|
||||
return (
|
||||
<AtMention
|
||||
mention={block.value.value}
|
||||
useRealName={useRealName}
|
||||
username={username}
|
||||
navToRoomInfo={navToRoomInfo}
|
||||
mentions={mentions}
|
||||
/>
|
||||
);
|
||||
case 'EMOJI':
|
||||
return <Emoji value={block.value} />;
|
||||
case 'MENTION_CHANNEL':
|
||||
return <Hashtag hashtag={block.value.value} navToRoomInfo={navToRoomInfo} channels={channels} />;
|
||||
case 'INLINE_CODE':
|
||||
return <InlineCode value={block.value} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Inline;
|
|
@ -0,0 +1,38 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { InlineCode as InlineCodeProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import styles from '../styles';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { useTheme } from '../../../theme';
|
||||
|
||||
interface IInlineCodeProps {
|
||||
value: InlineCodeProps['value'];
|
||||
}
|
||||
|
||||
const InlineCode = ({ value }: IInlineCodeProps): JSX.Element => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
styles.codeInline,
|
||||
{
|
||||
color: themes[theme!].bodyText,
|
||||
backgroundColor: themes[theme!].bannerBackground,
|
||||
borderColor: themes[theme!].borderColor
|
||||
}
|
||||
]}>
|
||||
{(block => {
|
||||
switch (block.type) {
|
||||
case 'PLAIN_TEXT':
|
||||
return <Text>{block.value}</Text>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})(value)}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
export default InlineCode;
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, Text } from 'react-native';
|
||||
import { Italic as ItalicProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import Strike from './Strike';
|
||||
import Bold from './Bold';
|
||||
import Plain from './Plain';
|
||||
import Link from './Link';
|
||||
|
||||
interface IItalicProps {
|
||||
value: ItalicProps['value'];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
fontStyle: 'italic'
|
||||
}
|
||||
});
|
||||
|
||||
const Italic = ({ value }: IItalicProps): JSX.Element => (
|
||||
<Text style={styles.text}>
|
||||
{value.map(block => {
|
||||
switch (block.type) {
|
||||
case 'LINK':
|
||||
return <Link value={block.value} />;
|
||||
case 'PLAIN_TEXT':
|
||||
return <Plain value={block.value} />;
|
||||
case 'STRIKE':
|
||||
return <Strike value={block.value} />;
|
||||
case 'BOLD':
|
||||
return <Bold value={block.value} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
|
||||
export default Italic;
|
|
@ -0,0 +1,60 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { Text, Clipboard } from 'react-native';
|
||||
import { Link as LinkProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import styles from '../styles';
|
||||
import I18n from '../../../i18n';
|
||||
import { LISTENER } from '../../Toast';
|
||||
import { useTheme } from '../../../theme';
|
||||
import openLink from '../../../utils/openLink';
|
||||
import EventEmitter from '../../../utils/events';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import Strike from './Strike';
|
||||
import Italic from './Italic';
|
||||
import Bold from './Bold';
|
||||
import MarkdownContext from './MarkdownContext';
|
||||
|
||||
interface ILinkProps {
|
||||
value: LinkProps['value'];
|
||||
}
|
||||
|
||||
const Link = ({ value }: ILinkProps): JSX.Element => {
|
||||
const { theme } = useTheme();
|
||||
const { onLinkPress } = useContext(MarkdownContext);
|
||||
const { src, label } = value;
|
||||
const handlePress = () => {
|
||||
if (!src.value) {
|
||||
return;
|
||||
}
|
||||
if (onLinkPress) {
|
||||
return onLinkPress(src.value);
|
||||
}
|
||||
openLink(src.value, theme);
|
||||
};
|
||||
|
||||
const onLongPress = () => {
|
||||
Clipboard.setString(src.value);
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
||||
};
|
||||
|
||||
return (
|
||||
<Text onPress={handlePress} onLongPress={onLongPress} style={[styles.link, { color: themes[theme!].actionTintColor }]}>
|
||||
{(block => {
|
||||
switch (block.type) {
|
||||
case 'PLAIN_TEXT':
|
||||
return block.value;
|
||||
case 'STRIKE':
|
||||
return <Strike value={block.value} />;
|
||||
case 'ITALIC':
|
||||
return <Italic value={block.value} />;
|
||||
case 'BOLD':
|
||||
return <Bold value={block.value} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})(label)}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
export default Link;
|
|
@ -0,0 +1,29 @@
|
|||
import React from 'react';
|
||||
|
||||
import { UserMention } from '../../message/interfaces';
|
||||
|
||||
interface IMarkdownContext {
|
||||
mentions: UserMention[];
|
||||
channels: {
|
||||
name: string;
|
||||
_id: number;
|
||||
}[];
|
||||
useRealName: boolean;
|
||||
username: string;
|
||||
baseUrl: string;
|
||||
navToRoomInfo: Function;
|
||||
getCustomEmoji?: Function;
|
||||
onLinkPress?: Function;
|
||||
}
|
||||
|
||||
const defaultState = {
|
||||
mentions: [],
|
||||
channels: [],
|
||||
useRealName: false,
|
||||
username: '',
|
||||
baseUrl: '',
|
||||
navToRoomInfo: () => {}
|
||||
};
|
||||
|
||||
const MarkdownContext = React.createContext<IMarkdownContext>(defaultState);
|
||||
export default MarkdownContext;
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import { OrderedList as OrderedListProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import Inline from './Inline';
|
||||
import styles from '../styles';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { useTheme } from '../../../theme';
|
||||
|
||||
interface IOrderedListProps {
|
||||
value: OrderedListProps['value'];
|
||||
}
|
||||
|
||||
const OrderedList = ({ value }: IOrderedListProps): JSX.Element => {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<View>
|
||||
{value.map((item, index) => (
|
||||
<View style={styles.row}>
|
||||
<Text style={[styles.text, { color: themes[theme!].bodyText }]}>{index + 1}. </Text>
|
||||
<Inline value={item.value} />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrderedList;
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { Paragraph as ParagraphProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import Inline from './Inline';
|
||||
import styles from '../styles';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { themes } from '../../../constants/colors';
|
||||
|
||||
interface IParagraphProps {
|
||||
value: ParagraphProps['value'];
|
||||
}
|
||||
|
||||
const Paragraph = ({ value }: IParagraphProps): JSX.Element => {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<Text style={[styles.text, { color: themes[theme!].bodyText }]}>
|
||||
<Inline value={value} />
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
export default Paragraph;
|
|
@ -0,0 +1,22 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { Plain as PlainProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import styles from '../styles';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { themes } from '../../../constants/colors';
|
||||
|
||||
interface IPlainProps {
|
||||
value: PlainProps['value'];
|
||||
}
|
||||
|
||||
const Plain = ({ value }: IPlainProps): JSX.Element => {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<Text accessibilityLabel={value} style={[styles.plainText, { color: themes[theme!].bodyText }]}>
|
||||
{value}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
export default Plain;
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { Quote as QuoteProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { useTheme } from '../../../theme';
|
||||
import styles from '../styles';
|
||||
import Paragraph from './Paragraph';
|
||||
|
||||
interface IQuoteProps {
|
||||
value: QuoteProps['value'];
|
||||
}
|
||||
|
||||
const Quote = ({ value }: IQuoteProps): JSX.Element => {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.quote, { backgroundColor: themes[theme!].borderColor }]} />
|
||||
<View style={styles.childContainer}>
|
||||
{value.map(item => (
|
||||
<Paragraph value={item.value} />
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Quote;
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, Text } from 'react-native';
|
||||
import { Strike as StrikeProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import Bold from './Bold';
|
||||
import Italic from './Italic';
|
||||
import Plain from './Plain';
|
||||
import Link from './Link';
|
||||
|
||||
interface IStrikeProps {
|
||||
value: StrikeProps['value'];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
textDecorationLine: 'line-through'
|
||||
}
|
||||
});
|
||||
|
||||
const Strike = ({ value }: IStrikeProps): JSX.Element => (
|
||||
<Text style={styles.text}>
|
||||
{value.map(block => {
|
||||
switch (block.type) {
|
||||
case 'LINK':
|
||||
return <Link value={block.value} />;
|
||||
case 'PLAIN_TEXT':
|
||||
return <Plain value={block.value} />;
|
||||
case 'BOLD':
|
||||
return <Bold value={block.value} />;
|
||||
case 'ITALIC':
|
||||
return <Italic value={block.value} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
|
||||
export default Strike;
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { Tasks as TasksProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import Inline from './Inline';
|
||||
import styles from '../styles';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { useTheme } from '../../../theme';
|
||||
|
||||
interface ITasksProps {
|
||||
value: TasksProps['value'];
|
||||
}
|
||||
|
||||
const TaskList = ({ value = [] }: ITasksProps): JSX.Element => {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<View>
|
||||
{value.map(item => (
|
||||
<View style={styles.row}>
|
||||
<Text style={[styles.text, { color: themes[theme!].bodyText }]}>{item.status ? '- [x] ' : '- [ ] '}</Text>
|
||||
<Inline value={item.value} />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskList;
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
import { UnorderedList as UnorderedListProps } from '@rocket.chat/message-parser';
|
||||
import { View, Text } from 'react-native';
|
||||
|
||||
import Inline from './Inline';
|
||||
import styles from '../styles';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { useTheme } from '../../../theme';
|
||||
|
||||
interface IUnorderedListProps {
|
||||
value: UnorderedListProps['value'];
|
||||
}
|
||||
|
||||
const UnorderedList = ({ value }: IUnorderedListProps): JSX.Element => {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<View>
|
||||
{value.map(item => (
|
||||
<View style={styles.row}>
|
||||
<Text style={[styles.text, { color: themes[theme!].bodyText }]}>- </Text>
|
||||
<Inline value={item.value} />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default UnorderedList;
|
|
@ -0,0 +1,77 @@
|
|||
import React from 'react';
|
||||
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||
|
||||
import Quote from './Quote';
|
||||
import Paragraph from './Paragraph';
|
||||
import Heading from './Heading';
|
||||
import Code from './Code';
|
||||
import BigEmoji from './BigEmoji';
|
||||
import OrderedList from './OrderedList';
|
||||
import UnorderedList from './UnorderedList';
|
||||
import { UserMention } from '../../message/interfaces';
|
||||
import TaskList from './TaskList';
|
||||
import MarkdownContext from './MarkdownContext';
|
||||
|
||||
interface IBodyProps {
|
||||
tokens: MarkdownAST;
|
||||
mentions: UserMention[];
|
||||
channels: {
|
||||
name: string;
|
||||
_id: number;
|
||||
}[];
|
||||
getCustomEmoji?: Function;
|
||||
onLinkPress?: Function;
|
||||
navToRoomInfo: Function;
|
||||
useRealName: boolean;
|
||||
username: string;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
const Body = ({
|
||||
tokens,
|
||||
mentions,
|
||||
channels,
|
||||
useRealName,
|
||||
username,
|
||||
navToRoomInfo,
|
||||
getCustomEmoji,
|
||||
baseUrl,
|
||||
onLinkPress
|
||||
}: IBodyProps): JSX.Element => (
|
||||
<MarkdownContext.Provider
|
||||
value={{
|
||||
mentions,
|
||||
channels,
|
||||
useRealName,
|
||||
username,
|
||||
navToRoomInfo,
|
||||
getCustomEmoji,
|
||||
baseUrl,
|
||||
onLinkPress
|
||||
}}>
|
||||
{tokens.map(block => {
|
||||
switch (block.type) {
|
||||
case 'BIG_EMOJI':
|
||||
return <BigEmoji value={block.value} />;
|
||||
case 'UNORDERED_LIST':
|
||||
return <UnorderedList value={block.value} />;
|
||||
case 'ORDERED_LIST':
|
||||
return <OrderedList value={block.value} />;
|
||||
case 'TASKS':
|
||||
return <TaskList value={block.value} />;
|
||||
case 'QUOTE':
|
||||
return <Quote value={block.value} />;
|
||||
case 'PARAGRAPH':
|
||||
return <Paragraph value={block.value} />;
|
||||
case 'CODE':
|
||||
return <Code value={block.value} />;
|
||||
case 'HEADING':
|
||||
return <Heading value={block.value} level={block.level} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</MarkdownContext.Provider>
|
||||
);
|
||||
|
||||
export default Body;
|
|
@ -30,6 +30,10 @@ export default StyleSheet.create<any>({
|
|||
del: {
|
||||
textDecorationLine: 'line-through'
|
||||
},
|
||||
plainText: {
|
||||
fontSize: 16,
|
||||
flexShrink: 1
|
||||
},
|
||||
text: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textRegular
|
||||
|
@ -70,12 +74,16 @@ export default StyleSheet.create<any>({
|
|||
resizeMode: 'contain'
|
||||
},
|
||||
codeInline: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textRegular,
|
||||
...codeFontFamily,
|
||||
borderWidth: 1,
|
||||
borderRadius: 4
|
||||
borderRadius: 4,
|
||||
paddingLeft: 2,
|
||||
paddingTop: 2
|
||||
},
|
||||
codeBlock: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textRegular,
|
||||
...codeFontFamily,
|
||||
borderWidth: 1,
|
||||
|
|
|
@ -51,8 +51,10 @@ const Content = React.memo(
|
|||
// @ts-ignore
|
||||
<Markdown
|
||||
msg={props.msg}
|
||||
md={props.md}
|
||||
baseUrl={baseUrl}
|
||||
getCustomEmoji={props.getCustomEmoji}
|
||||
enableMessageParser={user.enableMessageParserEarlyAdoption}
|
||||
username={user.username}
|
||||
isEdited={props.isEdited}
|
||||
numberOfLines={isPreview ? 1 : 0}
|
||||
|
@ -103,6 +105,9 @@ const Content = React.memo(
|
|||
if (prevProps.isIgnored !== nextProps.isIgnored) {
|
||||
return false;
|
||||
}
|
||||
if (!dequal(prevProps.md, nextProps.md)) {
|
||||
return false;
|
||||
}
|
||||
if (!dequal(prevProps.mentions, nextProps.mentions)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -357,7 +357,8 @@ class MessageContainer extends React.Component<IMessageContainerProps, any> {
|
|||
unread,
|
||||
blocks,
|
||||
autoTranslate: autoTranslateMessage,
|
||||
replies
|
||||
replies,
|
||||
md
|
||||
} = item;
|
||||
|
||||
let message = msg;
|
||||
|
@ -391,6 +392,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, any> {
|
|||
<Message
|
||||
id={id}
|
||||
msg={message}
|
||||
md={md}
|
||||
rid={rid}
|
||||
author={u}
|
||||
ts={ts}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||
|
||||
export interface IMessageAttachments {
|
||||
attachments: any;
|
||||
timeFormat: string;
|
||||
|
@ -48,12 +50,21 @@ export interface IMessageCallButton {
|
|||
callJitsi: Function;
|
||||
}
|
||||
|
||||
export interface IUser {
|
||||
_id: string;
|
||||
username: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type UserMention = Pick<IUser, '_id' | 'username' | 'name'>;
|
||||
|
||||
export interface IMessageContent {
|
||||
isTemp: boolean;
|
||||
isInfo: boolean;
|
||||
tmid: string;
|
||||
isThreadRoom: boolean;
|
||||
msg: string;
|
||||
md: MarkdownAST;
|
||||
theme: string;
|
||||
isEdited: boolean;
|
||||
isEncrypted: boolean;
|
||||
|
@ -62,7 +73,7 @@ export interface IMessageContent {
|
|||
name: string;
|
||||
_id: number;
|
||||
}[];
|
||||
mentions: object[];
|
||||
mentions: UserMention[];
|
||||
navToRoomInfo: Function;
|
||||
useRealName: boolean;
|
||||
isIgnored: boolean;
|
||||
|
|
|
@ -106,7 +106,6 @@ export default StyleSheet.create<any>({
|
|||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
// maxWidth: 400,
|
||||
minHeight: isTablet ? 300 : 200,
|
||||
borderRadius: 4,
|
||||
borderWidth: 1,
|
||||
|
|
|
@ -81,7 +81,6 @@
|
|||
"error-you-are-last-owner": "You are the last owner. Please set new owner before leaving the room.",
|
||||
"error-status-not-allowed": "Invisible status is disabled",
|
||||
"Actions": "Actions",
|
||||
"activity": "activity",
|
||||
"Activity": "Activity",
|
||||
"Add_Reaction": "Add Reaction",
|
||||
"Add_Server": "Add Server",
|
||||
|
@ -232,7 +231,6 @@
|
|||
"Everyone_can_access_this_team": "Everyone can access this team",
|
||||
"Error_uploading": "Error uploading",
|
||||
"Expiration_Days": "Expiration (Days)",
|
||||
"Favorite": "Favorite",
|
||||
"Favorites": "Favorites",
|
||||
"Files": "Files",
|
||||
"File_description": "File description",
|
||||
|
@ -249,9 +247,6 @@
|
|||
"Forward_to_user": "Forward to user",
|
||||
"Full_table": "Click to see full table",
|
||||
"Generate_New_Link": "Generate New Link",
|
||||
"Group_by_favorites": "Group favorites",
|
||||
"Group_by_type": "Group by type",
|
||||
"Hide": "Hide",
|
||||
"Has_joined_the_channel": "has joined the channel",
|
||||
"Has_joined_the_conversation": "has joined the conversation",
|
||||
"Has_left_the_channel": "has left the channel",
|
||||
|
@ -334,7 +329,6 @@
|
|||
"N_people_reacted": "{{n}} people reacted",
|
||||
"N_users": "{{n}} users",
|
||||
"N_channels": "{{n}} channels",
|
||||
"name": "name",
|
||||
"Name": "Name",
|
||||
"Navigation_history": "Navigation history",
|
||||
"Never": "Never",
|
||||
|
@ -412,7 +406,6 @@
|
|||
"Reactions_are_disabled": "Reactions are disabled",
|
||||
"Reactions_are_enabled": "Reactions are enabled",
|
||||
"Reactions": "Reactions",
|
||||
"Read": "Read",
|
||||
"Read_External_Permission_Message": "Rocket.Chat needs to access photos, media, and files on your device",
|
||||
"Read_External_Permission": "Read Media Permission",
|
||||
"Read_Only_Channel": "Read Only Channel",
|
||||
|
@ -507,7 +500,6 @@
|
|||
"Sign_in_your_server": "Sign in your server",
|
||||
"Sign_Up": "Sign Up",
|
||||
"Some_field_is_invalid_or_empty": "Some field is invalid or empty",
|
||||
"Sorting_by": "Sorting by {{key}}",
|
||||
"Sound": "Sound",
|
||||
"Star_room": "Star room",
|
||||
"Star": "Star",
|
||||
|
@ -546,7 +538,6 @@
|
|||
"unarchive": "unarchive",
|
||||
"UNARCHIVE": "UNARCHIVE",
|
||||
"Unblock_user": "Unblock user",
|
||||
"Unfavorite": "Unfavorite",
|
||||
"Unfollowed_thread": "Unfollowed thread",
|
||||
"Unmute": "Unmute",
|
||||
"unmuted": "unmuted",
|
||||
|
@ -688,7 +679,6 @@
|
|||
"No_threads": "There are no threads",
|
||||
"No_threads_following": "You are not following any threads",
|
||||
"No_threads_unread": "There are no unread threads",
|
||||
"No_discussions": "There are no discussions",
|
||||
"Messagebox_Send_to_channel": "Send to channel",
|
||||
"Leader": "Leader",
|
||||
"Moderator": "Moderator",
|
||||
|
@ -783,11 +773,15 @@
|
|||
"creating_discussion": "creating discussion",
|
||||
"Canned_Responses": "Canned Responses",
|
||||
"No_match_found": "No match found.",
|
||||
"No_discussions": "No discussions",
|
||||
"Check_canned_responses": "Check on canned responses.",
|
||||
"Searching": "Searching",
|
||||
"Use": "Use",
|
||||
"Shortcut": "Shortcut",
|
||||
"Content": "Content",
|
||||
"Sharing": "Sharing",
|
||||
"No_canned_responses": "No canned responses"
|
||||
"No_canned_responses": "No canned responses",
|
||||
"Send_email_confirmation": "Send email confirmation",
|
||||
"sending_email_confirmation": "sending email confirmation",
|
||||
"Enable_Message_Parser": "Enable Message Parser"
|
||||
}
|
||||
|
|
|
@ -679,5 +679,7 @@
|
|||
"Use": "Use",
|
||||
"Shortcut": "Atalho",
|
||||
"Content": "Conteúdo",
|
||||
"No_canned_responses": "Não há respostas predefinidas"
|
||||
"No_canned_responses": "Não há respostas predefinidas",
|
||||
"Send_email_confirmation": "Enviar email de confirmação",
|
||||
"sending_email_confirmation": "enviando email de confirmação"
|
||||
}
|
||||
|
|
|
@ -81,4 +81,6 @@ export default class Message extends Model {
|
|||
@field('e2e') e2e;
|
||||
|
||||
@field('tshow') tshow;
|
||||
|
||||
@json('md', sanitizer) md;
|
||||
}
|
||||
|
|
|
@ -190,6 +190,15 @@ export default schemaMigrations({
|
|||
]
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
toVersion: 14,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: 'messages',
|
||||
columns: [{ name: 'md', type: 'string', isOptional: true }]
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
@ -25,4 +25,6 @@ export default class User extends Model {
|
|||
@field('show_message_in_main_thread') showMessageInMainThread;
|
||||
|
||||
@field('is_from_webview') isFromWebView;
|
||||
|
||||
@field('enable_message_parser_early_adoption') enableMessageParserEarlyAdoption;
|
||||
}
|
||||
|
|
|
@ -94,6 +94,15 @@ export default schemaMigrations({
|
|||
columns: [{ name: 'is_from_webview', type: 'boolean', isOptional: true }]
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
toVersion: 12,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: 'users',
|
||||
columns: [{ name: 'enable_message_parser_early_adoption', type: 'boolean', isOptional: true }]
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||
|
||||
export default appSchema({
|
||||
version: 13,
|
||||
version: 14,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: 'subscriptions',
|
||||
|
@ -115,7 +115,8 @@ export default appSchema({
|
|||
{ name: 'tmsg', type: 'string', isOptional: true },
|
||||
{ name: 'blocks', type: 'string', isOptional: true },
|
||||
{ name: 'e2e', type: 'string', isOptional: true },
|
||||
{ name: 'tshow', type: 'boolean', isOptional: true }
|
||||
{ name: 'tshow', type: 'boolean', isOptional: true },
|
||||
{ name: 'md', type: 'string', isOptional: true }
|
||||
]
|
||||
}),
|
||||
tableSchema({
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||
|
||||
export default appSchema({
|
||||
version: 11,
|
||||
version: 12,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: 'users',
|
||||
|
@ -16,7 +16,8 @@ export default appSchema({
|
|||
{ name: 'login_email_password', type: 'boolean', isOptional: true },
|
||||
{ name: 'show_message_in_main_thread', type: 'boolean', isOptional: true },
|
||||
{ name: 'avatar_etag', type: 'string', isOptional: true },
|
||||
{ name: 'is_from_webview', type: 'boolean', isOptional: true }
|
||||
{ name: 'is_from_webview', type: 'boolean', isOptional: true },
|
||||
{ name: 'enable_message_parser_early_adoption', type: 'boolean', isOptional: true }
|
||||
]
|
||||
}),
|
||||
tableSchema({
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Q } from '@nozbe/watermelondb';
|
|||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
import defaultSettings from '../constants/settings';
|
||||
import log from '../utils/log';
|
||||
|
@ -530,6 +531,10 @@ const RocketChat = {
|
|||
return this.post('users.forgotPassword', { email }, false);
|
||||
},
|
||||
|
||||
sendConfirmationEmail(email) {
|
||||
return this.methodCallWrapper('sendConfirmationEmail', email);
|
||||
},
|
||||
|
||||
loginTOTP(params, loginEmailPassword, isFromWebView = false) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
|
@ -622,7 +627,8 @@ const RocketChat = {
|
|||
roles: result.me.roles,
|
||||
avatarETag: result.me.avatarETag,
|
||||
isFromWebView,
|
||||
showMessageInMainThread: result.me.settings?.preferences?.showMessageInMainThread ?? true
|
||||
showMessageInMainThread: result.me.settings?.preferences?.showMessageInMainThread ?? true,
|
||||
enableMessageParserEarlyAdoption: result.me.settings?.preferences?.enableMessageParserEarlyAdoption ?? true
|
||||
};
|
||||
return user;
|
||||
},
|
||||
|
@ -1069,8 +1075,12 @@ const RocketChat = {
|
|||
},
|
||||
methodCallWrapper(method, ...params) {
|
||||
const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings;
|
||||
const { user } = reduxStore.getState().login;
|
||||
if (API_Use_REST_For_DDP_Calls) {
|
||||
return this.post(`method.call/${method}`, { message: EJSON.stringify({ method, params }) });
|
||||
const url = isEmpty(user) ? 'method.callAnon' : 'method.call';
|
||||
return this.post(`${url}/${method}`, {
|
||||
message: EJSON.stringify({ method, params })
|
||||
});
|
||||
}
|
||||
const parsedParams = params.map(param => {
|
||||
if (param instanceof Date) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { I18nManager, StyleSheet, TextInput, TextInputProps } from 'react-native';
|
||||
import { I18nManager, StyleProp, StyleSheet, TextInput, TextInputProps, TextStyle } from 'react-native';
|
||||
|
||||
import { themes } from '../constants/colors';
|
||||
|
||||
|
@ -10,7 +10,7 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
interface IThemedTextInput extends TextInputProps {
|
||||
style: object;
|
||||
style: StyleProp<TextStyle>;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ import { encryptionInit, encryptionStop } from '../actions/encryption';
|
|||
import UserPreferences from '../lib/userPreferences';
|
||||
import { inquiryRequest, inquiryReset } from '../ee/omnichannel/actions/inquiry';
|
||||
import { isOmnichannelStatusAvailable } from '../ee/omnichannel/lib';
|
||||
import Navigation from '../lib/Navigation';
|
||||
|
||||
const getServer = state => state.server.server;
|
||||
const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
|
||||
|
@ -191,8 +190,6 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
|
|||
yield put(appStart({ root: ROOT_OUTSIDE }));
|
||||
showErrorAlert(I18n.t('Logged_out_by_server'), I18n.t('Oops'));
|
||||
yield delay(300);
|
||||
Navigation.navigate('NewServerView');
|
||||
yield delay(300);
|
||||
EventEmitter.emit('NewServer', { server });
|
||||
} else {
|
||||
const serversDB = database.servers;
|
||||
|
|
|
@ -10,6 +10,7 @@ import NewServerView from '../views/NewServerView';
|
|||
import WorkspaceView from '../views/WorkspaceView';
|
||||
import LoginView from '../views/LoginView';
|
||||
import ForgotPasswordView from '../views/ForgotPasswordView';
|
||||
import SendEmailConfirmationView from '../views/SendEmailConfirmationView';
|
||||
import RegisterView from '../views/RegisterView';
|
||||
import LegalView from '../views/LegalView';
|
||||
import AuthenticationWebView from '../views/AuthenticationWebView';
|
||||
|
@ -25,6 +26,11 @@ const _OutsideStack = () => {
|
|||
<Outside.Screen name='WorkspaceView' component={WorkspaceView} options={WorkspaceView.navigationOptions} />
|
||||
<Outside.Screen name='LoginView' component={LoginView} options={LoginView.navigationOptions} />
|
||||
<Outside.Screen name='ForgotPasswordView' component={ForgotPasswordView} options={ForgotPasswordView.navigationOptions} />
|
||||
<Outside.Screen
|
||||
name='SendEmailConfirmationView'
|
||||
component={SendEmailConfirmationView}
|
||||
options={SendEmailConfirmationView.navigationOptions}
|
||||
/>
|
||||
<Outside.Screen name='RegisterView' component={RegisterView} options={RegisterView.navigationOptions} />
|
||||
<Outside.Screen name='LegalView' component={LegalView} options={LegalView.navigationOptions} />
|
||||
</Outside.Navigator>
|
||||
|
|
|
@ -3,14 +3,14 @@ import hoistNonReactStatics from 'hoist-non-react-statics';
|
|||
|
||||
interface IThemeContextProps {
|
||||
theme: string;
|
||||
themePreferences: {
|
||||
themePreferences?: {
|
||||
currentTheme: 'automatic' | 'light';
|
||||
darkLevel: string;
|
||||
};
|
||||
setTheme: (newTheme?: {}) => void;
|
||||
setTheme?: (newTheme?: {}) => void;
|
||||
}
|
||||
|
||||
export const ThemeContext = React.createContext<Partial<IThemeContextProps>>({ theme: 'light' });
|
||||
export const ThemeContext = React.createContext<IThemeContextProps>({ theme: 'light' });
|
||||
|
||||
export function withTheme(Component: React.ComponentType<any>): (props: any) => JSX.Element {
|
||||
const ThemedComponent = (props: any) => (
|
||||
|
|
|
@ -11,6 +11,9 @@ export default {
|
|||
FP_FORGOT_PASSWORD: 'fp_forgot_password',
|
||||
FP_FORGOT_PASSWORD_F: 'fp_forgot_password_f',
|
||||
|
||||
// SEND EMAIL CONFIRMATION VIEW
|
||||
SEC_SEND_EMAIL_CONFIRMATION: 'sec_send_email_confirmation',
|
||||
|
||||
// REGISTER VIEW
|
||||
REGISTER_DEFAULT_SIGN_UP: 'register_default_sign_up',
|
||||
REGISTER_DEFAULT_SIGN_UP_F: 'register_default_sign_up_f',
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
import { isTablet } from './deviceInfo';
|
||||
|
||||
const guidelineBaseWidth = isTablet ? 600 : 375;
|
||||
const guidelineBaseHeight = isTablet ? 800 : 667;
|
||||
|
||||
// TODO: we need to refactor this
|
||||
const scale = (size, width) => (width / guidelineBaseWidth) * size;
|
||||
const verticalScale = (size, height) => (height / guidelineBaseHeight) * size;
|
||||
const moderateScale = (size, factor = 0.5, width) => size + (scale(size, width) - size) * factor;
|
||||
|
||||
export { scale, verticalScale, moderateScale };
|
|
@ -0,0 +1,16 @@
|
|||
import { isTablet } from './deviceInfo';
|
||||
|
||||
const guidelineBaseWidth = isTablet ? 600 : 375;
|
||||
const guidelineBaseHeight = isTablet ? 800 : 667;
|
||||
|
||||
function scale({ size, width }: { size: number; width: number }): number {
|
||||
return (width / guidelineBaseWidth) * size;
|
||||
}
|
||||
function verticalScale({ size, height }: { size: number; height: number }): number {
|
||||
return (height / guidelineBaseHeight) * size;
|
||||
}
|
||||
function moderateScale({ size, factor = 0.5, width }: { size: number; factor?: number; width: number }): number {
|
||||
return size + (scale({ size, width }) - size) * factor;
|
||||
}
|
||||
|
||||
export { scale, verticalScale, moderateScale };
|
|
@ -1,8 +1,9 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import { WebView, WebViewNavigation } from 'react-native-webview';
|
||||
import { connect } from 'react-redux';
|
||||
import parse from 'url-parse';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { WebViewMessage } from 'react-native-webview/lib/WebViewTypes';
|
||||
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import { isIOS } from '../utils/deviceInfo';
|
||||
|
@ -40,17 +41,44 @@ window.addEventListener('popstate', function() {
|
|||
});
|
||||
`;
|
||||
|
||||
class AuthenticationWebView extends React.PureComponent {
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
server: PropTypes.string,
|
||||
Accounts_Iframe_api_url: PropTypes.bool,
|
||||
Accounts_Iframe_api_method: PropTypes.bool,
|
||||
theme: PropTypes.string
|
||||
interface IRoute {
|
||||
params: {
|
||||
authType: string;
|
||||
url: string;
|
||||
ssoToken?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface INavigationOption {
|
||||
navigation: StackNavigationProp<any, 'AuthenticationWebView'>;
|
||||
route: IRoute;
|
||||
}
|
||||
|
||||
interface IAuthenticationWebView extends INavigationOption {
|
||||
server: string;
|
||||
Accounts_Iframe_api_url: string;
|
||||
Accounts_Iframe_api_method: string;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
logging: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView, IState> {
|
||||
private oauthRedirectRegex: RegExp;
|
||||
private iframeRedirectRegex: RegExp;
|
||||
|
||||
static navigationOptions = ({ route, navigation }: INavigationOption) => {
|
||||
const { authType } = route.params;
|
||||
return {
|
||||
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} />,
|
||||
title: ['saml', 'cas', 'iframe'].includes(authType) ? 'SSO' : 'OAuth'
|
||||
};
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IAuthenticationWebView) {
|
||||
super(props);
|
||||
this.state = {
|
||||
logging: false,
|
||||
|
@ -71,7 +99,7 @@ class AuthenticationWebView extends React.PureComponent {
|
|||
navigation.pop();
|
||||
};
|
||||
|
||||
login = params => {
|
||||
login = (params: any) => {
|
||||
const { logging } = this.state;
|
||||
if (logging) {
|
||||
return;
|
||||
|
@ -89,7 +117,7 @@ class AuthenticationWebView extends React.PureComponent {
|
|||
};
|
||||
|
||||
// Force 3s delay so the server has time to evaluate the token
|
||||
debouncedLogin = debounce(params => this.login(params), 3000);
|
||||
debouncedLogin = debounce((params: any) => this.login(params), 3000);
|
||||
|
||||
tryLogin = debounce(
|
||||
async () => {
|
||||
|
@ -104,7 +132,7 @@ class AuthenticationWebView extends React.PureComponent {
|
|||
true
|
||||
);
|
||||
|
||||
onNavigationStateChange = webViewState => {
|
||||
onNavigationStateChange = (webViewState: WebViewNavigation | WebViewMessage) => {
|
||||
const url = decodeURIComponent(webViewState.url);
|
||||
const { route } = this.props;
|
||||
const { authType } = route.params;
|
||||
|
@ -180,18 +208,10 @@ class AuthenticationWebView extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
server: state.server.server,
|
||||
Accounts_Iframe_api_url: state.settings.Accounts_Iframe_api_url,
|
||||
Accounts_Iframe_api_method: state.settings.Accounts_Iframe_api_method
|
||||
});
|
||||
|
||||
AuthenticationWebView.navigationOptions = ({ route, navigation }) => {
|
||||
const { authType } = route.params;
|
||||
return {
|
||||
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} />,
|
||||
title: ['saml', 'cas', 'iframe'].includes(authType) ? 'SSO' : 'OAuth'
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(AuthenticationWebView));
|
|
@ -1,8 +1,10 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ScrollView, Share, View } from 'react-native';
|
||||
import moment from 'moment';
|
||||
import { connect } from 'react-redux';
|
||||
import { StackNavigationProp, StackNavigationOptions } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/core';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
import {
|
||||
inviteLinksClear as inviteLinksClearAction,
|
||||
|
@ -20,22 +22,28 @@ import SafeAreaView from '../../containers/SafeAreaView';
|
|||
import { events, logEvent } from '../../utils/log';
|
||||
import styles from './styles';
|
||||
|
||||
class InviteUsersView extends React.Component {
|
||||
static navigationOptions = () => ({
|
||||
title: I18n.t('Invite_users')
|
||||
});
|
||||
interface IInviteUsersView {
|
||||
navigation: StackNavigationProp<any, 'InviteUsersView'>;
|
||||
route: RouteProp<any, 'InviteUsersView'>;
|
||||
theme: string;
|
||||
timeDateFormat: string;
|
||||
invite: {
|
||||
url: string;
|
||||
expires: number;
|
||||
maxUses: number;
|
||||
uses: number;
|
||||
};
|
||||
createInviteLink(rid: string): void;
|
||||
clearInviteLink(): void;
|
||||
}
|
||||
class InviteUsersView extends React.Component<IInviteUsersView, any> {
|
||||
private rid: string;
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
timeDateFormat: PropTypes.string,
|
||||
invite: PropTypes.object,
|
||||
createInviteLink: PropTypes.func,
|
||||
clearInviteLink: PropTypes.func
|
||||
static navigationOptions: StackNavigationOptions = {
|
||||
title: I18n.t('Invite_users')
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IInviteUsersView) {
|
||||
super(props);
|
||||
this.rid = props.route.params?.rid;
|
||||
}
|
||||
|
@ -97,6 +105,7 @@ class InviteUsersView extends React.Component {
|
|||
renderExpiration = () => {
|
||||
const { theme } = this.props;
|
||||
const expirationMessage = this.linkExpirationText();
|
||||
// @ts-ignore
|
||||
return <Markdown msg={expirationMessage} username='' baseUrl='' theme={theme} />;
|
||||
};
|
||||
|
||||
|
@ -104,10 +113,10 @@ class InviteUsersView extends React.Component {
|
|||
const { theme, invite } = this.props;
|
||||
return (
|
||||
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||
{/* @ts-ignore*/}
|
||||
<ScrollView
|
||||
{...scrollPersistTaps}
|
||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
showsVerticalScrollIndicator={false}>
|
||||
<StatusBar />
|
||||
<View style={styles.innerContainer}>
|
||||
|
@ -123,15 +132,15 @@ class InviteUsersView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
timeDateFormat: state.settings.Message_TimeAndDateFormat,
|
||||
days: state.inviteLinks.days,
|
||||
maxUses: state.inviteLinks.maxUses,
|
||||
invite: state.inviteLinks.invite
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
createInviteLink: rid => dispatch(inviteLinksCreateAction(rid)),
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
createInviteLink: (rid: string) => dispatch(inviteLinksCreateAction(rid)),
|
||||
clearInviteLink: () => dispatch(inviteLinksClearAction())
|
||||
});
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Alert, Keyboard, StyleSheet, Text, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { dequal } from 'dequal';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/core';
|
||||
|
||||
import Button from '../containers/Button';
|
||||
import I18n from '../i18n';
|
||||
|
@ -46,31 +47,35 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
class LoginView extends React.Component {
|
||||
static navigationOptions = ({ route, navigation }) => ({
|
||||
title: route.params?.title ?? 'Rocket.Chat',
|
||||
interface IProps {
|
||||
navigation: StackNavigationProp<any>;
|
||||
route: RouteProp<any, 'RegisterView'>;
|
||||
Site_Name: string;
|
||||
Accounts_RegistrationForm: string;
|
||||
Accounts_RegistrationForm_LinkReplacementText: string;
|
||||
Accounts_EmailOrUsernamePlaceholder: string;
|
||||
Accounts_PasswordPlaceholder: string;
|
||||
Accounts_PasswordReset: boolean;
|
||||
Accounts_ShowFormLogin: boolean;
|
||||
isFetching: boolean;
|
||||
error: {
|
||||
error: string;
|
||||
};
|
||||
failure: boolean;
|
||||
theme: string;
|
||||
loginRequest: Function;
|
||||
inviteLinkToken: string;
|
||||
}
|
||||
|
||||
class LoginView extends React.Component<IProps, any> {
|
||||
private passwordInput: any;
|
||||
|
||||
static navigationOptions = ({ route, navigation }: Partial<IProps>) => ({
|
||||
title: route?.params?.title ?? 'Rocket.Chat',
|
||||
headerRight: () => <HeaderButton.Legal testID='login-view-more' navigation={navigation} />
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
Site_Name: PropTypes.string,
|
||||
Accounts_RegistrationForm: PropTypes.string,
|
||||
Accounts_RegistrationForm_LinkReplacementText: PropTypes.string,
|
||||
Accounts_EmailOrUsernamePlaceholder: PropTypes.string,
|
||||
Accounts_PasswordPlaceholder: PropTypes.string,
|
||||
Accounts_PasswordReset: PropTypes.bool,
|
||||
Accounts_ShowFormLogin: PropTypes.bool,
|
||||
isFetching: PropTypes.bool,
|
||||
error: PropTypes.object,
|
||||
failure: PropTypes.bool,
|
||||
theme: PropTypes.string,
|
||||
loginRequest: PropTypes.func,
|
||||
inviteLinkToken: PropTypes.string
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
user: props.route.params?.username ?? '',
|
||||
|
@ -78,12 +83,16 @@ class LoginView extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps: IProps) {
|
||||
const { error } = this.props;
|
||||
if (nextProps.failure && !dequal(error, nextProps.error)) {
|
||||
if (nextProps.error?.error === 'error-invalid-email') {
|
||||
this.resendEmailConfirmation();
|
||||
} else {
|
||||
Alert.alert(I18n.t('Oops'), I18n.t('Login_error'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get showRegistrationButton() {
|
||||
const { Accounts_RegistrationForm, inviteLinkToken } = this.props;
|
||||
|
@ -105,6 +114,12 @@ class LoginView extends React.Component {
|
|||
navigation.navigate('ForgotPasswordView', { title: Site_Name });
|
||||
};
|
||||
|
||||
resendEmailConfirmation = () => {
|
||||
const { user } = this.state;
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate('SendEmailConfirmationView', { user });
|
||||
};
|
||||
|
||||
valid = () => {
|
||||
const { user, password } = this.state;
|
||||
return user.trim() && password.trim();
|
||||
|
@ -146,7 +161,7 @@ class LoginView extends React.Component {
|
|||
placeholder={Accounts_EmailOrUsernamePlaceholder || I18n.t('Username_or_email')}
|
||||
keyboardType='email-address'
|
||||
returnKeyType='next'
|
||||
onChangeText={value => this.setState({ user: value })}
|
||||
onChangeText={(value: string) => this.setState({ user: value })}
|
||||
onSubmitEditing={() => {
|
||||
this.passwordInput.focus();
|
||||
}}
|
||||
|
@ -166,7 +181,7 @@ class LoginView extends React.Component {
|
|||
returnKeyType='send'
|
||||
secureTextEntry
|
||||
onSubmitEditing={this.submit}
|
||||
onChangeText={value => this.setState({ password: value })}
|
||||
onChangeText={(value: string) => this.setState({ password: value })}
|
||||
testID='login-view-password'
|
||||
textContentType='password'
|
||||
autoCompleteType='password'
|
||||
|
@ -227,7 +242,7 @@ class LoginView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
server: state.server.server,
|
||||
Site_Name: state.settings.Site_Name,
|
||||
Accounts_ShowFormLogin: state.settings.Accounts_ShowFormLogin,
|
||||
|
@ -242,8 +257,8 @@ const mapStateToProps = state => ({
|
|||
inviteLinkToken: state.inviteLinks.token
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
loginRequest: params => dispatch(loginRequestAction(params))
|
||||
const mapDispatchToProps = (dispatch: any) => ({
|
||||
loginRequest: (params: any) => dispatch(loginRequestAction(params))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(LoginView));
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { CustomIcon } from '../../../lib/Icons';
|
||||
import sharedStyles from '../../Styles';
|
||||
import Touch from '../../../utils/touch';
|
||||
import { IServer } from '../index';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -27,13 +27,20 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const Item = ({ item, theme, onPress, onDelete }) => (
|
||||
interface IItem {
|
||||
item: IServer;
|
||||
theme: string;
|
||||
onPress(url: string): void;
|
||||
onDelete(item: IServer): void;
|
||||
}
|
||||
|
||||
const Item = ({ item, theme, onPress, onDelete }: IItem): JSX.Element => (
|
||||
<Touch style={styles.container} onPress={() => onPress(item.url)} theme={theme} testID={`server-history-${item.url}`}>
|
||||
<View style={styles.content}>
|
||||
<Text numberOfLines={1} style={[styles.server, { color: themes[theme].bodyText }]}>
|
||||
{item.url}
|
||||
</Text>
|
||||
<Text numberOfLines={1} style={[styles.username, { color: themes[theme].auxiliaryText }]}>
|
||||
<Text numberOfLines={1} style={{ color: themes[theme].auxiliaryText }}>
|
||||
{item.username}
|
||||
</Text>
|
||||
</View>
|
||||
|
@ -43,11 +50,4 @@ const Item = ({ item, theme, onPress, onDelete }) => (
|
|||
</Touch>
|
||||
);
|
||||
|
||||
Item.propTypes = {
|
||||
item: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
onPress: PropTypes.func,
|
||||
onDelete: PropTypes.func
|
||||
};
|
||||
|
||||
export default Item;
|
|
@ -1,12 +1,12 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FlatList, StyleSheet, View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import TextInput from '../../../containers/TextInput';
|
||||
import * as List from '../../../containers/List';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import I18n from '../../../i18n';
|
||||
import Item from './Item';
|
||||
import { IServer } from '../index';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -28,7 +28,25 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const ServerInput = ({ text, theme, serversHistory, onChangeText, onSubmit, onDelete, onPressServerHistory }) => {
|
||||
interface IServerInput {
|
||||
text: string;
|
||||
theme: string;
|
||||
serversHistory: any[];
|
||||
onChangeText(text: string): void;
|
||||
onSubmit(): void;
|
||||
onDelete(item: IServer): void;
|
||||
onPressServerHistory(serverHistory: IServer): void;
|
||||
}
|
||||
|
||||
const ServerInput = ({
|
||||
text,
|
||||
theme,
|
||||
serversHistory,
|
||||
onChangeText,
|
||||
onSubmit,
|
||||
onDelete,
|
||||
onPressServerHistory
|
||||
}: IServerInput): JSX.Element => {
|
||||
const [focused, setFocused] = useState(false);
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
|
@ -68,14 +86,4 @@ const ServerInput = ({ text, theme, serversHistory, onChangeText, onSubmit, onDe
|
|||
);
|
||||
};
|
||||
|
||||
ServerInput.propTypes = {
|
||||
text: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
serversHistory: PropTypes.array,
|
||||
onChangeText: PropTypes.func,
|
||||
onSubmit: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
onPressServerHistory: PropTypes.func
|
||||
};
|
||||
|
||||
export default ServerInput;
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Text, Keyboard, StyleSheet, View, BackHandler, Image } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { Base64 } from 'js-base64';
|
||||
|
@ -7,6 +6,9 @@ import parse from 'url-parse';
|
|||
import { Q } from '@nozbe/watermelondb';
|
||||
import { TouchableOpacity } from 'react-native-gesture-handler';
|
||||
import Orientation from 'react-native-orientation-locker';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { Dispatch } from 'redux';
|
||||
import Model from '@nozbe/watermelondb/Model';
|
||||
|
||||
import UserPreferences from '../../lib/userPreferences';
|
||||
import EventEmitter from '../../utils/events';
|
||||
|
@ -19,7 +21,6 @@ import FormContainer, { FormContainerInner } from '../../containers/FormContaine
|
|||
import I18n from '../../i18n';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { events, logEvent } from '../../utils/log';
|
||||
import { animateNextTransition } from '../../utils/layoutAnimation';
|
||||
import { withTheme } from '../../theme';
|
||||
import { BASIC_AUTH_KEY, setBasicAuth } from '../../utils/fetch';
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
|
@ -66,19 +67,32 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
class NewServerView extends React.Component {
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
connecting: PropTypes.bool.isRequired,
|
||||
connectServer: PropTypes.func.isRequired,
|
||||
selectServer: PropTypes.func.isRequired,
|
||||
previousServer: PropTypes.string,
|
||||
inviteLinksClear: PropTypes.func,
|
||||
serverFinishAdd: PropTypes.func
|
||||
};
|
||||
export interface IServer extends Model {
|
||||
url: string;
|
||||
username: string;
|
||||
}
|
||||
interface INewServerView {
|
||||
navigation: StackNavigationProp<any, 'NewServerView'>;
|
||||
theme: string;
|
||||
connecting: boolean;
|
||||
connectServer(server: string, username?: string, fromServerHistory?: boolean): void;
|
||||
selectServer(server: string): void;
|
||||
previousServer: string;
|
||||
inviteLinksClear(): void;
|
||||
serverFinishAdd(): void;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
interface IState {
|
||||
text: string;
|
||||
connectingOpen: boolean;
|
||||
certificate: any;
|
||||
serversHistory: IServer[];
|
||||
}
|
||||
|
||||
class NewServerView extends React.Component<INewServerView, IState> {
|
||||
constructor(props: INewServerView) {
|
||||
super(props);
|
||||
if (!isTablet) {
|
||||
Orientation.lockToPortrait();
|
||||
|
@ -131,21 +145,21 @@ class NewServerView extends React.Component {
|
|||
return false;
|
||||
};
|
||||
|
||||
onChangeText = text => {
|
||||
onChangeText = (text: string) => {
|
||||
this.setState({ text });
|
||||
this.queryServerHistory(text);
|
||||
};
|
||||
|
||||
queryServerHistory = async text => {
|
||||
queryServerHistory = async (text?: string) => {
|
||||
const db = database.servers;
|
||||
try {
|
||||
const serversHistoryCollection = db.get('servers_history');
|
||||
let whereClause = [Q.where('username', Q.notEq(null)), Q.experimentalSortBy('updated_at', Q.desc), Q.experimentalTake(3)];
|
||||
const likeString = sanitizeLikeString(text);
|
||||
if (text) {
|
||||
const likeString = sanitizeLikeString(text);
|
||||
whereClause = [...whereClause, Q.where('url', Q.like(`%${likeString}%`))];
|
||||
}
|
||||
const serversHistory = await serversHistoryCollection.query(...whereClause).fetch();
|
||||
const serversHistory = (await serversHistoryCollection.query(...whereClause).fetch()) as IServer[];
|
||||
this.setState({ serversHistory });
|
||||
} catch {
|
||||
// Do nothing
|
||||
|
@ -158,7 +172,7 @@ class NewServerView extends React.Component {
|
|||
selectServer(previousServer);
|
||||
};
|
||||
|
||||
handleNewServerEvent = event => {
|
||||
handleNewServerEvent = (event: { server: string }) => {
|
||||
let { server } = event;
|
||||
if (!server) {
|
||||
return;
|
||||
|
@ -169,13 +183,11 @@ class NewServerView extends React.Component {
|
|||
connectServer(server);
|
||||
};
|
||||
|
||||
onPressServerHistory = serverHistory => {
|
||||
this.setState({ text: serverHistory?.url }, () =>
|
||||
this.submit({ fromServerHistory: true, username: serverHistory?.username })
|
||||
);
|
||||
onPressServerHistory = (serverHistory: IServer) => {
|
||||
this.setState({ text: serverHistory.url }, () => this.submit(true, serverHistory?.username));
|
||||
};
|
||||
|
||||
submit = async ({ fromServerHistory = false, username }) => {
|
||||
submit = async (fromServerHistory?: boolean, username?: string) => {
|
||||
logEvent(events.NS_CONNECT_TO_WORKSPACE);
|
||||
const { text, certificate } = this.state;
|
||||
const { connectServer } = this.props;
|
||||
|
@ -207,7 +219,7 @@ class NewServerView extends React.Component {
|
|||
connectServer('https://open.rocket.chat');
|
||||
};
|
||||
|
||||
basicAuth = async (server, text) => {
|
||||
basicAuth = async (server: string, text: string) => {
|
||||
try {
|
||||
const parsedUrl = parse(text, true);
|
||||
if (parsedUrl.auth.length) {
|
||||
|
@ -222,14 +234,14 @@ class NewServerView extends React.Component {
|
|||
|
||||
chooseCertificate = async () => {
|
||||
try {
|
||||
const certificate = await SSLPinning.pickCertificate();
|
||||
const certificate = await SSLPinning?.pickCertificate();
|
||||
this.setState({ certificate });
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
|
||||
completeUrl = url => {
|
||||
completeUrl = (url: string) => {
|
||||
const parsedUrl = parse(url, true);
|
||||
if (parsedUrl.auth.length) {
|
||||
url = parsedUrl.origin;
|
||||
|
@ -252,14 +264,11 @@ class NewServerView extends React.Component {
|
|||
return url.replace(/\/+$/, '').replace(/\\/g, '/');
|
||||
};
|
||||
|
||||
uriToPath = uri => uri.replace('file://', '');
|
||||
|
||||
saveCertificate = certificate => {
|
||||
animateNextTransition();
|
||||
this.setState({ certificate });
|
||||
};
|
||||
uriToPath = (uri: string) => uri.replace('file://', '');
|
||||
|
||||
handleRemove = () => {
|
||||
// TODO: Remove ts-ignore when migrate the showConfirmationAlert
|
||||
// @ts-ignore
|
||||
showConfirmationAlert({
|
||||
message: I18n.t('You_will_unset_a_certificate_for_this_server'),
|
||||
confirmationText: I18n.t('Remove'),
|
||||
|
@ -267,14 +276,15 @@ class NewServerView extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
deleteServerHistory = async item => {
|
||||
const { serversHistory } = this.state;
|
||||
deleteServerHistory = async (item: IServer) => {
|
||||
const db = database.servers;
|
||||
try {
|
||||
await db.action(async () => {
|
||||
await db.write(async () => {
|
||||
await item.destroyPermanently();
|
||||
});
|
||||
this.setState({ serversHistory: serversHistory.filter(server => server.id !== item.id) });
|
||||
this.setState((prevstate: IState) => ({
|
||||
serversHistory: prevstate.serversHistory.filter((server: IServer) => server.id !== item.id)
|
||||
}));
|
||||
} catch {
|
||||
// Nothing
|
||||
}
|
||||
|
@ -288,20 +298,21 @@ class NewServerView extends React.Component {
|
|||
style={[
|
||||
styles.certificatePicker,
|
||||
{
|
||||
marginBottom: verticalScale(previousServer && !isTablet ? 10 : 30, height)
|
||||
marginBottom: verticalScale({ size: previousServer && !isTablet ? 10 : 30, height })
|
||||
}
|
||||
]}>
|
||||
<Text
|
||||
style={[
|
||||
styles.chooseCertificateTitle,
|
||||
{ color: themes[theme].auxiliaryText, fontSize: moderateScale(13, null, width) }
|
||||
{ color: themes[theme].auxiliaryText, fontSize: moderateScale({ size: 13, width }) }
|
||||
]}>
|
||||
{certificate ? I18n.t('Your_certificate') : I18n.t('Do_you_have_a_certificate')}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={certificate ? this.handleRemove : this.chooseCertificate}
|
||||
testID='new-server-choose-certificate'>
|
||||
<Text style={[styles.chooseCertificate, { color: themes[theme].tintColor, fontSize: moderateScale(13, null, width) }]}>
|
||||
<Text
|
||||
style={[styles.chooseCertificate, { color: themes[theme].tintColor, fontSize: moderateScale({ size: 13, width }) }]}>
|
||||
{certificate ?? I18n.t('Apply_Your_Certificate')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
@ -321,10 +332,10 @@ class NewServerView extends React.Component {
|
|||
style={[
|
||||
styles.onboardingImage,
|
||||
{
|
||||
marginBottom: verticalScale(10, height),
|
||||
marginTop: isTablet ? 0 : verticalScale(marginTop, height),
|
||||
width: verticalScale(100, height),
|
||||
height: verticalScale(100, height)
|
||||
marginBottom: verticalScale({ size: 10, height }),
|
||||
marginTop: isTablet ? 0 : verticalScale({ size: marginTop, height }),
|
||||
width: verticalScale({ size: 100, height }),
|
||||
height: verticalScale({ size: 100, height })
|
||||
}
|
||||
]}
|
||||
source={require('../../static/images/logo.png')}
|
||||
|
@ -335,8 +346,8 @@ class NewServerView extends React.Component {
|
|||
styles.title,
|
||||
{
|
||||
color: themes[theme].titleText,
|
||||
fontSize: moderateScale(22, null, width),
|
||||
marginBottom: verticalScale(8, height)
|
||||
fontSize: moderateScale({ size: 22, width }),
|
||||
marginBottom: verticalScale({ size: 8, height })
|
||||
}
|
||||
]}>
|
||||
Rocket.Chat
|
||||
|
@ -346,8 +357,8 @@ class NewServerView extends React.Component {
|
|||
styles.subtitle,
|
||||
{
|
||||
color: themes[theme].controlText,
|
||||
fontSize: moderateScale(16, null, width),
|
||||
marginBottom: verticalScale(30, height)
|
||||
fontSize: moderateScale({ size: 16, width }),
|
||||
marginBottom: verticalScale({ size: 30, height })
|
||||
}
|
||||
]}>
|
||||
{I18n.t('Onboarding_subtitle')}
|
||||
|
@ -367,7 +378,7 @@ class NewServerView extends React.Component {
|
|||
onPress={this.submit}
|
||||
disabled={!text || connecting}
|
||||
loading={!connectingOpen && connecting}
|
||||
style={[styles.connectButton, { marginTop: verticalScale(16, height) }]}
|
||||
style={[styles.connectButton, { marginTop: verticalScale({ size: 16, height }) }]}
|
||||
theme={theme}
|
||||
testID='new-server-view-button'
|
||||
/>
|
||||
|
@ -377,8 +388,8 @@ class NewServerView extends React.Component {
|
|||
styles.description,
|
||||
{
|
||||
color: themes[theme].auxiliaryText,
|
||||
fontSize: moderateScale(14, null, width),
|
||||
marginBottom: verticalScale(16, height)
|
||||
fontSize: moderateScale({ size: 14, width }),
|
||||
marginBottom: verticalScale({ size: 16, height })
|
||||
}
|
||||
]}>
|
||||
{I18n.t('Onboarding_join_open_description')}
|
||||
|
@ -400,14 +411,15 @@ class NewServerView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
connecting: state.server.connecting,
|
||||
previousServer: state.server.previousServer
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
connectServer: (...params) => dispatch(serverRequest(...params)),
|
||||
selectServer: server => dispatch(selectServerRequest(server)),
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
connectServer: (server: string, username: string & null, fromServerHistory?: boolean) =>
|
||||
dispatch(serverRequest(server, username, fromServerHistory)),
|
||||
selectServer: (server: string) => dispatch(selectServerRequest(server)),
|
||||
inviteLinksClear: () => dispatch(inviteLinksClearAction()),
|
||||
serverFinishAdd: () => dispatch(serverFinishAddAction())
|
||||
});
|
|
@ -1,13 +1,12 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, Text, View } from 'react-native';
|
||||
import { FlatList, Text, View, RefreshControl } from 'react-native';
|
||||
import { dequal } from 'dequal';
|
||||
import moment from 'moment';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import * as List from '../../containers/List';
|
||||
import Avatar from '../../containers/Avatar';
|
||||
import ActivityIndicator from '../../containers/ActivityIndicator';
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
import I18n from '../../i18n';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
|
@ -85,12 +84,16 @@ class ReadReceiptView extends React.Component {
|
|||
};
|
||||
|
||||
renderEmpty = () => {
|
||||
const { loading } = this.state;
|
||||
const { theme } = this.props;
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<View
|
||||
style={[styles.listEmptyContainer, { backgroundColor: themes[theme].chatComponentBackground }]}
|
||||
testID='read-receipt-view'>
|
||||
<Text style={{ color: themes[theme].titleText }}>{I18n.t('No_Read_Receipts')}</Text>
|
||||
<Text style={[styles.emptyText, { color: themes[theme].auxiliaryTintColor }]}>{I18n.t('No_Read_Receipts')}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -107,9 +110,15 @@ class ReadReceiptView extends React.Component {
|
|||
<View style={styles.infoContainer}>
|
||||
<View style={styles.item}>
|
||||
<Text style={[styles.name, { color: themes[theme].titleText }]}>{item?.user?.name}</Text>
|
||||
<Text style={{ color: themes[theme].auxiliaryText }}>{time}</Text>
|
||||
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
|
||||
</View>
|
||||
<Text style={{ color: themes[theme].auxiliaryText }}>{`@${item.user.username}`}</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.username,
|
||||
{
|
||||
color: themes[theme].auxiliaryText
|
||||
}
|
||||
]}>{`@${item.user.username}`}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
@ -119,20 +128,15 @@ class ReadReceiptView extends React.Component {
|
|||
const { receipts, loading } = this.state;
|
||||
const { theme } = this.props;
|
||||
|
||||
if (!loading && receipts.length === 0) {
|
||||
return this.renderEmpty();
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView testID='read-receipt-view'>
|
||||
<StatusBar />
|
||||
{loading ? (
|
||||
<ActivityIndicator theme={theme} />
|
||||
) : (
|
||||
<FlatList
|
||||
data={receipts}
|
||||
renderItem={this.renderItem}
|
||||
ItemSeparatorComponent={List.Separator}
|
||||
ListEmptyComponent={this.renderEmpty}
|
||||
contentContainerStyle={List.styles.contentContainerStyleFlatList}
|
||||
style={[
|
||||
styles.list,
|
||||
{
|
||||
|
@ -140,9 +144,9 @@ class ReadReceiptView extends React.Component {
|
|||
borderColor: themes[theme].separatorColor
|
||||
}
|
||||
]}
|
||||
refreshControl={<RefreshControl refreshing={loading} onRefresh={this.load} tintColor={themes[theme].auxiliaryText} />}
|
||||
keyExtractor={item => item._id}
|
||||
/>
|
||||
)}
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,9 +8,14 @@ export default StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
emptyText: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
item: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
separator: {
|
||||
|
@ -20,6 +25,14 @@ export default StyleSheet.create({
|
|||
...sharedStyles.textRegular,
|
||||
fontSize: 17
|
||||
},
|
||||
username: {
|
||||
...sharedStyles.textMedium,
|
||||
fontSize: 14
|
||||
},
|
||||
time: {
|
||||
...sharedStyles.textRegular,
|
||||
fontSize: 12
|
||||
},
|
||||
infoContainer: {
|
||||
flex: 1,
|
||||
marginLeft: 10
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Keyboard, StyleSheet, Text, View } from 'react-native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { RouteProp } from '@react-navigation/core';
|
||||
import { connect } from 'react-redux';
|
||||
import RNPickerSelect from 'react-native-picker-select';
|
||||
|
||||
|
@ -49,27 +50,37 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
class RegisterView extends React.Component {
|
||||
static navigationOptions = ({ route, navigation }) => ({
|
||||
title: route.params?.title ?? 'Rocket.Chat',
|
||||
interface IProps {
|
||||
navigation: StackNavigationProp<any>;
|
||||
route: RouteProp<any, 'RegisterView'>;
|
||||
server: string;
|
||||
Site_Name: string;
|
||||
Gitlab_URL: string;
|
||||
CAS_enabled: boolean;
|
||||
CAS_login_url: string;
|
||||
Accounts_CustomFields: string;
|
||||
Accounts_EmailVerification: boolean;
|
||||
Accounts_ManuallyApproveNewUsers: boolean;
|
||||
showLoginButton: boolean;
|
||||
loginRequest: Function;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
class RegisterView extends React.Component<IProps, any> {
|
||||
private parsedCustomFields: any;
|
||||
private usernameInput: any;
|
||||
private passwordInput: any;
|
||||
private emailInput: any;
|
||||
private avatarUrl: any;
|
||||
|
||||
static navigationOptions = ({ route, navigation }: Partial<IProps>) => ({
|
||||
title: route?.params?.title ?? 'Rocket.Chat',
|
||||
headerRight: () => <HeaderButton.Legal testID='register-view-more' navigation={navigation} />
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
server: PropTypes.string,
|
||||
Accounts_CustomFields: PropTypes.string,
|
||||
Accounts_EmailVerification: PropTypes.bool,
|
||||
Accounts_ManuallyApproveNewUsers: PropTypes.bool,
|
||||
theme: PropTypes.string,
|
||||
Site_Name: PropTypes.string,
|
||||
loginRequest: PropTypes.func,
|
||||
showLoginButton: PropTypes.bool
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
const customFields = {};
|
||||
const customFields: any = {};
|
||||
this.parsedCustomFields = {};
|
||||
if (props.Accounts_CustomFields) {
|
||||
try {
|
||||
|
@ -78,7 +89,7 @@ class RegisterView extends React.Component {
|
|||
log(e);
|
||||
}
|
||||
}
|
||||
Object.keys(this.parsedCustomFields).forEach(key => {
|
||||
Object.keys(this.parsedCustomFields).forEach((key: string) => {
|
||||
if (this.parsedCustomFields[key].defaultValue) {
|
||||
customFields[key] = this.parsedCustomFields[key].defaultValue;
|
||||
}
|
||||
|
@ -101,7 +112,7 @@ class RegisterView extends React.Component {
|
|||
valid = () => {
|
||||
const { name, email, password, username, customFields } = this.state;
|
||||
let requiredCheck = true;
|
||||
Object.keys(this.parsedCustomFields).forEach(key => {
|
||||
Object.keys(this.parsedCustomFields).forEach((key: string) => {
|
||||
if (this.parsedCustomFields[key].required) {
|
||||
requiredCheck = requiredCheck && customFields[key] && Boolean(customFields[key].trim());
|
||||
}
|
||||
|
@ -138,7 +149,7 @@ class RegisterView extends React.Component {
|
|||
} else {
|
||||
await loginRequest({ user: email, password });
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
if (e.data?.errorType === 'username-invalid') {
|
||||
return loginRequest({ user: email, password });
|
||||
}
|
||||
|
@ -150,7 +161,7 @@ class RegisterView extends React.Component {
|
|||
this.setState({ saving: false });
|
||||
};
|
||||
|
||||
openContract = route => {
|
||||
openContract = (route: string) => {
|
||||
const { server, theme } = this.props;
|
||||
if (!server) {
|
||||
return;
|
||||
|
@ -167,19 +178,20 @@ class RegisterView extends React.Component {
|
|||
try {
|
||||
return Object.keys(this.parsedCustomFields).map((key, index, array) => {
|
||||
if (this.parsedCustomFields[key].type === 'select') {
|
||||
const options = this.parsedCustomFields[key].options.map(option => ({ label: option, value: option }));
|
||||
const options = this.parsedCustomFields[key].options.map((option: string) => ({ label: option, value: option }));
|
||||
return (
|
||||
<RNPickerSelect
|
||||
key={key}
|
||||
items={options}
|
||||
onValueChange={value => {
|
||||
const newValue = {};
|
||||
const newValue: { [key: string]: string | number } = {};
|
||||
newValue[key] = value;
|
||||
this.setState({ customFields: { ...customFields, ...newValue } });
|
||||
}}
|
||||
value={customFields[key]}>
|
||||
<TextInput
|
||||
inputRef={e => {
|
||||
inputRef={(e: any) => {
|
||||
// @ts-ignore
|
||||
this[key] = e;
|
||||
}}
|
||||
placeholder={key}
|
||||
|
@ -193,20 +205,22 @@ class RegisterView extends React.Component {
|
|||
|
||||
return (
|
||||
<TextInput
|
||||
inputRef={e => {
|
||||
inputRef={(e: any) => {
|
||||
// @ts-ignore
|
||||
this[key] = e;
|
||||
}}
|
||||
key={key}
|
||||
label={key}
|
||||
placeholder={key}
|
||||
value={customFields[key]}
|
||||
onChangeText={value => {
|
||||
const newValue = {};
|
||||
onChangeText={(value: string) => {
|
||||
const newValue: { [key: string]: string | number } = {};
|
||||
newValue[key] = value;
|
||||
this.setState({ customFields: { ...customFields, ...newValue } });
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
if (array.length - 1 > index) {
|
||||
// @ts-ignore
|
||||
return this[array[index + 1]].focus();
|
||||
}
|
||||
this.avatarUrl.focus();
|
||||
|
@ -234,7 +248,7 @@ class RegisterView extends React.Component {
|
|||
containerStyle={styles.inputContainer}
|
||||
placeholder={I18n.t('Name')}
|
||||
returnKeyType='next'
|
||||
onChangeText={name => this.setState({ name })}
|
||||
onChangeText={(name: string) => this.setState({ name })}
|
||||
onSubmitEditing={() => {
|
||||
this.usernameInput.focus();
|
||||
}}
|
||||
|
@ -249,7 +263,7 @@ class RegisterView extends React.Component {
|
|||
}}
|
||||
placeholder={I18n.t('Username')}
|
||||
returnKeyType='next'
|
||||
onChangeText={username => this.setState({ username })}
|
||||
onChangeText={(username: string) => this.setState({ username })}
|
||||
onSubmitEditing={() => {
|
||||
this.emailInput.focus();
|
||||
}}
|
||||
|
@ -265,7 +279,7 @@ class RegisterView extends React.Component {
|
|||
placeholder={I18n.t('Email')}
|
||||
returnKeyType='next'
|
||||
keyboardType='email-address'
|
||||
onChangeText={email => this.setState({ email })}
|
||||
onChangeText={(email: string) => this.setState({ email })}
|
||||
onSubmitEditing={() => {
|
||||
this.passwordInput.focus();
|
||||
}}
|
||||
|
@ -281,7 +295,7 @@ class RegisterView extends React.Component {
|
|||
placeholder={I18n.t('Password')}
|
||||
returnKeyType='send'
|
||||
secureTextEntry
|
||||
onChangeText={value => this.setState({ password: value })}
|
||||
onChangeText={(value: string) => this.setState({ password: value })}
|
||||
onSubmitEditing={this.submit}
|
||||
testID='register-view-password'
|
||||
theme={theme}
|
||||
|
@ -334,7 +348,7 @@ class RegisterView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
server: state.server.server,
|
||||
Site_Name: state.settings.Site_Name,
|
||||
Gitlab_URL: state.settings.API_Gitlab_URL,
|
||||
|
@ -346,8 +360,8 @@ const mapStateToProps = state => ({
|
|||
showLoginButton: getShowLoginButton(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
loginRequest: params => dispatch(loginRequestAction(params))
|
||||
const mapDispatchToProps = (dispatch: any) => ({
|
||||
loginRequest: (params: any) => dispatch(loginRequestAction(params))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(RegisterView));
|
|
@ -14,6 +14,7 @@ import { animateNextTransition } from '../../../utils/layoutAnimation';
|
|||
import ActivityIndicator from '../../../containers/ActivityIndicator';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import debounce from '../../../utils/debounce';
|
||||
import { compareServerVersion, methods } from '../../../lib/utils';
|
||||
import List from './List';
|
||||
import NavBottomFAB from './NavBottomFAB';
|
||||
|
||||
|
@ -43,7 +44,8 @@ class ListContainer extends React.Component {
|
|||
tunread: PropTypes.array,
|
||||
ignored: PropTypes.array,
|
||||
navigation: PropTypes.object,
|
||||
showMessageInMainThread: PropTypes.bool
|
||||
showMessageInMainThread: PropTypes.bool,
|
||||
serverVersion: PropTypes.string
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -131,7 +133,7 @@ class ListContainer extends React.Component {
|
|||
|
||||
query = async () => {
|
||||
this.count += QUERY_SIZE;
|
||||
const { rid, tmid, showMessageInMainThread } = this.props;
|
||||
const { rid, tmid, showMessageInMainThread, serverVersion } = this.props;
|
||||
const db = database.active;
|
||||
|
||||
// handle servers with version < 3.0.0
|
||||
|
@ -172,7 +174,14 @@ class ListContainer extends React.Component {
|
|||
if (tmid && this.thread) {
|
||||
messages = [...messages, this.thread];
|
||||
}
|
||||
|
||||
/**
|
||||
* Since 3.16.0 server version, the backend don't response with messages if
|
||||
* hide system message is enabled
|
||||
*/
|
||||
if (compareServerVersion(serverVersion, '3.16.0', methods.lowerThan) || hideSystemMessages.length) {
|
||||
messages = messages.filter(m => !m.t || !hideSystemMessages?.includes(m.t));
|
||||
}
|
||||
|
||||
if (this.mounted) {
|
||||
this.setState({ messages }, () => this.update());
|
||||
|
|
|
@ -120,6 +120,7 @@ class RoomView extends React.Component {
|
|||
Message_Read_Receipt_Enabled: PropTypes.bool,
|
||||
Hide_System_Messages: PropTypes.array,
|
||||
baseUrl: PropTypes.string,
|
||||
serverVersion: PropTypes.string,
|
||||
customEmojis: PropTypes.object,
|
||||
isMasterDetail: PropTypes.bool,
|
||||
theme: PropTypes.string,
|
||||
|
@ -1154,7 +1155,7 @@ class RoomView extends React.Component {
|
|||
render() {
|
||||
console.count(`${this.constructor.name}.render calls`);
|
||||
const { room, reactionsModalVisible, selectedMessage, loading, reacting, showingBlockingLoader } = this.state;
|
||||
const { user, baseUrl, theme, navigation, Hide_System_Messages, width, height } = this.props;
|
||||
const { user, baseUrl, theme, navigation, Hide_System_Messages, width, height, serverVersion } = this.props;
|
||||
const { rid, t, sysMes, bannerClosed, announcement } = room;
|
||||
|
||||
return (
|
||||
|
@ -1182,6 +1183,7 @@ class RoomView extends React.Component {
|
|||
navigation={navigation}
|
||||
hideSystemMessages={Array.isArray(sysMes) ? sysMes : Hide_System_Messages}
|
||||
showMessageInMainThread={user.showMessageInMainThread}
|
||||
serverVersion={serverVersion}
|
||||
/>
|
||||
{this.renderFooter()}
|
||||
{this.renderActions()}
|
||||
|
@ -1220,6 +1222,7 @@ const mapStateToProps = state => ({
|
|||
Message_TimeFormat: state.settings.Message_TimeFormat,
|
||||
customEmojis: state.customEmojis,
|
||||
baseUrl: state.server.server,
|
||||
serverVersion: state.server.version,
|
||||
Message_Read_Receipt_Enabled: state.settings.Message_Read_Receipt_Enabled,
|
||||
Hide_System_Messages: state.settings.Hide_System_Messages
|
||||
});
|
||||
|
|
|
@ -613,6 +613,8 @@ class RoomsListView extends React.Component {
|
|||
|
||||
isRead = item => RocketChat.isRead(item);
|
||||
|
||||
isSwipeEnabled = item => !(item?.search || item?.joinCodeRequired || item?.outside);
|
||||
|
||||
getUserPresence = uid => RocketChat.getUserPresence(uid);
|
||||
|
||||
getUidDirectMessage = room => RocketChat.getUidDirectMessage(room);
|
||||
|
@ -928,6 +930,7 @@ class RoomsListView extends React.Component {
|
|||
displayMode
|
||||
} = this.props;
|
||||
const id = this.getUidDirectMessage(item);
|
||||
const swipeEnabled = this.isSwipeEnabled(item);
|
||||
|
||||
return (
|
||||
<RoomItem
|
||||
|
@ -950,6 +953,7 @@ class RoomsListView extends React.Component {
|
|||
getIsRead={this.isRead}
|
||||
visitor={item.visitor}
|
||||
isFocused={currentItem?.rid === item.rid}
|
||||
swipeEnabled={swipeEnabled}
|
||||
showAvatar={showAvatar}
|
||||
displayMode={displayMode}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
|
||||
import TextInput from '../containers/TextInput';
|
||||
import Button from '../containers/Button';
|
||||
import { showErrorAlert } from '../utils/info';
|
||||
import isValidEmail from '../utils/isValidEmail';
|
||||
import I18n from '../i18n';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import { useTheme } from '../theme';
|
||||
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
|
||||
import log, { events, logEvent } from '../utils/log';
|
||||
import sharedStyles from './Styles';
|
||||
|
||||
interface ISendEmailConfirmationView {
|
||||
navigation: StackNavigationProp<any, 'SendEmailConfirmationView'>;
|
||||
route: {
|
||||
params: {
|
||||
user?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const SendEmailConfirmationView = ({ navigation, route }: ISendEmailConfirmationView): JSX.Element => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [invalidEmail, setInvalidEmail] = useState(true);
|
||||
const [isFetching, setIsFetching] = useState(false);
|
||||
|
||||
const { theme } = useTheme();
|
||||
|
||||
const validate = (val: string) => {
|
||||
const isInvalidEmail = !isValidEmail(val);
|
||||
setEmail(val);
|
||||
setInvalidEmail(isInvalidEmail);
|
||||
};
|
||||
|
||||
const resendConfirmationEmail = async () => {
|
||||
logEvent(events.SEC_SEND_EMAIL_CONFIRMATION);
|
||||
if (invalidEmail || !email) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setIsFetching(true);
|
||||
const result = await RocketChat.sendConfirmationEmail(email);
|
||||
if (result.success) {
|
||||
navigation.pop();
|
||||
showErrorAlert(I18n.t('Verify_email_desc'));
|
||||
}
|
||||
} catch (e: any) {
|
||||
log(e);
|
||||
const msg = e?.data?.error || I18n.t('There_was_an_error_while_action', { action: I18n.t('sending_email_confirmation') });
|
||||
showErrorAlert(msg, I18n.t('Alert'));
|
||||
}
|
||||
setIsFetching(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
title: 'Rocket.Chat'
|
||||
});
|
||||
if (route.params?.user) {
|
||||
validate(route.params.user);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FormContainer theme={theme} testID='send-email-confirmation-view'>
|
||||
<FormContainerInner>
|
||||
<TextInput
|
||||
autoFocus
|
||||
placeholder={I18n.t('Email')}
|
||||
keyboardType='email-address'
|
||||
returnKeyType='send'
|
||||
onChangeText={(email: string) => validate(email)}
|
||||
onSubmitEditing={resendConfirmationEmail}
|
||||
testID='send-email-confirmation-view-email'
|
||||
containerStyle={sharedStyles.inputLastChild}
|
||||
theme={theme}
|
||||
value={email}
|
||||
/>
|
||||
<Button
|
||||
title={I18n.t('Send_email_confirmation')}
|
||||
type='primary'
|
||||
onPress={resendConfirmationEmail}
|
||||
testID='send-email-confirmation-view-submit'
|
||||
loading={isFetching}
|
||||
disabled={invalidEmail}
|
||||
theme={theme}
|
||||
/>
|
||||
</FormContainerInner>
|
||||
</FormContainer>
|
||||
);
|
||||
};
|
||||
export default SendEmailConfirmationView;
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import { Clipboard, Linking, Share } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||
import CookieManager from '@react-native-cookies/cookies';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
|
||||
import { logout as logoutAction } from '../../actions/login';
|
||||
import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
|
||||
|
@ -29,8 +29,25 @@ import database from '../../lib/database';
|
|||
import { isFDroidBuild } from '../../constants/environment';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
|
||||
class SettingsView extends React.Component {
|
||||
static navigationOptions = ({ navigation, isMasterDetail }) => ({
|
||||
interface IProps {
|
||||
navigation: StackNavigationProp<any, 'SettingsView'>;
|
||||
server: {
|
||||
version: string;
|
||||
server: string;
|
||||
};
|
||||
theme: string;
|
||||
isMasterDetail: boolean;
|
||||
logout: Function;
|
||||
selectServerRequest: Function;
|
||||
user: {
|
||||
roles: [];
|
||||
id: string;
|
||||
};
|
||||
appStart: Function;
|
||||
}
|
||||
|
||||
class SettingsView extends React.Component<IProps, any> {
|
||||
static navigationOptions = ({ navigation, isMasterDetail }: Partial<IProps>) => ({
|
||||
headerLeft: () =>
|
||||
isMasterDetail ? (
|
||||
<HeaderButton.CloseModal navigation={navigation} testID='settings-view-close' />
|
||||
|
@ -40,26 +57,12 @@ class SettingsView extends React.Component {
|
|||
title: I18n.t('Settings')
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
server: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
isMasterDetail: PropTypes.bool,
|
||||
logout: PropTypes.func.isRequired,
|
||||
selectServerRequest: PropTypes.func,
|
||||
user: PropTypes.shape({
|
||||
roles: PropTypes.array,
|
||||
id: PropTypes.string
|
||||
}),
|
||||
appStart: PropTypes.func
|
||||
};
|
||||
|
||||
checkCookiesAndLogout = async () => {
|
||||
const { logout, user } = this.props;
|
||||
const db = database.servers;
|
||||
const usersCollection = db.get('users');
|
||||
try {
|
||||
const userRecord = await usersCollection.find(user.id);
|
||||
const userRecord: any = await usersCollection.find(user.id);
|
||||
if (userRecord.isFromWebView) {
|
||||
showConfirmationAlert({
|
||||
title: I18n.t('Clear_cookies_alert'),
|
||||
|
@ -84,6 +87,7 @@ class SettingsView extends React.Component {
|
|||
|
||||
handleLogout = () => {
|
||||
logEvent(events.SE_LOG_OUT);
|
||||
// @ts-ignore
|
||||
showConfirmationAlert({
|
||||
message: I18n.t('You_will_be_logged_out_of_this_application'),
|
||||
confirmationText: I18n.t('Logout'),
|
||||
|
@ -93,6 +97,7 @@ class SettingsView extends React.Component {
|
|||
|
||||
handleClearCache = () => {
|
||||
logEvent(events.SE_CLEAR_LOCAL_SERVER_CACHE);
|
||||
/* @ts-ignore */
|
||||
showConfirmationAlert({
|
||||
message: I18n.t('This_will_clear_all_your_offline_data'),
|
||||
confirmationText: I18n.t('Clear'),
|
||||
|
@ -112,7 +117,8 @@ class SettingsView extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
navigateToScreen = screen => {
|
||||
navigateToScreen = (screen: string) => {
|
||||
/* @ts-ignore */
|
||||
logEvent(events[`SE_GO_${screen.replace('View', '').toUpperCase()}`]);
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate(screen);
|
||||
|
@ -160,7 +166,7 @@ class SettingsView extends React.Component {
|
|||
this.saveToClipboard(getReadableVersion);
|
||||
};
|
||||
|
||||
saveToClipboard = async content => {
|
||||
saveToClipboard = async (content: string) => {
|
||||
await Clipboard.setString(content);
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
||||
};
|
||||
|
@ -293,16 +299,16 @@ class SettingsView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = (state: any) => ({
|
||||
server: state.server,
|
||||
user: getUserSelector(state),
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
const mapDispatchToProps = (dispatch: any) => ({
|
||||
logout: () => dispatch(logoutAction()),
|
||||
selectServerRequest: params => dispatch(selectServerRequestAction(params)),
|
||||
appStart: params => dispatch(appStartAction(params))
|
||||
selectServerRequest: (params: any) => dispatch(selectServerRequestAction(params)),
|
||||
appStart: (params: any) => dispatch(appStartAction(params))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(SettingsView));
|
|
@ -1,28 +1,45 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Switch } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
import { events, logEvent } from '../../utils/log';
|
||||
import log, { logEvent, events } from '../../utils/log';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import * as List from '../../containers/List';
|
||||
import { SWITCH_TRACK_COLOR } from '../../constants/colors';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
|
||||
class UserPreferencesView extends React.Component {
|
||||
static navigationOptions = () => ({
|
||||
const UserPreferencesView = ({ navigation }) => {
|
||||
const user = useSelector(state => getUserSelector(state));
|
||||
const [enableParser, setEnableParser] = useState(user.enableMessageParserEarlyAdoption);
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
title: I18n.t('Preferences')
|
||||
});
|
||||
}, []);
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object
|
||||
};
|
||||
|
||||
navigateToScreen = (screen, params) => {
|
||||
const navigateToScreen = (screen, params) => {
|
||||
logEvent(events[`SE_GO_${screen.replace('View', '').toUpperCase()}`]);
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate(screen, params);
|
||||
};
|
||||
|
||||
render() {
|
||||
const toggleMessageParser = async value => {
|
||||
try {
|
||||
await RocketChat.saveUserPreferences({ id: user.id, enableMessageParserEarlyAdoption: value });
|
||||
setEnableParser(value);
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const renderMessageParserSwitch = () => (
|
||||
<Switch value={enableParser} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleMessageParser} />
|
||||
);
|
||||
|
||||
return (
|
||||
<SafeAreaView testID='preferences-view'>
|
||||
<StatusBar />
|
||||
|
@ -31,16 +48,28 @@ class UserPreferencesView extends React.Component {
|
|||
<List.Separator />
|
||||
<List.Item
|
||||
title='Notifications'
|
||||
onPress={() => this.navigateToScreen('UserNotificationPrefView')}
|
||||
onPress={() => navigateToScreen('UserNotificationPrefView')}
|
||||
showActionIndicator
|
||||
testID='preferences-view-notifications'
|
||||
/>
|
||||
<List.Separator />
|
||||
</List.Section>
|
||||
<List.Section>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title='Enable_Message_Parser'
|
||||
testID='preferences-view-enable-message-parser'
|
||||
right={() => renderMessageParserSwitch()}
|
||||
/>
|
||||
<List.Separator />
|
||||
</List.Section>
|
||||
</List.Container>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
UserPreferencesView.propTypes = {
|
||||
navigation: PropTypes.object
|
||||
};
|
||||
|
||||
export default UserPreferencesView;
|
||||
|
|
135
ios/Podfile.lock
135
ios/Podfile.lock
|
@ -4,36 +4,40 @@ PODS:
|
|||
- React-Core
|
||||
- CocoaAsyncSocket (7.6.5)
|
||||
- DoubleConversion (1.1.6)
|
||||
- EXAppleAuthentication (2.2.1):
|
||||
- EXAppleAuthentication (3.2.1):
|
||||
- UMCore
|
||||
- EXAV (8.2.1):
|
||||
- EXAV (9.2.3):
|
||||
- ExpoModulesCore
|
||||
- UMCore
|
||||
- UMFileSystemInterface
|
||||
- UMPermissionsInterface
|
||||
- EXConstants (9.1.1):
|
||||
- UMConstantsInterface
|
||||
- EXConstants (11.0.2):
|
||||
- ExpoModulesCore
|
||||
- UMCore
|
||||
- EXFileSystem (9.0.1):
|
||||
- EXFileSystem (11.1.3):
|
||||
- ExpoModulesCore
|
||||
- UMCore
|
||||
- UMFileSystemInterface
|
||||
- EXHaptics (8.2.1):
|
||||
- EXHaptics (10.1.0):
|
||||
- UMCore
|
||||
- EXImageLoader (1.1.1):
|
||||
- EXImageLoader (2.2.0):
|
||||
- ExpoModulesCore
|
||||
- React-Core
|
||||
- UMCore
|
||||
- UMImageLoaderInterface
|
||||
- EXKeepAwake (8.2.1):
|
||||
- EXKeepAwake (9.2.0):
|
||||
- UMCore
|
||||
- EXLocalAuthentication (9.2.0):
|
||||
- UMConstantsInterface
|
||||
- EXLocalAuthentication (11.1.1):
|
||||
- UMCore
|
||||
- EXPermissions (9.0.1):
|
||||
- ExpoModulesCore (0.2.0):
|
||||
- ExpoModulesCore/Core (= 0.2.0)
|
||||
- ExpoModulesCore/Interfaces (= 0.2.0)
|
||||
- UMCore
|
||||
- UMPermissionsInterface
|
||||
- EXVideoThumbnails (5.1.0):
|
||||
- ExpoModulesCore/Core (0.2.0):
|
||||
- UMCore
|
||||
- UMFileSystemInterface
|
||||
- EXWebBrowser (8.3.1):
|
||||
- ExpoModulesCore/Interfaces (0.2.0):
|
||||
- ExpoModulesCore/Core
|
||||
- UMCore
|
||||
- EXVideoThumbnails (5.2.1):
|
||||
- ExpoModulesCore
|
||||
- UMCore
|
||||
- EXWebBrowser (9.2.0):
|
||||
- UMCore
|
||||
- FBLazyVector (0.64.2)
|
||||
- FBReactNativeSpec (0.64.2):
|
||||
|
@ -584,23 +588,14 @@ PODS:
|
|||
- SDWebImage/Core (~> 5.5)
|
||||
- simdjson (0.9.6-fix2)
|
||||
- TOCropViewController (2.5.3)
|
||||
- UMAppLoader (1.2.0)
|
||||
- UMBarCodeScannerInterface (5.2.1)
|
||||
- UMCameraInterface (5.2.1)
|
||||
- UMConstantsInterface (5.2.1)
|
||||
- UMCore (5.3.0)
|
||||
- UMFaceDetectorInterface (5.2.1)
|
||||
- UMFileSystemInterface (5.2.1)
|
||||
- UMFontInterface (5.2.1)
|
||||
- UMImageLoaderInterface (5.2.1)
|
||||
- UMPermissionsInterface (5.2.1):
|
||||
- UMCore
|
||||
- UMReactNativeAdapter (5.4.0):
|
||||
- UMAppLoader (2.2.0)
|
||||
- UMCore (7.1.2)
|
||||
- UMReactNativeAdapter (6.3.9):
|
||||
- ExpoModulesCore
|
||||
- React-Core
|
||||
- UMCore
|
||||
- UMFontInterface
|
||||
- UMSensorsInterface (5.2.1)
|
||||
- UMTaskManagerInterface (5.2.1)
|
||||
- UMTaskManagerInterface (6.2.0):
|
||||
- UMCore
|
||||
- WatermelonDB (0.23.0):
|
||||
- React
|
||||
- React-jsi
|
||||
|
@ -619,7 +614,7 @@ DEPENDENCIES:
|
|||
- EXImageLoader (from `../node_modules/expo-image-loader/ios`)
|
||||
- EXKeepAwake (from `../node_modules/expo-keep-awake/ios`)
|
||||
- EXLocalAuthentication (from `../node_modules/expo-local-authentication/ios`)
|
||||
- EXPermissions (from `../node_modules/expo-permissions/ios`)
|
||||
- ExpoModulesCore (from `../node_modules/expo-modules-core/ios`)
|
||||
- EXVideoThumbnails (from `../node_modules/expo-video-thumbnails/ios`)
|
||||
- EXWebBrowser (from `../node_modules/expo-web-browser/ios`)
|
||||
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
|
||||
|
@ -714,17 +709,8 @@ DEPENDENCIES:
|
|||
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
|
||||
- "simdjson (from `../node_modules/@nozbe/simdjson`)"
|
||||
- UMAppLoader (from `../node_modules/unimodules-app-loader/ios`)
|
||||
- UMBarCodeScannerInterface (from `../node_modules/unimodules-barcode-scanner-interface/ios`)
|
||||
- UMCameraInterface (from `../node_modules/unimodules-camera-interface/ios`)
|
||||
- UMConstantsInterface (from `../node_modules/unimodules-constants-interface/ios`)
|
||||
- "UMCore (from `../node_modules/@unimodules/core/ios`)"
|
||||
- UMFaceDetectorInterface (from `../node_modules/unimodules-face-detector-interface/ios`)
|
||||
- UMFileSystemInterface (from `../node_modules/unimodules-file-system-interface/ios`)
|
||||
- UMFontInterface (from `../node_modules/unimodules-font-interface/ios`)
|
||||
- UMImageLoaderInterface (from `../node_modules/unimodules-image-loader-interface/ios`)
|
||||
- UMPermissionsInterface (from `../node_modules/unimodules-permissions-interface/ios`)
|
||||
- "UMReactNativeAdapter (from `../node_modules/@unimodules/react-native-adapter/ios`)"
|
||||
- UMSensorsInterface (from `../node_modules/unimodules-sensors-interface/ios`)
|
||||
- UMTaskManagerInterface (from `../node_modules/unimodules-task-manager-interface/ios`)
|
||||
- "WatermelonDB (from `../node_modules/@nozbe/watermelondb`)"
|
||||
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
|
||||
|
@ -784,8 +770,8 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/expo-keep-awake/ios"
|
||||
EXLocalAuthentication:
|
||||
:path: "../node_modules/expo-local-authentication/ios"
|
||||
EXPermissions:
|
||||
:path: "../node_modules/expo-permissions/ios"
|
||||
ExpoModulesCore:
|
||||
:path: "../node_modules/expo-modules-core/ios"
|
||||
EXVideoThumbnails:
|
||||
:path: "../node_modules/expo-video-thumbnails/ios"
|
||||
EXWebBrowser:
|
||||
|
@ -926,28 +912,10 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/@nozbe/simdjson"
|
||||
UMAppLoader:
|
||||
:path: "../node_modules/unimodules-app-loader/ios"
|
||||
UMBarCodeScannerInterface:
|
||||
:path: "../node_modules/unimodules-barcode-scanner-interface/ios"
|
||||
UMCameraInterface:
|
||||
:path: "../node_modules/unimodules-camera-interface/ios"
|
||||
UMConstantsInterface:
|
||||
:path: "../node_modules/unimodules-constants-interface/ios"
|
||||
UMCore:
|
||||
:path: "../node_modules/@unimodules/core/ios"
|
||||
UMFaceDetectorInterface:
|
||||
:path: "../node_modules/unimodules-face-detector-interface/ios"
|
||||
UMFileSystemInterface:
|
||||
:path: "../node_modules/unimodules-file-system-interface/ios"
|
||||
UMFontInterface:
|
||||
:path: "../node_modules/unimodules-font-interface/ios"
|
||||
UMImageLoaderInterface:
|
||||
:path: "../node_modules/unimodules-image-loader-interface/ios"
|
||||
UMPermissionsInterface:
|
||||
:path: "../node_modules/unimodules-permissions-interface/ios"
|
||||
UMReactNativeAdapter:
|
||||
:path: "../node_modules/@unimodules/react-native-adapter/ios"
|
||||
UMSensorsInterface:
|
||||
:path: "../node_modules/unimodules-sensors-interface/ios"
|
||||
UMTaskManagerInterface:
|
||||
:path: "../node_modules/unimodules-task-manager-interface/ios"
|
||||
WatermelonDB:
|
||||
|
@ -965,17 +933,17 @@ SPEC CHECKSUMS:
|
|||
BugsnagReactNative: a97b3132c1854fd7bf92350fabd505e3ebdd7829
|
||||
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
|
||||
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
|
||||
EXAppleAuthentication: 5b3da71bada29e2423d8ea27e5538ef0d75aba62
|
||||
EXAV: 86344030966e0da7e00556fbb97269d9ad16071d
|
||||
EXConstants: f907b3b6ce16e20d1750f22af1e095e924574bcb
|
||||
EXFileSystem: 76875135b61708b9afa7e6a89b72a60ba0fdfa20
|
||||
EXHaptics: 5428b344a216ca5d9df6ca8f65720b2a1ad9f109
|
||||
EXImageLoader: 02ca02c9cd5cc8a97b423207a73a791e0a86bea5
|
||||
EXKeepAwake: 8b0f68242f036b971f9f8976341823cbe6f50812
|
||||
EXLocalAuthentication: 985c65e08a6eb84f8f98b51f7435df138b18b9e8
|
||||
EXPermissions: 80ac3acbdb145930079810fe5b08c022b3428aa8
|
||||
EXVideoThumbnails: cd257fc6e07884a704a5674d362a6410933acb68
|
||||
EXWebBrowser: d37a5ffdea1b65947352bc001dd9f732463725d4
|
||||
EXAppleAuthentication: e8c537fcbe80670dd76fde7a07acb94af70ada00
|
||||
EXAV: 67bcc1d0afeb1fab854b206c84b9f2afbd61d0cd
|
||||
EXConstants: 4cb52b6d8f636c767104a44bf7db3873e9c01a6f
|
||||
EXFileSystem: 0a04aba8da751b9ac954065911bcf166503f8267
|
||||
EXHaptics: 6dc4307ab0794fe7a87ec8d7d1c299cf103d6cb3
|
||||
EXImageLoader: d3531a3fe530b22925c19977cb53bb43e3821fe6
|
||||
EXKeepAwake: f4105ef469be7b283f66ce2d7234bb71ac80cd26
|
||||
EXLocalAuthentication: 88a1a69ea66c4934387d1eb503628170c853caef
|
||||
ExpoModulesCore: 2734852616127a6c1fc23012197890a6f3763dc7
|
||||
EXVideoThumbnails: 442c3abadb51a81551a3b53705b7560de390e6f7
|
||||
EXWebBrowser: 76783ba5dcb8699237746ecf41a9643d428a4cc5
|
||||
FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b
|
||||
FBReactNativeSpec: 110d69378fce79af38271c39894b59fec7890221
|
||||
Firebase: 919186c8e119dd9372a45fd1dd17a8a942bc1892
|
||||
|
@ -1071,19 +1039,10 @@ SPEC CHECKSUMS:
|
|||
SDWebImageWebPCoder: 36f8f47bd9879a8aea6044765c1351120fd8e3a8
|
||||
simdjson: 85016870cd17207312b718ef6652eb6a1cd6a2b0
|
||||
TOCropViewController: 20a14b6a7a098308bf369e7c8d700dc983a974e6
|
||||
UMAppLoader: 61049c8d55590b74e9ae1d5429bf68d96b4a2528
|
||||
UMBarCodeScannerInterface: e5e4c87797d3d01214e25cd1618866caf5d4f17f
|
||||
UMCameraInterface: 415ac060034edecacdbbaa739c223e3f276e0056
|
||||
UMConstantsInterface: 1a52f2d884c95e8829439da13e36b7669a1a8fb4
|
||||
UMCore: d98083b522b08c0a8ba3992bc263c624ae5d887c
|
||||
UMFaceDetectorInterface: 67c6c82451338da01a4bc00ec46365a2a8ea9057
|
||||
UMFileSystemInterface: 303d696ede28102a7e11d111808bd2ed2c5eb62f
|
||||
UMFontInterface: 6edf1ee8bc55d2030766f8cf0a7b20a5d5a913b0
|
||||
UMImageLoaderInterface: 9cdbf3bab6a513bddd88505cb2340fe02d6a11c0
|
||||
UMPermissionsInterface: 019170ad655f464e3f8d23d2a8bcbda2e645cde4
|
||||
UMReactNativeAdapter: 538efe92e781b5d7678cf95b34c46f2d0989a557
|
||||
UMSensorsInterface: cb5bf31d52c4349f0ff9e3c049bbe4df0d80d383
|
||||
UMTaskManagerInterface: 80653f25c55d9e6d79d6a0a65589fa213feaee11
|
||||
UMAppLoader: 21af63390e55c82e037fb9752d93114a80ecf16e
|
||||
UMCore: ce3a4faa010239063b8343895b29a6d97b01069d
|
||||
UMReactNativeAdapter: d03cefd0e4e4179ab8c490408589f1c8a6c8b785
|
||||
UMTaskManagerInterface: 2be431101b73604e64fbfffcf759336f9d8fccbb
|
||||
WatermelonDB: 577c61fceff16e9f9103b59d14aee4850c0307b6
|
||||
Yoga: 575c581c63e0d35c9a83f4b46d01d63abc1100ac
|
||||
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
||||
|
|
20
package.json
20
package.json
|
@ -47,23 +47,25 @@
|
|||
"@react-navigation/drawer": "5.12.5",
|
||||
"@react-navigation/native": "5.9.4",
|
||||
"@react-navigation/stack": "5.14.5",
|
||||
"@rocket.chat/message-parser": "0.30.0",
|
||||
"@rocket.chat/react-native-fast-image": "^8.2.0",
|
||||
"@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile",
|
||||
"@rocket.chat/ui-kit": "0.13.0",
|
||||
"@types/url-parse": "^1.4.4",
|
||||
"bytebuffer": "^5.0.1",
|
||||
"color2k": "1.2.4",
|
||||
"commonmark": "git+https://github.com/RocketChat/commonmark.js.git",
|
||||
"commonmark-react-renderer": "git+https://github.com/RocketChat/commonmark-react-renderer.git",
|
||||
"dequal": "^2.0.2",
|
||||
"ejson": "2.2.1",
|
||||
"expo-apple-authentication": "^2.2.1",
|
||||
"expo-av": "8.2.1",
|
||||
"expo-file-system": "9.0.1",
|
||||
"expo-haptics": "8.2.1",
|
||||
"expo-keep-awake": "8.2.1",
|
||||
"expo-local-authentication": "9.2.0",
|
||||
"expo-video-thumbnails": "5.1.0",
|
||||
"expo-web-browser": "8.3.1",
|
||||
"expo-apple-authentication": "3.2.1",
|
||||
"expo-av": "9.2.3",
|
||||
"expo-file-system": "11.1.3",
|
||||
"expo-haptics": "10.1.0",
|
||||
"expo-keep-awake": "9.2.0",
|
||||
"expo-local-authentication": "11.1.1",
|
||||
"expo-video-thumbnails": "5.2.1",
|
||||
"expo-web-browser": "9.2.0",
|
||||
"hoist-non-react-statics": "3.3.2",
|
||||
"i18n-js": "3.8.0",
|
||||
"js-base64": "3.6.1",
|
||||
|
@ -112,7 +114,7 @@
|
|||
"react-native-simple-crypto": "RocketChat/react-native-simple-crypto#0.5.0",
|
||||
"react-native-slowlog": "^1.0.2",
|
||||
"react-native-ui-lib": "RocketChat/react-native-ui-lib#minor-improvements",
|
||||
"react-native-unimodules": "0.10.1",
|
||||
"react-native-unimodules": "^0.14.8",
|
||||
"react-native-vector-icons": "8.1.0",
|
||||
"react-native-webview": "10.3.2",
|
||||
"react-redux": "7.2.4",
|
||||
|
|
|
@ -0,0 +1,627 @@
|
|||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { storiesOf } from '@storybook/react-native';
|
||||
|
||||
import NewMarkdown from '../../app/containers/markdown/new';
|
||||
import { themes } from '../../app/constants/colors';
|
||||
|
||||
const stories = storiesOf('NewMarkdown', module);
|
||||
|
||||
const theme = 'light';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginHorizontal: 15,
|
||||
backgroundColor: themes[theme].backgroundColor,
|
||||
marginVertical: 50
|
||||
},
|
||||
separator: {
|
||||
marginHorizontal: 10,
|
||||
marginVertical: 10
|
||||
}
|
||||
});
|
||||
|
||||
const getCustomEmoji = content => {
|
||||
const customEmoji = {
|
||||
marioparty: { name: content, extension: 'gif' },
|
||||
nyan_rocket: { name: content, extension: 'png' }
|
||||
}[content];
|
||||
return customEmoji;
|
||||
};
|
||||
const baseUrl = 'https://open.rocket.chat';
|
||||
|
||||
const simpleTextMsg = [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'This is Rocket.Chat'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const longTextMsg = [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value:
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const lineBreakMsg = [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'a'
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'b'
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'c'
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'd'
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'e'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const sequentialEmptySpacesMsg = [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'a b c'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const boldOrUnderscoreMsg = [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'BOLD',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'This is bold'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: ' and '
|
||||
},
|
||||
{
|
||||
type: 'ITALIC',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'this is italic'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
stories.add('Text', () => (
|
||||
<View style={styles.container}>
|
||||
<NewMarkdown tokens={simpleTextMsg} />
|
||||
<NewMarkdown tokens={longTextMsg} />
|
||||
<NewMarkdown tokens={lineBreakMsg} />
|
||||
<NewMarkdown tokens={sequentialEmptySpacesMsg} />
|
||||
<NewMarkdown tokens={boldOrUnderscoreMsg} />
|
||||
</View>
|
||||
));
|
||||
|
||||
const allMentionTokens = [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'MENTION_USER',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'rocket.cat'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const multipleMentionTokens = [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'MENTION_USER',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'name'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: ' '
|
||||
},
|
||||
{
|
||||
type: 'MENTION_USER',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'rocket.cat'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: ' '
|
||||
},
|
||||
{
|
||||
type: 'MENTION_USER',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'here'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: ' '
|
||||
},
|
||||
{
|
||||
type: 'MENTION_USER',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'all'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const allMentions = [
|
||||
{
|
||||
_id: 'rocket.cat',
|
||||
username: 'rocket.cat'
|
||||
}
|
||||
];
|
||||
|
||||
const multipleMentions = [
|
||||
{
|
||||
_id: 'name',
|
||||
username: 'name'
|
||||
},
|
||||
{
|
||||
_id: 'rocket.cat',
|
||||
username: 'rocket.cat'
|
||||
},
|
||||
{
|
||||
_id: 'here',
|
||||
username: 'here'
|
||||
},
|
||||
{
|
||||
_id: 'all',
|
||||
username: 'all'
|
||||
}
|
||||
];
|
||||
|
||||
stories.add('Mentions', () => (
|
||||
<View style={styles.container}>
|
||||
<NewMarkdown tokens={allMentionTokens} mentions={allMentions} navToRoomInfo={() => {}} style={[]} />
|
||||
<NewMarkdown
|
||||
tokens={multipleMentionTokens}
|
||||
mentions={multipleMentions}
|
||||
navToRoomInfo={() => {}}
|
||||
style={[]}
|
||||
username='rocket.cat'
|
||||
/>
|
||||
</View>
|
||||
));
|
||||
|
||||
const channelTokens = [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'MENTION_CHANNEL',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'text_channel'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: ' and '
|
||||
},
|
||||
{
|
||||
type: 'MENTION_CHANNEL',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'not_a_channel'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const channelMention = [
|
||||
{
|
||||
_id: 'text_channel',
|
||||
name: 'text_channel'
|
||||
}
|
||||
];
|
||||
|
||||
stories.add('Hashtag', () => (
|
||||
<View style={styles.container}>
|
||||
<NewMarkdown tokens={channelTokens} channels={channelMention} navToRoomInfo={() => {}} />
|
||||
</View>
|
||||
));
|
||||
|
||||
const bigEmojiTokens = [
|
||||
{
|
||||
type: 'BIG_EMOJI',
|
||||
value: [
|
||||
{
|
||||
type: 'EMOJI',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'green_heart'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'EMOJI',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'joy'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'EMOJI',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'grin'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const emojiTokens = [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'EMOJI',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'rocket'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'EMOJI',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'facepalm'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'EMOJI',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'nyan_rocket'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'EMOJI',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'marioparty'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
stories.add('Emoji', () => (
|
||||
<View style={styles.container}>
|
||||
<NewMarkdown tokens={bigEmojiTokens} />
|
||||
<NewMarkdown tokens={emojiTokens} getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} />
|
||||
</View>
|
||||
));
|
||||
|
||||
const blockQuoteTokens = [
|
||||
{
|
||||
type: 'QUOTE',
|
||||
value: [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'Rocket.Chat to the moon'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
stories.add('Block quote', () => (
|
||||
<View style={styles.container}>
|
||||
<NewMarkdown tokens={blockQuoteTokens} />
|
||||
</View>
|
||||
));
|
||||
|
||||
const rocketChatLink = [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'LINK',
|
||||
value: {
|
||||
src: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'https://rocket.chat'
|
||||
},
|
||||
label: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'https://rocket.chat'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const markdownLink = [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'LINK',
|
||||
value: {
|
||||
src: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'https://rocket.chat'
|
||||
},
|
||||
label: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'Markdown link'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
stories.add('Links', () => (
|
||||
<View style={styles.container}>
|
||||
<NewMarkdown tokens={rocketChatLink} />
|
||||
<NewMarkdown tokens={markdownLink} />
|
||||
</View>
|
||||
));
|
||||
|
||||
stories.add('Headers', () => (
|
||||
<View style={styles.container}>
|
||||
<NewMarkdown
|
||||
tokens={[
|
||||
{
|
||||
type: 'HEADING',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: '# Header 1'
|
||||
}
|
||||
],
|
||||
level: 1
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<NewMarkdown
|
||||
tokens={[
|
||||
{
|
||||
type: 'HEADING',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: '## Header 2'
|
||||
}
|
||||
],
|
||||
level: 2
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<NewMarkdown
|
||||
tokens={[
|
||||
{
|
||||
type: 'HEADING',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: '### Header 3'
|
||||
}
|
||||
],
|
||||
level: 3
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<NewMarkdown
|
||||
tokens={[
|
||||
{
|
||||
type: 'HEADING',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: '#### Header 4'
|
||||
}
|
||||
],
|
||||
level: 4
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<NewMarkdown
|
||||
tokens={[
|
||||
{
|
||||
type: 'HEADING',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: '##### Header 5'
|
||||
}
|
||||
],
|
||||
level: 5
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<NewMarkdown
|
||||
tokens={[
|
||||
{
|
||||
type: 'HEADING',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: '###### Header 6'
|
||||
}
|
||||
],
|
||||
level: 6
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
));
|
||||
|
||||
const inlineCodeToken = [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'INLINE_CODE',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'inline code'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const multilineCodeToken = [
|
||||
{
|
||||
type: 'CODE',
|
||||
language: 'none',
|
||||
value: [
|
||||
{
|
||||
type: 'CODE_LINE',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'Multi line '
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'CODE_LINE',
|
||||
value: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'Code'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
stories.add('Code', () => (
|
||||
<View style={styles.container}>
|
||||
<NewMarkdown tokens={inlineCodeToken} style={[]} />
|
||||
<NewMarkdown tokens={multilineCodeToken} style={[]} />
|
||||
</View>
|
||||
));
|
||||
|
||||
const unorederedListToken = [
|
||||
{
|
||||
type: 'UNORDERED_LIST',
|
||||
value: [
|
||||
{
|
||||
type: 'LIST_ITEM',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'Open Source'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'LIST_ITEM',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'Rocket.Chat'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const orderedListToken = [
|
||||
{
|
||||
type: 'ORDERED_LIST',
|
||||
value: [
|
||||
{
|
||||
type: 'LIST_ITEM',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'Open Source'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'LIST_ITEM',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'Rocket.Chat'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
stories.add('Lists', () => (
|
||||
<View style={styles.container}>
|
||||
<NewMarkdown tokens={unorederedListToken} />
|
||||
<NewMarkdown tokens={orderedListToken} />
|
||||
</View>
|
||||
));
|
|
@ -12,6 +12,7 @@ import './HeaderButtons';
|
|||
import './UnreadBadge';
|
||||
import '../../app/views/ThreadMessagesView/Item.stories.js';
|
||||
import './Avatar';
|
||||
import './NewMarkdown';
|
||||
import '../../app/containers/BackgroundContainer/index.stories.js';
|
||||
import '../../app/containers/RoomHeader/RoomHeader.stories.js';
|
||||
import '../../app/views/RoomView/LoadMore/LoadMore.stories';
|
||||
|
|
Loading…
Reference in New Issue