Chore: Migration to Hooks - Markdown (#4264)

* chore: migrate TextInput from class to functional

* changing from themes[theme] to colors

* removing markdown theme props from other files

* adding a force update and fix a stories

* adding testID and tests for markdown

* fixing some interfaces

* minor tweak

Co-authored-by: GleidsonDaniel <gleidson10daniel@hotmail.com>
This commit is contained in:
Alex Junior 2022-06-27 18:27:22 -03:00 committed by GitHub
parent 18c44178d7
commit 4fd0084bc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 531 additions and 438 deletions

View File

@ -44,7 +44,14 @@ const Avatar = React.memo(
let image; let image;
if (emoji) { if (emoji) {
image = ( image = (
<Emoji baseUrl={server} getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} style={avatarStyle} /> <Emoji
baseUrl={server}
getCustomEmoji={getCustomEmoji}
isMessageContainsOnlyEmoji
literal={emoji}
style={avatarStyle}
testID='avatar'
/>
); );
} else { } else {
let uri = avatar; let uri = avatar;

View File

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import FastImage from 'react-native-fast-image'; import FastImage from 'react-native-fast-image';
import { ICustomEmoji } from '../../definitions/IEmoji'; import { ICustomEmoji } from '../../definitions';
const CustomEmoji = React.memo( const CustomEmoji = React.memo(
({ baseUrl, emoji, style }: ICustomEmoji) => ( ({ baseUrl, emoji, style, testID }: ICustomEmoji) => (
<FastImage <FastImage
style={style} style={style}
source={{ source={{
@ -12,6 +12,7 @@ const CustomEmoji = React.memo(
priority: FastImage.priority.high priority: FastImage.priority.high
}} }}
resizeMode={FastImage.resizeMode.contain} resizeMode={FastImage.resizeMode.contain}
testID={testID}
/> />
), ),
(prevProps, nextProps) => { (prevProps, nextProps) => {

View File

@ -76,7 +76,7 @@ class MessageParser extends UiKitParserMessage<React.ReactElement> {
<MarkdownPreview msg={element.text} style={[isContext && { color: themes[theme].auxiliaryText }]} numberOfLines={0} /> <MarkdownPreview msg={element.text} style={[isContext && { color: themes[theme].auxiliaryText }]} numberOfLines={0} />
); );
} }
return <Markdown msg={element.text} theme={theme} style={[isContext && { color: themes[theme].auxiliaryText }]} />; return <Markdown msg={element.text} style={[isContext && { color: themes[theme].auxiliaryText }]} />;
} }
button(element: IButton, context: BlockContext) { button(element: IButton, context: BlockContext) {

View File

@ -2,7 +2,6 @@ import React from 'react';
import { StyleProp, Text, TextStyle } from 'react-native'; import { StyleProp, Text, TextStyle } from 'react-native';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import { themes } from '../../lib/constants';
import styles from './styles'; import styles from './styles';
import { events, logEvent } from '../../lib/methods/helpers/log'; import { events, logEvent } from '../../lib/methods/helpers/log';
import { IUserMention } from './interfaces'; import { IUserMention } from './interfaces';
@ -14,20 +13,22 @@ interface IAtMention {
style?: StyleProp<TextStyle>[]; style?: StyleProp<TextStyle>[];
useRealName?: boolean; useRealName?: boolean;
mentions?: IUserMention[]; mentions?: IUserMention[];
testID: string;
} }
const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, style = [], useRealName }: IAtMention) => { const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, style = [], useRealName, testID }: IAtMention) => {
const { theme } = useTheme(); const { colors } = useTheme();
if (mention === 'all' || mention === 'here') { if (mention === 'all' || mention === 'here') {
return ( return (
<Text <Text
style={[ style={[
styles.mention, styles.mention,
{ {
color: themes[theme].mentionGroupColor color: colors.mentionGroupColor
}, },
...style ...style
]}> ]}
testID={`${testID}-mention-all-here`}>
{mention} {mention}
</Text> </Text>
); );
@ -36,11 +37,11 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl
let mentionStyle = {}; let mentionStyle = {};
if (mention === username) { if (mention === username) {
mentionStyle = { mentionStyle = {
color: themes[theme].mentionMeColor color: colors.mentionMeColor
}; };
} else { } else {
mentionStyle = { mentionStyle = {
color: themes[theme].mentionOtherColor color: colors.mentionOtherColor
}; };
} }
@ -59,13 +60,15 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl
if (user) { if (user) {
return ( return (
<Text style={[styles.mention, mentionStyle, ...style]} onPress={handlePress}> <Text style={[styles.mention, mentionStyle, ...style]} onPress={handlePress} testID={`${testID}-mention-users`}>
{useRealName && user.name ? user.name : user.username} {useRealName && user.name ? user.name : user.username}
</Text> </Text>
); );
} }
return <Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}>{`@${mention}`}</Text>; return (
<Text style={[styles.text, { color: colors.bodyText }, ...style]} testID={`${testID}-mention-unknown`}>{`@${mention}`}</Text>
);
}); });
export default AtMention; export default AtMention;

View File

@ -1,20 +1,21 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { TSupportedThemes } from '../../theme'; import { useTheme } from '../../theme';
import { themes } from '../../lib/constants';
import styles from './styles'; import styles from './styles';
interface IBlockQuote { interface IBlockQuote {
children: React.ReactElement | null; children: React.ReactElement | null;
theme: TSupportedThemes;
} }
const BlockQuote = React.memo(({ children, theme }: IBlockQuote) => ( const BlockQuote = React.memo(({ children }: IBlockQuote) => {
<View style={styles.container}> const { colors } = useTheme();
<View style={[styles.quote, { backgroundColor: themes[theme].borderColor }]} /> return (
<View style={styles.childContainer}>{children}</View> <View style={styles.container} testID='markdown-block-quote'>
</View> <View style={[styles.quote, { backgroundColor: colors.borderColor }]} />
)); <View style={styles.childContainer}>{children}</View>
</View>
);
});
export default BlockQuote; export default BlockQuote;

View File

@ -15,10 +15,11 @@ interface IEmoji {
style?: object; style?: object;
onEmojiSelected?: Function; onEmojiSelected?: Function;
tabEmojiStyle?: object; tabEmojiStyle?: object;
testID: string;
} }
const Emoji = React.memo( const Emoji = React.memo(
({ literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, customEmojis = true, style = {} }: IEmoji) => { ({ literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, testID, customEmojis = true, style = {} }: IEmoji) => {
const { colors } = useTheme(); const { colors } = useTheme();
const emojiUnicode = shortnameToUnicode(literal); const emojiUnicode = shortnameToUnicode(literal);
const emoji: any = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, '')); const emoji: any = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, ''));
@ -28,11 +29,14 @@ const Emoji = React.memo(
baseUrl={baseUrl} baseUrl={baseUrl}
style={[isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji, style]} style={[isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji, style]}
emoji={emoji} emoji={emoji}
testID={`${testID}-custom-emoji`}
/> />
); );
} }
return ( return (
<Text style={[{ color: colors.bodyText }, isMessageContainsOnlyEmoji ? styles.textBig : styles.text, style]}> <Text
style={[{ color: colors.bodyText }, isMessageContainsOnlyEmoji ? styles.textBig : styles.text, style]}
testID={`${testID}-unicode-emoji`}>
{emojiUnicode} {emojiUnicode}
</Text> </Text>
); );

View File

@ -3,7 +3,6 @@ import { StyleProp, Text, TextStyle } from 'react-native';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import { IUserChannel } from './interfaces'; import { IUserChannel } from './interfaces';
import styles from './styles'; import styles from './styles';
@ -17,10 +16,11 @@ interface IHashtag {
navToRoomInfo?: Function; navToRoomInfo?: Function;
style?: StyleProp<TextStyle>[]; style?: StyleProp<TextStyle>[];
channels?: IUserChannel[]; channels?: IUserChannel[];
testID: string;
} }
const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IHashtag) => { const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, testID, style = [] }: IHashtag) => {
const { theme } = useTheme(); const { colors } = useTheme();
const isMasterDetail = useAppSelector(state => state.app.isMasterDetail); const isMasterDetail = useAppSelector(state => state.app.isMasterDetail);
const navigation = useNavigation<StackNavigationProp<ChatsStackParamList, 'RoomView'>>(); const navigation = useNavigation<StackNavigationProp<ChatsStackParamList, 'RoomView'>>();
@ -46,16 +46,21 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IH
style={[ style={[
styles.mention, styles.mention,
{ {
color: themes[theme].mentionOtherColor color: colors.mentionOtherColor
}, },
...style ...style
]} ]}
onPress={handlePress}> onPress={handlePress}
testID={`${testID}-hashtag-channels`}>
{`#${hashtag}`} {`#${hashtag}`}
</Text> </Text>
); );
} }
return <Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}>{`#${hashtag}`}</Text>; return (
<Text
style={[styles.text, { color: colors.bodyText }, ...style]}
testID={`${testID}-hashtag-without-channels`}>{`#${hashtag}`}</Text>
);
}); });
export default Hashtag; export default Hashtag;

View File

@ -3,22 +3,23 @@ import { Text } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard'; import Clipboard from '@react-native-clipboard/clipboard';
import styles from './styles'; import styles from './styles';
import { themes } from '../../lib/constants';
import { LISTENER } from '../Toast'; import { LISTENER } from '../Toast';
import EventEmitter from '../../lib/methods/helpers/events'; import EventEmitter from '../../lib/methods/helpers/events';
import I18n from '../../i18n'; import I18n from '../../i18n';
import openLink from '../../lib/methods/helpers/openLink'; import openLink from '../../lib/methods/helpers/openLink';
import { TOnLinkPress } from './interfaces'; import { TOnLinkPress } from './interfaces';
import { TSupportedThemes } from '../../theme'; import { useTheme } from '../../theme';
interface ILink { interface ILink {
children: React.ReactElement | null; children: React.ReactElement | null;
link: string; link: string;
theme: TSupportedThemes;
onLinkPress?: TOnLinkPress; onLinkPress?: TOnLinkPress;
testID: string;
} }
const Link = React.memo(({ children, link, theme, onLinkPress }: ILink) => { const Link = React.memo(({ children, link, onLinkPress, testID }: ILink) => {
const { colors, theme } = useTheme();
const handlePress = () => { const handlePress = () => {
if (!link) { if (!link) {
return; return;
@ -37,7 +38,11 @@ const Link = React.memo(({ children, link, theme, onLinkPress }: ILink) => {
// if you have a [](https://rocket.chat) render https://rocket.chat // if you have a [](https://rocket.chat) render https://rocket.chat
return ( return (
<Text onPress={handlePress} onLongPress={onLongPress} style={{ ...styles.link, color: themes[theme].actionTintColor }}> <Text
onPress={handlePress}
onLongPress={onLongPress}
style={{ ...styles.link, color: colors.actionTintColor }}
testID={`${testID}-link`}>
{childLength !== 0 ? children : link} {childLength !== 0 ? children : link}
</Text> </Text>
); );

View File

@ -1,8 +1,7 @@
import React from 'react'; import React from 'react';
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import { TSupportedThemes } from '../../theme'; import { useTheme } from '../../theme';
import { themes } from '../../lib/constants';
const style = StyleSheet.create({ const style = StyleSheet.create({
container: { container: {
@ -24,11 +23,13 @@ interface IListItem {
level: number; level: number;
ordered: boolean; ordered: boolean;
continue: boolean; continue: boolean;
theme: TSupportedThemes;
index: number; index: number;
testID: string;
} }
const ListItem = React.memo(({ children, level, bulletWidth, continue: _continue, ordered, index, theme }: IListItem) => { const ListItem = React.memo(({ children, level, bulletWidth, continue: _continue, ordered, index, testID }: IListItem) => {
const { colors } = useTheme();
let bullet; let bullet;
if (_continue) { if (_continue) {
bullet = ''; bullet = '';
@ -41,9 +42,9 @@ const ListItem = React.memo(({ children, level, bulletWidth, continue: _continue
} }
return ( return (
<View style={style.container}> <View style={style.container} testID={`${testID}-list`}>
<View style={[{ width: bulletWidth }, style.bullet]}> <View style={[{ width: bulletWidth }, style.bullet]}>
<Text style={{ color: themes[theme].bodyText }}>{bullet}</Text> <Text style={{ color: colors.bodyText }}>{bullet}</Text>
</View> </View>
<View style={style.contents}>{children}</View> <View style={style.contents}>{children}</View>
</View> </View>

View File

@ -5,24 +5,25 @@ import { CELL_WIDTH } from './TableCell';
import Navigation from '../../lib/navigation/appNavigation'; import Navigation from '../../lib/navigation/appNavigation';
import styles from './styles'; import styles from './styles';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { TSupportedThemes } from '../../theme'; import { useTheme } from '../../theme';
import { themes } from '../../lib/constants';
import { useAppSelector } from '../../lib/hooks'; import { useAppSelector } from '../../lib/hooks';
interface ITable { interface ITable {
children: React.ReactElement | null; children: React.ReactElement | null;
numColumns: number; numColumns: number;
theme: TSupportedThemes; testID: string;
} }
const MAX_HEIGHT = 300; const MAX_HEIGHT = 300;
const Table = React.memo(({ children, numColumns, theme }: ITable) => { const Table = React.memo(({ children, numColumns, testID }: ITable) => {
const { colors } = useTheme();
const getTableWidth = () => numColumns * CELL_WIDTH; const getTableWidth = () => numColumns * CELL_WIDTH;
const isMasterDetail = useAppSelector(state => state.app.isMasterDetail); const isMasterDetail = useAppSelector(state => state.app.isMasterDetail);
const renderRows = (drawExtraBorders = true) => { const renderRows = (drawExtraBorders = true) => {
const tableStyle: ViewStyle[] = [styles.table, { borderColor: themes[theme].borderColor }]; const tableStyle: ViewStyle[] = [styles.table, { borderColor: colors.borderColor }];
if (drawExtraBorders) { if (drawExtraBorders) {
tableStyle.push(styles.tableExtraBorders); tableStyle.push(styles.tableExtraBorders);
} }
@ -47,18 +48,15 @@ const Table = React.memo(({ children, numColumns, theme }: ITable) => {
}; };
return ( return (
<TouchableOpacity onPress={onPress}> <TouchableOpacity onPress={onPress} testID={`${testID}-table`}>
<ScrollView <ScrollView
contentContainerStyle={{ width: getTableWidth() }} contentContainerStyle={{ width: getTableWidth() }}
scrollEnabled={false} scrollEnabled={false}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
style={[ style={[styles.containerTable, { maxWidth: getTableWidth(), maxHeight: MAX_HEIGHT, borderColor: colors.borderColor }]}>
styles.containerTable,
{ maxWidth: getTableWidth(), maxHeight: MAX_HEIGHT, borderColor: themes[theme].borderColor }
]}>
{renderRows(false)} {renderRows(false)}
</ScrollView> </ScrollView>
<Text style={[styles.textInfo, { color: themes[theme].auxiliaryText }]}>{I18n.t('Full_table')}</Text> <Text style={[styles.textInfo, { color: colors.auxiliaryText }]}>{I18n.t('Full_table')}</Text>
</TouchableOpacity> </TouchableOpacity>
); );
}); });

View File

@ -1,21 +1,21 @@
import React from 'react'; import React from 'react';
import { Text, View, ViewStyle } from 'react-native'; import { Text, View, ViewStyle } from 'react-native';
import { TSupportedThemes } from '../../theme'; import { useTheme } from '../../theme';
import { themes } from '../../lib/constants';
import styles from './styles'; import styles from './styles';
interface ITableCell { export interface ITableCell {
align: '' | 'left' | 'center' | 'right'; align: '' | 'left' | 'center' | 'right';
children: React.ReactElement | null; children: React.ReactElement | null;
isLastCell: boolean; isLastCell: boolean;
theme: TSupportedThemes;
} }
export const CELL_WIDTH = 100; export const CELL_WIDTH = 100;
const TableCell = React.memo(({ isLastCell, align, children, theme }: ITableCell) => { const TableCell = React.memo(({ isLastCell, align, children }: ITableCell) => {
const cellStyle: ViewStyle[] = [styles.cell, { borderColor: themes[theme].borderColor }]; const { colors } = useTheme();
const cellStyle: ViewStyle[] = [styles.cell, { borderColor: colors.borderColor }];
if (!isLastCell) { if (!isLastCell) {
cellStyle.push(styles.cellRightBorder); cellStyle.push(styles.cellRightBorder);
} }
@ -29,7 +29,7 @@ const TableCell = React.memo(({ isLastCell, align, children, theme }: ITableCell
return ( return (
<View style={[...cellStyle, { width: CELL_WIDTH }]}> <View style={[...cellStyle, { width: CELL_WIDTH }]}>
<Text style={[textStyle, { color: themes[theme].bodyText }]}>{children}</Text> <Text style={[textStyle, { color: colors.bodyText }]}>{children}</Text>
</View> </View>
); );
}); });

View File

@ -1,18 +1,18 @@
import React from 'react'; import React from 'react';
import { View, ViewStyle } from 'react-native'; import { View, ViewStyle } from 'react-native';
import { TSupportedThemes } from '../../theme'; import { useTheme } from '../../theme';
import { themes } from '../../lib/constants';
import styles from './styles'; import styles from './styles';
interface ITableRow { export interface ITableRow {
children: React.ReactElement | null; children: React.ReactElement | null;
isLastRow: boolean; isLastRow: boolean;
theme: TSupportedThemes;
} }
const TableRow = React.memo(({ isLastRow, children: _children, theme }: ITableRow) => { const TableRow = React.memo(({ isLastRow, children: _children }: ITableRow) => {
const rowStyle: ViewStyle[] = [styles.row, { borderColor: themes[theme].borderColor }]; const { colors } = useTheme();
const rowStyle: ViewStyle[] = [styles.row, { borderColor: colors.borderColor }];
if (!isLastRow) { if (!isLastRow) {
rowStyle.push(styles.rowBottomBorder); rowStyle.push(styles.rowBottomBorder);
} }

View File

@ -1,4 +1,4 @@
import React, { PureComponent } from 'react'; import React, { useCallback, useEffect, useReducer, useRef } from 'react';
import { Image, StyleProp, Text, TextStyle } from 'react-native'; import { Image, StyleProp, Text, TextStyle } from 'react-native';
import { Parser } from 'commonmark'; import { Parser } from 'commonmark';
import Renderer from 'commonmark-react-renderer'; import Renderer from 'commonmark-react-renderer';
@ -12,24 +12,23 @@ import MarkdownHashtag from './Hashtag';
import MarkdownBlockQuote from './BlockQuote'; import MarkdownBlockQuote from './BlockQuote';
import MarkdownEmoji from './Emoji'; import MarkdownEmoji from './Emoji';
import MarkdownTable from './Table'; import MarkdownTable from './Table';
import MarkdownTableRow from './TableRow'; import MarkdownTableRow, { ITableRow } from './TableRow';
import MarkdownTableCell from './TableCell'; import MarkdownTableCell, { ITableCell } from './TableCell';
import mergeTextNodes from './mergeTextNodes'; import mergeTextNodes from './mergeTextNodes';
import styles from './styles'; import styles from './styles';
import { isValidURL } from '../../lib/methods/helpers/url'; import { isValidURL } from '../../lib/methods/helpers';
import NewMarkdown from './new'; import NewMarkdown from './new';
import { formatText } from './formatText'; import { formatText } from './formatText';
import { IUserMention, IUserChannel, TOnLinkPress } from './interfaces'; import { IUserMention, IUserChannel, TOnLinkPress } from './interfaces';
import { TGetCustomEmoji } from '../../definitions/IEmoji'; import { TGetCustomEmoji } from '../../definitions';
import { formatHyperlink } from './formatHyperlink'; import { formatHyperlink } from './formatHyperlink';
import { TSupportedThemes } from '../../theme'; import { useTheme } from '../../theme';
import { themes } from '../../lib/constants'; import { IRoomInfoParam } from '../../views/SearchMessagesView';
export { default as MarkdownPreview } from './Preview'; export { default as MarkdownPreview } from './Preview';
interface IMarkdownProps { export interface IMarkdownProps {
msg?: string | null; msg?: string | null;
theme: TSupportedThemes;
md?: MarkdownAST; md?: MarkdownAST;
mentions?: IUserMention[]; mentions?: IUserMention[];
getCustomEmoji?: TGetCustomEmoji; getCustomEmoji?: TGetCustomEmoji;
@ -41,8 +40,7 @@ interface IMarkdownProps {
useRealName?: boolean; useRealName?: boolean;
channels?: IUserChannel[]; channels?: IUserChannel[];
enableMessageParser?: boolean; enableMessageParser?: boolean;
// TODO: Refactor when migrate Room navToRoomInfo?: (params: IRoomInfoParam) => void;
navToRoomInfo?: Function;
testID?: string; testID?: string;
style?: StyleProp<TextStyle>[]; style?: StyleProp<TextStyle>[];
onLinkPress?: TOnLinkPress; onLinkPress?: TOnLinkPress;
@ -86,270 +84,243 @@ const emojiCount = (str: string) => {
}; };
const parser = new Parser(); const parser = new Parser();
export const markdownTestID = 'markdown';
class Markdown extends PureComponent<IMarkdownProps, any> { const Markdown = ({
private renderer: any; msg,
md,
mentions,
getCustomEmoji,
baseUrl,
username,
tmid,
numberOfLines,
customEmojis,
useRealName,
channels,
enableMessageParser,
navToRoomInfo,
style,
onLinkPress
}: IMarkdownProps) => {
const { colors } = useTheme();
const renderer = useRef<any>();
const isMessageContainsOnlyEmoji = useRef(false);
const [, forceUpdate] = useReducer(x => x + 1, 0);
private isMessageContainsOnlyEmoji!: boolean; const isNewMarkdown = useCallback(() => !!enableMessageParser && !!md, [enableMessageParser, md]);
const createRenderer = useCallback(
() =>
new Renderer({
renderers: {
text: renderText,
constructor(props: IMarkdownProps) { emph: Renderer.forwardChildren,
super(props); strong: Renderer.forwardChildren,
if (!this.isNewMarkdown) { del: Renderer.forwardChildren,
this.renderer = this.createRenderer(); code: renderCodeInline,
link: renderLink,
image: renderImage,
atMention: renderAtMention,
emoji: renderEmoji,
hashtag: renderHashtag,
paragraph: renderParagraph,
heading: renderHeading,
codeBlock: renderCodeBlock,
blockQuote: renderBlockQuote,
list: renderList,
item: renderListItem,
hardBreak: renderBreak,
thematicBreak: renderBreak,
softBreak: renderBreak,
htmlBlock: renderText,
htmlInline: renderText,
table: renderTable,
table_row: renderTableRow,
table_cell: renderTableCell
},
renderParagraphsInLists: true
}),
[]
);
useEffect(() => {
if (!isNewMarkdown() && msg) {
renderer.current = createRenderer();
forceUpdate();
} }
}, [createRenderer, isNewMarkdown, msg]);
if (!msg) {
return null;
} }
createRenderer = () => const renderText = ({ context, literal }: { context: []; literal: string }) => {
new Renderer({ const defaultStyle = [isMessageContainsOnlyEmoji.current ? styles.textBig : {}, ...context.map(type => styles[type])];
renderers: {
text: this.renderText,
emph: Renderer.forwardChildren,
strong: Renderer.forwardChildren,
del: Renderer.forwardChildren,
code: this.renderCodeInline,
link: this.renderLink,
image: this.renderImage,
atMention: this.renderAtMention,
emoji: this.renderEmoji,
hashtag: this.renderHashtag,
paragraph: this.renderParagraph,
heading: this.renderHeading,
codeBlock: this.renderCodeBlock,
blockQuote: this.renderBlockQuote,
list: this.renderList,
item: this.renderListItem,
hardBreak: this.renderBreak,
thematicBreak: this.renderBreak,
softBreak: this.renderBreak,
htmlBlock: this.renderText,
htmlInline: this.renderText,
table: this.renderTable,
table_row: this.renderTableRow,
table_cell: this.renderTableCell
},
renderParagraphsInLists: true
});
get isNewMarkdown(): boolean {
const { md, enableMessageParser } = this.props;
return !!enableMessageParser && !!md;
}
renderText = ({ context, literal }: { context: []; literal: string }) => {
const { numberOfLines, style = [] } = this.props;
const defaultStyle = [this.isMessageContainsOnlyEmoji ? styles.textBig : {}, ...context.map(type => styles[type])];
return ( return (
<Text accessibilityLabel={literal} style={[styles.text, defaultStyle, ...style]} numberOfLines={numberOfLines}> <Text accessibilityLabel={literal} style={[styles.text, defaultStyle, ...(style || [])]} numberOfLines={numberOfLines}>
{literal} {literal}
</Text> </Text>
); );
}; };
renderCodeInline = ({ literal }: TLiteral) => { const renderCodeInline = ({ literal }: TLiteral) => (
const { theme, style = [] } = this.props; <Text
return ( testID={`${markdownTestID}-code-in-line`}
<Text style={[
style={[ {
{ ...styles.codeInline,
...styles.codeInline, color: colors.bodyText,
color: themes[theme].bodyText, backgroundColor: colors.bannerBackground,
backgroundColor: themes[theme].bannerBackground, borderColor: colors.bannerBackground
borderColor: themes[theme].bannerBackground },
}, ...(style || [])
...style ]}>
]}> {literal}
{literal} </Text>
</Text> );
);
};
renderCodeBlock = ({ literal }: TLiteral) => { const renderCodeBlock = ({ literal }: TLiteral) => (
const { theme, style = [] } = this.props; <Text
return ( testID={`${markdownTestID}-code-block`}
<Text style={[
style={[ {
{ ...styles.codeBlock,
...styles.codeBlock, color: colors.bodyText,
color: themes[theme].bodyText, backgroundColor: colors.bannerBackground,
backgroundColor: themes[theme].bannerBackground, borderColor: colors.bannerBackground
borderColor: themes[theme].bannerBackground },
}, ...(style || [])
...style ]}>
]}> {literal}
{literal} </Text>
</Text> );
);
};
renderBreak = () => { const renderBreak = () => <Text>{tmid ? ' ' : '\n'}</Text>;
const { tmid } = this.props;
return <Text>{tmid ? ' ' : '\n'}</Text>;
};
renderParagraph = ({ children }: any) => { const renderParagraph = ({ children }: { children: React.ReactElement[] }) => {
const { numberOfLines, style, theme } = this.props;
if (!children || children.length === 0) { if (!children || children.length === 0) {
return null; return null;
} }
return ( return (
<Text style={[styles.text, style, { color: themes[theme].bodyText }]} numberOfLines={numberOfLines}> <Text style={[styles.text, style, { color: colors.bodyText }]} numberOfLines={numberOfLines}>
{children} {children}
</Text> </Text>
); );
}; };
renderLink = ({ children, href }: any) => { const renderLink = ({ children, href }: { children: React.ReactElement | null; href: string }) => (
const { theme, onLinkPress } = this.props; <MarkdownLink link={href} onLinkPress={onLinkPress} testID={markdownTestID}>
return ( {children}
<MarkdownLink link={href} theme={theme} onLinkPress={onLinkPress}> </MarkdownLink>
{children} );
</MarkdownLink>
);
};
renderHashtag = ({ hashtag }: { hashtag: string }) => { const renderHashtag = ({ hashtag }: { hashtag: string }) => (
const { channels, navToRoomInfo, style } = this.props; <MarkdownHashtag hashtag={hashtag} channels={channels} navToRoomInfo={navToRoomInfo} style={style} testID={markdownTestID} />
return <MarkdownHashtag hashtag={hashtag} channels={channels} navToRoomInfo={navToRoomInfo} style={style} />; );
};
renderAtMention = ({ mentionName }: { mentionName: string }) => { const renderAtMention = ({ mentionName }: { mentionName: string }) => (
const { username = '', mentions, navToRoomInfo, useRealName, style } = this.props; <MarkdownAtMention
return ( mentions={mentions}
<MarkdownAtMention mention={mentionName}
mentions={mentions} useRealName={useRealName}
mention={mentionName} username={username}
useRealName={useRealName} navToRoomInfo={navToRoomInfo}
username={username} style={style}
navToRoomInfo={navToRoomInfo} testID={markdownTestID}
style={style} />
/> );
);
};
renderEmoji = ({ literal }: TLiteral) => { const renderEmoji = ({ literal }: TLiteral) => (
const { getCustomEmoji, baseUrl = '', customEmojis, style } = this.props; <MarkdownEmoji
return ( literal={literal}
<MarkdownEmoji isMessageContainsOnlyEmoji={isMessageContainsOnlyEmoji.current}
literal={literal} getCustomEmoji={getCustomEmoji}
isMessageContainsOnlyEmoji={this.isMessageContainsOnlyEmoji} baseUrl={baseUrl || ''}
getCustomEmoji={getCustomEmoji} customEmojis={customEmojis}
baseUrl={baseUrl} style={style}
customEmojis={customEmojis} testID={markdownTestID}
style={style} />
/> );
);
};
renderImage = ({ src }: { src: string }) => { const renderImage = ({ src }: { src: string }) => {
if (!isValidURL(src)) { if (!isValidURL(src)) {
return null; return null;
} }
return <Image style={styles.inlineImage} source={{ uri: encodeURI(src) }} />; return <Image style={styles.inlineImage} source={{ uri: encodeURI(src) }} testID={`${markdownTestID}-image`} />;
}; };
renderHeading = ({ children, level }: any) => { const renderHeading = ({ children, level }: { children: React.ReactElement; level: string }) => {
const { numberOfLines, theme } = this.props;
// @ts-ignore // @ts-ignore
const textStyle = styles[`heading${level}Text`]; const textStyle = styles[`heading${level}Text`];
return ( return (
<Text numberOfLines={numberOfLines} style={[textStyle, { color: themes[theme].bodyText }]}> <Text testID={`${markdownTestID}-header`} numberOfLines={numberOfLines} style={[textStyle, { color: colors.bodyText }]}>
{children} {children}
</Text> </Text>
); );
}; };
renderList = ({ children, start, tight, type }: any) => { const renderList = ({ children, start, tight, type }: any) => (
const { numberOfLines } = this.props; <MarkdownList ordered={type !== 'bullet'} start={start} tight={tight} numberOfLines={numberOfLines}>
return ( {children}
<MarkdownList ordered={type !== 'bullet'} start={start} tight={tight} numberOfLines={numberOfLines}> </MarkdownList>
{children} );
</MarkdownList>
);
};
renderListItem = ({ children, context, ...otherProps }: any) => { const renderListItem = ({ children, context, ...otherProps }: any) => {
const { theme } = this.props;
const level = context.filter((type: string) => type === 'list').length; const level = context.filter((type: string) => type === 'list').length;
return ( return (
<MarkdownListItem level={level} theme={theme} {...otherProps}> <MarkdownListItem level={level} {...otherProps}>
{children} {children}
</MarkdownListItem> </MarkdownListItem>
); );
}; };
renderBlockQuote = ({ children }: { children: JSX.Element }) => { const renderBlockQuote = ({ children }: { children: React.ReactElement }) => (
const { theme } = this.props; <MarkdownBlockQuote>{children}</MarkdownBlockQuote>
return <MarkdownBlockQuote theme={theme}>{children}</MarkdownBlockQuote>; );
};
renderTable = ({ children, numColumns }: { children: JSX.Element; numColumns: number }) => { const renderTable = ({ children, numColumns }: { children: React.ReactElement; numColumns: number }) => (
const { theme } = this.props; <MarkdownTable numColumns={numColumns} testID={markdownTestID}>
{children}
</MarkdownTable>
);
const renderTableRow = ({ children, isLastRow }: ITableRow) => (
<MarkdownTableRow isLastRow={isLastRow}>{children}</MarkdownTableRow>
);
const renderTableCell = ({ align, children, isLastCell }: ITableCell) => (
<MarkdownTableCell align={align} isLastCell={isLastCell}>
{children}
</MarkdownTableCell>
);
if (isNewMarkdown()) {
return ( return (
<MarkdownTable numColumns={numColumns} theme={theme}> <NewMarkdown
{children} username={username || ''}
</MarkdownTable> baseUrl={baseUrl || ''}
getCustomEmoji={getCustomEmoji}
useRealName={useRealName}
tokens={md}
mentions={mentions}
channels={channels}
navToRoomInfo={navToRoomInfo}
onLinkPress={onLinkPress}
/>
); );
};
renderTableRow = (args: any) => {
const { theme } = this.props;
return <MarkdownTableRow {...args} theme={theme} />;
};
renderTableCell = (args: any) => {
const { theme } = this.props;
return <MarkdownTableCell {...args} theme={theme} />;
};
render() {
const {
msg,
md,
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);
m = formatHyperlink(m);
let ast = parser.parse(m);
ast = mergeTextNodes(ast);
this.isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3;
return this.renderer.render(ast);
} }
}
const formattedMessage = formatHyperlink(formatText(msg));
const ast = mergeTextNodes(parser.parse(formattedMessage));
isMessageContainsOnlyEmoji.current = isOnlyEmoji(formattedMessage) && emojiCount(formattedMessage) <= 3;
return renderer?.current?.render(ast) || null;
};
export default Markdown; export default Markdown;

View File

@ -0,0 +1,140 @@
import React from 'react';
import { render } from '@testing-library/react-native';
import { Provider } from 'react-redux';
import { combineReducers, createStore } from 'redux';
import Markdown, { IMarkdownProps, markdownTestID } from './index';
import { IEmoji, TGetCustomEmoji } from '../../definitions';
const getCustomEmoji: TGetCustomEmoji = content =>
({
marioparty: { name: content, extension: 'gif' },
react_rocket: { name: content, extension: 'png' },
nyan_rocket: { name: content, extension: 'png' }
}[content] as IEmoji);
const Render = ({ msg, baseUrl, getCustomEmoji, mentions, username }: IMarkdownProps) => {
const reducers = combineReducers({
app: () => ({ isMasterDetail: false })
});
const store = createStore(reducers);
return (
<Provider store={store}>
<Markdown msg={msg} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} mentions={mentions} username={username} />
</Provider>
);
};
describe('Markdown', () => {
test('should render header', () => {
const header1 = render(<Render msg='# Header 1' />);
const header2 = render(<Render msg='## Header 2' />);
const header3 = render(<Render msg='### Header 3' />);
const header4 = render(<Render msg='#### Header 4' />);
const header5 = render(<Render msg='##### Header 5' />);
const header6 = render(<Render msg='###### Header 6' />);
expect(header1.findByTestId(`${markdownTestID}-header`)).toBeTruthy();
expect(header2.findByTestId(`${markdownTestID}-header`)).toBeTruthy();
expect(header3.findByTestId(`${markdownTestID}-header`)).toBeTruthy();
expect(header4.findByTestId(`${markdownTestID}-header`)).toBeTruthy();
expect(header5.findByTestId(`${markdownTestID}-header`)).toBeTruthy();
expect(header6.findByTestId(`${markdownTestID}-header`)).toBeTruthy();
});
test('should render code in line', async () => {
const { findByTestId } = render(<Render msg='This is `inline code`' />);
const component = await findByTestId(`${markdownTestID}-code-in-line`);
expect(component).toBeTruthy();
});
test('should render code block', async () => {
const { findByTestId } = render(
<Render
msg='
```
Code block
```'
/>
);
const component = await findByTestId(`${markdownTestID}-code-block`);
expect(component).toBeTruthy();
});
test('should render image', async () => {
const { findByTestId } = render(<Render msg='![alt text](https://play.google.com/intl/en_us/badges/images/badge_new.png)' />);
const component = await findByTestId(`${markdownTestID}-image`);
expect(component).toBeTruthy();
});
test('should render link', () => {
const markdownLink = render(<Render msg='[Markdown link](https://rocket.chat): `[description](url)`' />);
const link = render(<Render msg='<https://rocket.chat|Formatted Link>: `<url|description>`' />);
expect(markdownLink.findByTestId(`${markdownTestID}-link`)).toBeTruthy();
expect(link.findByTestId(`${markdownTestID}-link`)).toBeTruthy();
});
test('should render block quote', async () => {
const { findByTestId } = render(
<Render
msg={`> This is block quote
this is a normal line`}
/>
);
const component = await findByTestId(`${markdownTestID}-block-quote`);
expect(component).toBeTruthy();
});
test('should render list', () => {
const markdownList = render(<Render msg={'* Open Source\n* Rocket.Chat\n - nodejs\n - ReactNative'} />);
const markdownNumberList = render(<Render msg={'1. Open Source\n2. Rocket.Chat'} />);
expect(markdownList.findByTestId(`${markdownTestID}-list`)).toBeTruthy();
expect(markdownNumberList.findByTestId(`${markdownTestID}-list`)).toBeTruthy();
});
test('should render emojis', () => {
const markdownCustomEmoji = render(
<Render msg='😃 :+1: :marioparty:' getCustomEmoji={getCustomEmoji} baseUrl='https://open.rocket.chat' />
);
const markdownUnicodeEmoji = render(<Render msg='Unicode: 😃😇👍' />);
expect(markdownCustomEmoji.findByTestId(`${markdownTestID}-custom-emoji`)).toBeTruthy();
expect(markdownUnicodeEmoji.findByTestId(`${markdownTestID}-unicode-emoji`)).toBeTruthy();
});
test('should render hashtags', () => {
const markdownHashtagChannels = render(<Render msg='#test-channel' channels={[{ _id: '123', name: 'test-channel' }]} />);
const markdownHashtagWithoutChannels = render(<Render msg='#unknown' />);
expect(markdownHashtagChannels.findByTestId(`${markdownTestID}-hashtag-channels`)).toBeTruthy();
expect(markdownHashtagWithoutChannels.findByTestId(`${markdownTestID}-hashtag-without-channels`)).toBeTruthy();
});
test('should render mentions', async () => {
const markdownMentionsAllAndHere = render(<Render msg='@all @here' username='rocket.cat' />);
const markdownMentionsUnknown = render(<Render msg='@unknown' username='rocket.cat' />);
const markdownMentionsUsers = render(
<Render
msg='@rocket.cat '
mentions={[{ _id: 'random', name: 'Rocket Cat', username: 'rocket.cat' }]}
username='rocket.cat'
/>
);
expect(await markdownMentionsAllAndHere.findAllByTestId(`${markdownTestID}-mention-all-here`)).toBeTruthy();
expect(await markdownMentionsUnknown.findByTestId(`${markdownTestID}-mention-unknown`)).toBeTruthy();
expect(await markdownMentionsUsers.findByTestId(`${markdownTestID}-mention-users`)).toBeTruthy();
});
test('should render table', async () => {
const { findByTestId } = render(
<Render
msg='First Header | Second Header
------------ | -------------
Content from cell 1 | Content from cell 2
Content in the first column | Content in the second column'
/>
);
const component = await findByTestId(`${markdownTestID}-table`);
expect(component).toBeTruthy();
});
});

View File

@ -3,7 +3,6 @@ import { Text } from 'react-native';
import { Code as CodeProps } from '@rocket.chat/message-parser'; import { Code as CodeProps } from '@rocket.chat/message-parser';
import styles from '../styles'; import styles from '../styles';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
import CodeLine from './CodeLine'; import CodeLine from './CodeLine';
@ -12,16 +11,16 @@ interface ICodeProps {
} }
const Code = ({ value }: ICodeProps) => { const Code = ({ value }: ICodeProps) => {
const { theme } = useTheme(); const { colors } = useTheme();
return ( return (
<Text <Text
style={[ style={[
styles.codeBlock, styles.codeBlock,
{ {
color: themes[theme].bodyText, color: colors.bodyText,
backgroundColor: themes[theme].bannerBackground, backgroundColor: colors.bannerBackground,
borderColor: themes[theme].borderColor borderColor: colors.borderColor
} }
]}> ]}>
{value.map(block => { {value.map(block => {

View File

@ -3,7 +3,6 @@ import { Text } from 'react-native';
import { Emoji as EmojiProps } from '@rocket.chat/message-parser'; import { Emoji as EmojiProps } from '@rocket.chat/message-parser';
import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode'; import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
import styles from '../styles'; import styles from '../styles';
import CustomEmoji from '../../EmojiPicker/CustomEmoji'; import CustomEmoji from '../../EmojiPicker/CustomEmoji';
@ -15,7 +14,7 @@ interface IEmojiProps {
} }
const Emoji = ({ value, isBigEmoji }: IEmojiProps) => { const Emoji = ({ value, isBigEmoji }: IEmojiProps) => {
const { theme } = useTheme(); const { colors } = useTheme();
const { baseUrl, getCustomEmoji } = useContext(MarkdownContext); const { baseUrl, getCustomEmoji } = useContext(MarkdownContext);
const emojiUnicode = shortnameToUnicode(`:${value.value}:`); const emojiUnicode = shortnameToUnicode(`:${value.value}:`);
const emoji = getCustomEmoji?.(value.value); const emoji = getCustomEmoji?.(value.value);
@ -23,7 +22,7 @@ const Emoji = ({ value, isBigEmoji }: IEmojiProps) => {
if (emoji) { if (emoji) {
return <CustomEmoji baseUrl={baseUrl} style={[isBigEmoji ? styles.customEmojiBig : styles.customEmoji]} emoji={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>; return <Text style={[{ color: colors.bodyText }, isBigEmoji ? styles.textBig : styles.text]}>{emojiUnicode}</Text>;
}; };
export default Emoji; export default Emoji;

View File

@ -2,7 +2,6 @@ import React from 'react';
import { Text } from 'react-native'; import { Text } from 'react-native';
import { Heading as HeadingProps } from '@rocket.chat/message-parser'; import { Heading as HeadingProps } from '@rocket.chat/message-parser';
import { themes } from '../../../lib/constants';
import styles from '../styles'; import styles from '../styles';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
@ -12,11 +11,11 @@ interface IHeadingProps {
} }
const Heading = ({ value, level }: IHeadingProps) => { const Heading = ({ value, level }: IHeadingProps) => {
const { theme } = useTheme(); const { colors } = useTheme();
const textStyle = styles[`heading${level}`]; const textStyle = styles[`heading${level}`];
return ( return (
<Text style={[textStyle, { color: themes[theme].bodyText }]}> <Text style={[textStyle, { color: colors.bodyText }]}>
{value.map(block => { {value.map(block => {
switch (block.type) { switch (block.type) {
case 'PLAIN_TEXT': case 'PLAIN_TEXT':

View File

@ -4,38 +4,30 @@ import { createImageProgress } from 'react-native-image-progress';
import * as Progress from 'react-native-progress'; import * as Progress from 'react-native-progress';
import FastImage from 'react-native-fast-image'; import FastImage from 'react-native-fast-image';
import { TSupportedThemes, useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
import { themes } from '../../../lib/constants';
import styles from '../../message/styles'; import styles from '../../message/styles';
interface IImageProps {
value: ImageProps['value'];
}
type TMessageImage = {
img: string;
theme: TSupportedThemes;
};
const ImageProgress = createImageProgress(FastImage); const ImageProgress = createImageProgress(FastImage);
const MessageImage = ({ img, theme }: TMessageImage) => ( const MessageImage = ({ img }: { img: string }) => {
<ImageProgress const { colors } = useTheme();
style={[styles.inlineImage, { borderColor: themes[theme].borderColor }]} return (
source={{ uri: encodeURI(img) }} <ImageProgress
resizeMode={FastImage.resizeMode.cover} style={[styles.inlineImage, { borderColor: colors.borderColor }]}
indicator={Progress.Pie} source={{ uri: encodeURI(img) }}
indicatorProps={{ resizeMode={FastImage.resizeMode.cover}
color: themes[theme].actionTintColor indicator={Progress.Pie}
}} indicatorProps={{
/> color: colors.actionTintColor
); }}
/>
);
};
const Image = ({ value }: IImageProps) => { const Image = ({ value }: { value: ImageProps['value'] }) => {
const { theme } = useTheme();
const { src } = value; const { src } = value;
return <MessageImage img={src.value} theme={theme} />; return <MessageImage img={src.value} />;
}; };
export default Image; export default Image;

View File

@ -45,12 +45,15 @@ const Inline = ({ value }: IParagraphProps) => {
username={username} username={username}
navToRoomInfo={navToRoomInfo} navToRoomInfo={navToRoomInfo}
mentions={mentions} mentions={mentions}
testID='new-markdown'
/> />
); );
case 'EMOJI': case 'EMOJI':
return <Emoji value={block.value} />; return <Emoji value={block.value} />;
case 'MENTION_CHANNEL': case 'MENTION_CHANNEL':
return <Hashtag hashtag={block.value.value} navToRoomInfo={navToRoomInfo} channels={channels} />; return (
<Hashtag hashtag={block.value.value} navToRoomInfo={navToRoomInfo} channels={channels} testID='new-markdown' />
);
case 'INLINE_CODE': case 'INLINE_CODE':
return <InlineCode value={block.value} />; return <InlineCode value={block.value} />;
default: default:

View File

@ -3,7 +3,6 @@ import { Text } from 'react-native';
import { InlineCode as InlineCodeProps } from '@rocket.chat/message-parser'; import { InlineCode as InlineCodeProps } from '@rocket.chat/message-parser';
import styles from '../styles'; import styles from '../styles';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
interface IInlineCodeProps { interface IInlineCodeProps {
@ -11,16 +10,16 @@ interface IInlineCodeProps {
} }
const InlineCode = ({ value }: IInlineCodeProps) => { const InlineCode = ({ value }: IInlineCodeProps) => {
const { theme } = useTheme(); const { colors } = useTheme();
return ( return (
<Text <Text
style={[ style={[
styles.codeInline, styles.codeInline,
{ {
color: themes[theme].bodyText, color: colors.bodyText,
backgroundColor: themes[theme].bannerBackground, backgroundColor: colors.bannerBackground,
borderColor: themes[theme].borderColor borderColor: colors.borderColor
} }
]}> ]}>
{(block => { {(block => {

View File

@ -9,7 +9,6 @@ import { LISTENER } from '../../Toast';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
import openLink from '../../../lib/methods/helpers/openLink'; import openLink from '../../../lib/methods/helpers/openLink';
import EventEmitter from '../../../lib/methods/helpers/events'; import EventEmitter from '../../../lib/methods/helpers/events';
import { themes } from '../../../lib/constants';
import Strike from './Strike'; import Strike from './Strike';
import Italic from './Italic'; import Italic from './Italic';
import Bold from './Bold'; import Bold from './Bold';
@ -20,7 +19,7 @@ interface ILinkProps {
} }
const Link = ({ value }: ILinkProps) => { const Link = ({ value }: ILinkProps) => {
const { theme } = useTheme(); const { theme, colors } = useTheme();
const { onLinkPress } = useContext(MarkdownContext); const { onLinkPress } = useContext(MarkdownContext);
const { src, label } = value; const { src, label } = value;
const handlePress = () => { const handlePress = () => {
@ -39,7 +38,7 @@ const Link = ({ value }: ILinkProps) => {
}; };
return ( return (
<Text onPress={handlePress} onLongPress={onLongPress} style={[styles.link, { color: themes[theme].actionTintColor }]}> <Text onPress={handlePress} onLongPress={onLongPress} style={[styles.link, { color: colors.actionTintColor }]}>
{(block => { {(block => {
switch (block.type) { switch (block.type) {
case 'PLAIN_TEXT': case 'PLAIN_TEXT':

View File

@ -4,7 +4,6 @@ import { OrderedList as OrderedListProps } from '@rocket.chat/message-parser';
import Inline from './Inline'; import Inline from './Inline';
import styles from '../styles'; import styles from '../styles';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
interface IOrderedListProps { interface IOrderedListProps {
@ -12,12 +11,12 @@ interface IOrderedListProps {
} }
const OrderedList = ({ value }: IOrderedListProps) => { const OrderedList = ({ value }: IOrderedListProps) => {
const { theme } = useTheme(); const { colors } = useTheme();
return ( return (
<View> <View>
{value.map((item, index) => ( {value.map((item, index) => (
<View style={styles.row}> <View style={styles.row}>
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{index + 1}. </Text> <Text style={[styles.text, { color: colors.bodyText }]}>{index + 1}. </Text>
<Inline value={item.value} /> <Inline value={item.value} />
</View> </View>
))} ))}

View File

@ -5,16 +5,15 @@ import { Paragraph as ParagraphProps } from '@rocket.chat/message-parser';
import Inline from './Inline'; import Inline from './Inline';
import styles from '../styles'; import styles from '../styles';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
import { themes } from '../../../lib/constants';
interface IParagraphProps { interface IParagraphProps {
value: ParagraphProps['value']; value: ParagraphProps['value'];
} }
const Paragraph = ({ value }: IParagraphProps) => { const Paragraph = ({ value }: IParagraphProps) => {
const { theme } = useTheme(); const { colors } = useTheme();
return ( return (
<Text style={[styles.text, { color: themes[theme].bodyText }]}> <Text style={[styles.text, { color: colors.bodyText }]}>
<Inline value={value} /> <Inline value={value} />
</Text> </Text>
); );

View File

@ -4,16 +4,15 @@ import { Plain as PlainProps } from '@rocket.chat/message-parser';
import styles from '../styles'; import styles from '../styles';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
import { themes } from '../../../lib/constants';
interface IPlainProps { interface IPlainProps {
value: PlainProps['value']; value: PlainProps['value'];
} }
const Plain = ({ value }: IPlainProps) => { const Plain = ({ value }: IPlainProps) => {
const { theme } = useTheme(); const { colors } = useTheme();
return ( return (
<Text accessibilityLabel={value} style={[styles.plainText, { color: themes[theme].bodyText }]}> <Text accessibilityLabel={value} style={[styles.plainText, { color: colors.bodyText }]}>
{value} {value}
</Text> </Text>
); );

View File

@ -2,7 +2,6 @@ import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { Quote as QuoteProps } from '@rocket.chat/message-parser'; import { Quote as QuoteProps } from '@rocket.chat/message-parser';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
import styles from '../styles'; import styles from '../styles';
import Paragraph from './Paragraph'; import Paragraph from './Paragraph';
@ -12,10 +11,10 @@ interface IQuoteProps {
} }
const Quote = ({ value }: IQuoteProps) => { const Quote = ({ value }: IQuoteProps) => {
const { theme } = useTheme(); const { colors } = useTheme();
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={[styles.quote, { backgroundColor: themes[theme].borderColor }]} /> <View style={[styles.quote, { backgroundColor: colors.borderColor }]} />
<View style={styles.childContainer}> <View style={styles.childContainer}>
{value.map(item => ( {value.map(item => (
<Paragraph value={item.value} /> <Paragraph value={item.value} />

View File

@ -4,7 +4,6 @@ import { Tasks as TasksProps } from '@rocket.chat/message-parser';
import Inline from './Inline'; import Inline from './Inline';
import styles from '../styles'; import styles from '../styles';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
interface ITasksProps { interface ITasksProps {
@ -12,12 +11,12 @@ interface ITasksProps {
} }
const TaskList = ({ value = [] }: ITasksProps) => { const TaskList = ({ value = [] }: ITasksProps) => {
const { theme } = useTheme(); const { colors } = useTheme();
return ( return (
<View> <View>
{value.map(item => ( {value.map(item => (
<View style={styles.row}> <View style={styles.row}>
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{item.status ? '- [x] ' : '- [ ] '}</Text> <Text style={[styles.text, { color: colors.bodyText }]}>{item.status ? '- [x] ' : '- [ ] '}</Text>
<Inline value={item.value} /> <Inline value={item.value} />
</View> </View>
))} ))}

View File

@ -4,7 +4,6 @@ import { View, Text } from 'react-native';
import Inline from './Inline'; import Inline from './Inline';
import styles from '../styles'; import styles from '../styles';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
interface IUnorderedListProps { interface IUnorderedListProps {
@ -12,12 +11,12 @@ interface IUnorderedListProps {
} }
const UnorderedList = ({ value }: IUnorderedListProps) => { const UnorderedList = ({ value }: IUnorderedListProps) => {
const { theme } = useTheme(); const { colors } = useTheme();
return ( return (
<View> <View>
{value.map(item => ( {value.map(item => (
<View style={styles.row}> <View style={styles.row}>
<Text style={[styles.text, { color: themes[theme].bodyText }]}>- </Text> <Text style={[styles.text, { color: colors.bodyText }]}>- </Text>
<Inline value={item.value} /> <Inline value={item.value} />
</View> </View>
))} ))}

View File

@ -280,7 +280,6 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
baseUrl={baseUrl} baseUrl={baseUrl}
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
theme={theme}
/> />
<View <View
style={[ style={[

View File

@ -100,7 +100,6 @@ const Fields = React.memo(
baseUrl={baseUrl} baseUrl={baseUrl}
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
theme={theme}
style={[styles.markdownFontSize]} style={[styles.markdownFontSize]}
/> />
</View> </View>

View File

@ -63,7 +63,6 @@ const Content = React.memo(
navToRoomInfo={props.navToRoomInfo} navToRoomInfo={props.navToRoomInfo}
tmid={props.tmid} tmid={props.tmid}
useRealName={props.useRealName} useRealName={props.useRealName}
theme={theme}
onLinkPress={onLinkPress} onLinkPress={onLinkPress}
/> />
); );

View File

@ -83,7 +83,6 @@ const ImageContainer = React.memo(
baseUrl={baseUrl} baseUrl={baseUrl}
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
theme={theme}
/> />
<MessageImage imgUri={img} theme={theme} /> <MessageImage imgUri={img} theme={theme} />
</View> </View>

View File

@ -131,7 +131,6 @@ const Description = React.memo(
baseUrl={baseUrl} baseUrl={baseUrl}
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
theme={theme}
/> />
); );
}, },
@ -184,13 +183,7 @@ const Fields = React.memo(
{attachment.fields.map(field => ( {attachment.fields.map(field => (
<View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}> <View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}>
<Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text> <Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text>
<Markdown <Markdown msg={field?.value || ''} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} />
msg={field?.value || ''}
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
</View> </View>
))} ))}
</View> </View>
@ -271,13 +264,7 @@ const Reply = React.memo(
) : null} ) : null}
</View> </View>
</Touchable> </Touchable>
<Markdown <Markdown msg={attachment.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} />
msg={attachment.description}
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
</> </>
); );
}, },

View File

@ -83,7 +83,6 @@ const Video = React.memo(
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
style={[isReply && style]} style={[isReply && style]}
theme={theme}
/> />
<Touchable <Touchable
disabled={isReply} disabled={isReply}

View File

@ -18,6 +18,7 @@ export interface ICustomEmoji {
baseUrl?: string; baseUrl?: string;
emoji: IEmoji; emoji: IEmoji;
style: StyleProp<ImageStyle>; style: StyleProp<ImageStyle>;
testID?: string;
} }
export interface ICustomEmojiModel { export interface ICustomEmojiModel {

View File

@ -84,7 +84,7 @@ const Item = ({ label, content, theme, testID }: IItem) =>
<Text accessibilityLabel={label} style={[styles.itemLabel, { color: themes[theme].titleText }]}> <Text accessibilityLabel={label} style={[styles.itemLabel, { color: themes[theme].titleText }]}>
{label} {label}
</Text> </Text>
<Markdown style={[styles.itemContent, { color: themes[theme].auxiliaryText }]} msg={content} theme={theme} /> <Markdown style={[styles.itemContent, { color: themes[theme].auxiliaryText }]} msg={content} />
</View> </View>
) : null; ) : null;

View File

@ -40,10 +40,10 @@ class E2EHowItWorksView extends React.Component<TE2EHowItWorksViewProps, any> {
return ( return (
<SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} testID='e2e-how-it-works-view'> <SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} testID='e2e-how-it-works-view'>
<Markdown msg={I18n.t('E2E_How_It_Works_info1')} style={infoStyle} theme={theme} /> <Markdown msg={I18n.t('E2E_How_It_Works_info1')} style={infoStyle} />
<Markdown msg={I18n.t('E2E_How_It_Works_info2')} style={infoStyle} theme={theme} /> <Markdown msg={I18n.t('E2E_How_It_Works_info2')} style={infoStyle} />
<Markdown msg={I18n.t('E2E_How_It_Works_info3')} style={infoStyle} theme={theme} /> <Markdown msg={I18n.t('E2E_How_It_Works_info3')} style={infoStyle} />
<Markdown msg={I18n.t('E2E_How_It_Works_info4')} style={infoStyle} theme={theme} /> <Markdown msg={I18n.t('E2E_How_It_Works_info4')} style={infoStyle} />
</SafeAreaView> </SafeAreaView>
); );
} }

View File

@ -23,7 +23,7 @@ const InviteUsersView = ({ route, navigation }: IInviteUsersViewProps): React.Re
const rid = route.params?.rid; const rid = route.params?.rid;
const timeDateFormat = useSelector((state: IApplicationState) => state.settings.Message_TimeAndDateFormat as string); const timeDateFormat = useSelector((state: IApplicationState) => state.settings.Message_TimeAndDateFormat as string);
const invite = useSelector((state: IApplicationState) => state.inviteLinks.invite); const invite = useSelector((state: IApplicationState) => state.inviteLinks.invite);
const { colors, theme } = useTheme(); const { colors } = useTheme();
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
@ -81,7 +81,7 @@ const InviteUsersView = ({ route, navigation }: IInviteUsersViewProps): React.Re
const renderExpiration = () => { const renderExpiration = () => {
const expirationMessage = linkExpirationText(); const expirationMessage = linkExpirationText();
return <Markdown msg={expirationMessage} theme={theme} />; return <Markdown msg={expirationMessage} />;
}; };
return ( return (

View File

@ -24,7 +24,7 @@ const Item = ({ label, content, testID }: IItem) => {
<Text accessibilityLabel={label} style={[styles.itemLabel, { color: themes[theme].titleText }]}> <Text accessibilityLabel={label} style={[styles.itemLabel, { color: themes[theme].titleText }]}>
{label} {label}
</Text> </Text>
<Markdown style={[styles.itemContent, { color: themes[theme].auxiliaryText }]} msg={content} theme={theme} /> <Markdown style={[styles.itemContent, { color: themes[theme].auxiliaryText }]} msg={content} />
</View> </View>
); );
}; };

View File

@ -45,7 +45,7 @@ const Banner = React.memo(
<View style={[styles.modalView, { backgroundColor: themes[theme].bannerBackground }]}> <View style={[styles.modalView, { backgroundColor: themes[theme].bannerBackground }]}>
<Text style={[styles.bannerModalTitle, { color: themes[theme].auxiliaryText }]}>{title}</Text> <Text style={[styles.bannerModalTitle, { color: themes[theme].auxiliaryText }]}>{title}</Text>
<ScrollView style={styles.modalScrollView}> <ScrollView style={styles.modalScrollView}>
<Markdown msg={text} theme={theme} /> <Markdown msg={text} />
</ScrollView> </ScrollView>
</View> </View>
</Modal> </Modal>

View File

@ -6,7 +6,7 @@ import { storiesOf } from '@storybook/react-native';
import { longText } from '../../../../storybook/utils'; import { longText } from '../../../../storybook/utils';
import { ThemeContext } from '../../../theme'; import { ThemeContext } from '../../../theme';
import { Message, MessageDecorator, StoryProvider } from '../../../../storybook/stories/Message'; import { Message, MessageDecorator, StoryProvider } from '../../../../storybook/stories/Message';
import { MessageTypeLoad, themes } from '../../../lib/constants'; import { colors, MessageTypeLoad, themes } from '../../../lib/constants';
import LoadMore from './index'; import LoadMore from './index';
const stories = storiesOf('LoadMore', module); const stories = storiesOf('LoadMore', module);
@ -24,7 +24,7 @@ stories.add('basic', () => (
)); ));
const ThemeStory = ({ theme }) => ( const ThemeStory = ({ theme }) => (
<ThemeContext.Provider value={{ theme }}> <ThemeContext.Provider value={{ theme, colors: colors[theme] }}>
<ScrollView style={{ backgroundColor: themes[theme].backgroundColor }}> <ScrollView style={{ backgroundColor: themes[theme].backgroundColor }}>
<LoadMore load={load} type={MessageTypeLoad.PREVIOUS_CHUNK} /> <LoadMore load={load} type={MessageTypeLoad.PREVIOUS_CHUNK} />
<Message msg='Hey!' theme={theme} /> <Message msg='Hey!' theme={theme} />

View File

@ -334,7 +334,7 @@ class SearchMessagesView extends React.Component<ISearchMessagesViewProps, ISear
placeholder={I18n.t('Search_Messages')} placeholder={I18n.t('Search_Messages')}
testID='search-message-view-input' testID='search-message-view-input'
/> />
<Markdown msg={I18n.t('You_can_search_using_RegExp_eg')} theme={theme} /> <Markdown msg={I18n.t('You_can_search_using_RegExp_eg')} />
<View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} /> <View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} />
</View> </View>
{this.renderList()} {this.renderList()}

View File

@ -48,11 +48,11 @@ const stories = storiesOf('Markdown', module).addDecorator(story => <Provider st
stories.add('Text', () => ( stories.add('Text', () => (
<View style={styles.container}> <View style={styles.container}>
<Markdown msg='This is Rocket.Chat' theme={theme} /> <Markdown msg='This is Rocket.Chat' />
<Markdown msg={longText} theme={theme} /> <Markdown msg={longText} />
<Markdown msg={lineBreakText} theme={theme} /> <Markdown msg={lineBreakText} />
<Markdown msg={sequentialEmptySpacesText} theme={theme} /> <Markdown msg={sequentialEmptySpacesText} />
<Markdown msg='Strong emphasis, aka bold, with **asterisks** or __underscores__' theme={theme} /> <Markdown msg='Strong emphasis, aka bold, with **asterisks** or __underscores__' />
</View> </View>
)); ));
@ -71,7 +71,6 @@ stories.add('Mentions', () => (
<ScrollView style={styles.container}> <ScrollView style={styles.container}>
<Markdown <Markdown
msg='@rocket.cat @name1 @all @here @unknown' msg='@rocket.cat @name1 @all @here @unknown'
theme={theme}
mentions={[ mentions={[
{ _id: 'random', name: 'Rocket Cat', username: 'rocket.cat' }, { _id: 'random', name: 'Rocket Cat', username: 'rocket.cat' },
{ _id: 'random2', name: 'Name', username: 'name1' }, { _id: 'random2', name: 'Name', username: 'name1' },
@ -82,7 +81,6 @@ stories.add('Mentions', () => (
/> />
<Markdown <Markdown
msg='@rocket.cat @name1 @all @here @unknown' msg='@rocket.cat @name1 @all @here @unknown'
theme={theme}
mentions={[ mentions={[
{ _id: 'random', name: 'Rocket Cat', username: 'rocket.cat' }, { _id: 'random', name: 'Rocket Cat', username: 'rocket.cat' },
{ _id: 'random2', name: 'Name', username: 'name1' }, { _id: 'random2', name: 'Name', username: 'name1' },
@ -97,21 +95,16 @@ stories.add('Mentions', () => (
stories.add('Hashtag', () => ( stories.add('Hashtag', () => (
<View style={styles.container}> <View style={styles.container}>
<Markdown msg='#test-channel #unknown' theme={theme} channels={[{ _id: '123', name: 'test-channel' }]} /> <Markdown msg='#test-channel #unknown' channels={[{ _id: '123', name: 'test-channel' }]} />
</View> </View>
)); ));
stories.add('Emoji', () => ( stories.add('Emoji', () => (
<View style={styles.container}> <View style={styles.container}>
<Markdown msg='Unicode: 😃😇👍' theme={theme} /> <Markdown msg='Unicode: 😃😇👍' />
<Markdown msg='Shortnames: :joy::+1:' theme={theme} /> <Markdown msg='Shortnames: :joy::+1:' />
<Markdown <Markdown msg='Custom emojis: :react_rocket: :nyan_rocket: :marioparty:' getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} />
msg='Custom emojis: :react_rocket: :nyan_rocket: :marioparty:' <Markdown msg='😃 :+1: :marioparty:' getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} />
theme={theme}
getCustomEmoji={getCustomEmoji}
baseUrl={baseUrl}
/>
<Markdown msg='😃 :+1: :marioparty:' theme={theme} getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} />
</View> </View>
)); ));
@ -120,52 +113,50 @@ stories.add('Block quote', () => (
<Markdown <Markdown
msg={`> This is block quote msg={`> This is block quote
this is a normal line`} this is a normal line`}
theme={theme}
/> />
</View> </View>
)); ));
stories.add('Links', () => ( stories.add('Links', () => (
<View style={styles.container}> <View style={styles.container}>
<Markdown msg='[Markdown link](https://rocket.chat): `[description](url)`' theme={theme} /> <Markdown msg='[Markdown link](https://rocket.chat): `[description](url)`' />
<Markdown msg='<https://rocket.chat|Formatted Link>: `<url|description>`' theme={theme} /> <Markdown msg='<https://rocket.chat|Formatted Link>: `<url|description>`' />
</View> </View>
)); ));
stories.add('Image', () => ( stories.add('Image', () => (
<View style={styles.container}> <View style={styles.container}>
<Markdown msg='![alt text](https://play.google.com/intl/en_us/badges/images/badge_new.png)' theme={theme} /> <Markdown msg='![alt text](https://play.google.com/intl/en_us/badges/images/badge_new.png)' />
</View> </View>
)); ));
stories.add('Headers', () => ( stories.add('Headers', () => (
<View style={styles.container}> <View style={styles.container}>
<Markdown msg='# Header 1' theme={theme} /> <Markdown msg='# Header 1' />
<Markdown msg='## Header 2' theme={theme} /> <Markdown msg='## Header 2' />
<Markdown msg='### Header 3' theme={theme} /> <Markdown msg='### Header 3' />
<Markdown msg='#### Header 4' theme={theme} /> <Markdown msg='#### Header 4' />
<Markdown msg='##### Header 5' theme={theme} /> <Markdown msg='##### Header 5' />
<Markdown msg='###### Header 6' theme={theme} /> <Markdown msg='###### Header 6' />
</View> </View>
)); ));
stories.add('Code', () => ( stories.add('Code', () => (
<View style={styles.container}> <View style={styles.container}>
<Markdown msg='This is `inline code`' theme={theme} /> <Markdown msg='This is `inline code`' />
<Markdown <Markdown
msg='Inline `code` has `back-ticks around` it. msg='Inline `code` has `back-ticks around` it.
``` ```
Code block Code block
```' ```'
theme={theme}
/> />
</View> </View>
)); ));
stories.add('Lists', () => ( stories.add('Lists', () => (
<View style={styles.container}> <View style={styles.container}>
<Markdown msg={'* Open Source\n* Rocket.Chat\n - nodejs\n - ReactNative'} theme={theme} /> <Markdown msg={'* Open Source\n* Rocket.Chat\n - nodejs\n - ReactNative'} />
<Markdown msg={'1. Open Source\n2. Rocket.Chat'} theme={theme} /> <Markdown msg={'1. Open Source\n2. Rocket.Chat'} />
</View> </View>
)); ));
@ -176,7 +167,6 @@ stories.add('Table', () => (
------------ | ------------- ------------ | -------------
Content from cell 1 | Content from cell 2 Content from cell 1 | Content from cell 2
Content in the first column | Content in the second column' Content in the first column | Content in the second column'
theme={theme}
/> />
</View> </View>
)); ));

View File

@ -18,7 +18,7 @@ exports[`Storyshots Avatar Custom style 1`] = `"{\\"type\\":\\"View\\",\\"props\
exports[`Storyshots Avatar Direct 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4},null],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/diego.mello?format=png&size=112\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]}"`; exports[`Storyshots Avatar Direct 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4},null],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/diego.mello?format=png&size=112\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]}"`;
exports[`Storyshots Avatar Emoji 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4},null],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},[{\\"width\\":30,\\"height\\":30},{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4}]]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/emoji-custom/troll.jpg\\",\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"contain\\"},\\"children\\":null}]}]}"`; exports[`Storyshots Avatar Emoji 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4},null],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},[{\\"width\\":30,\\"height\\":30},{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4}]]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"testID\\":\\"avatar-custom-emoji\\",\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/emoji-custom/troll.jpg\\",\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"contain\\"},\\"children\\":null}]}]}"`;
exports[`Storyshots Avatar Static 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4},null],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://user-images.githubusercontent.com/29778115/89444446-14738480-d728-11ea-9412-75fd978d95fb.jpg\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]}"`; exports[`Storyshots Avatar Static 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4},null],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":56,\\"height\\":56,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://user-images.githubusercontent.com/29778115/89444446-14738480-d728-11ea-9412-75fd978d95fb.jpg\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]}"`;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long