Compare commits

...

30 Commits

Author SHA1 Message Date
Gerzon Z 9ddbef603c Merge branch 'develop' into update.message-parser 2021-10-01 11:10:30 -04:00
Gerzon Z 4418a5eacf
Merge branch 'develop' into new.support-new-message-parser 2021-09-28 12:08:30 -04:00
Gerzon Z 417ea353a9 Fix new markdown and styles 2021-09-27 12:41:17 -04:00
Gerzon Z 47eeadc07f
Merge branch 'develop' into new.support-new-message-parser 2021-09-23 16:03:22 -04:00
Gerzon Z 4895fd2e2e Change interfaces and add TaskListComponent and styles 2021-09-23 15:23:20 -04:00
Gerzon Z ddfdba438b Merge branch 'develop' into new.support-new-message-parser 2021-09-23 10:12:43 -04:00
Gerzon Z e27ffd7487 Merge branch 'develop' into new.support-new-message-parser 2021-09-22 19:11:40 -04:00
Gerzon Z 5edbfe65b6 Migrate components to TypeScript and fix styling 2021-09-22 19:09:26 -04:00
Gerzon Z 20d2db9aa3 Renamed folder, add @rocket.chat/message-parser, migrate some files to TypeScript 2021-09-21 18:42:01 -04:00
Gerzon Z 7a88d8df62 Merge branch 'develop' into new.support-new-message-parser 2021-09-21 16:50:46 -04:00
Diego Mello 3c7ad9a427
Merge branch 'develop' into new.support-new-message-parser 2021-09-06 14:47:44 -03:00
Gerzon Z 73f4e815c0 Add server message parser validation 2021-08-22 21:30:30 -04:00
Gerzon Z 81c0d322c3
Merge branch 'develop' into new.support-new-message-parser 2021-08-21 03:02:21 -04:00
Gerzon Z 06b3da0b7a Minor tweaks 2021-08-21 03:01:14 -04:00
Gerzon Z d4c3322c32 Update Mention component 2021-08-21 02:02:25 -04:00
Gerzon Z d7c0b4c76f Merge branch 'new.support-new-message-parser' of https://github.com/RocketChat/Rocket.Chat.ReactNative into new.support-new-message-parser 2021-08-21 02:00:03 -04:00
Gerzon Z 8f26301428 Update Code component and add it to storybooks 2021-08-21 01:59:45 -04:00
Gerzon Z 68e2fb179d
Merge branch 'develop' into new.support-new-message-parser 2021-08-20 12:26:53 -04:00
Gerzon Z c2dd2dbc48 update components and add storybooks 2021-08-20 12:25:30 -04:00
Gerzon Z 71aed0cde8 Updated components and added more Code components 2021-08-13 21:18:22 -04:00
Gerzon Z 1e7bb87c9d Fix BigEmoji 2021-08-12 17:12:04 -04:00
Gerzon Z ff9b0fec9c Add new parser components 2021-08-12 17:05:18 -04:00
Gerzon Z 559f804073 Remove NewMarkdown component and add specific components for new message parser 2021-08-03 14:13:07 -04:00
Gerzon Z 332a4ebf71 Add NewMarkdown component 2021-08-03 03:59:59 -04:00
Gerzon Z 56ee8d45cd Remove admin enableMessageParserEarlyAdoption 2021-08-03 03:01:53 -04:00
Gerzon Z be431179e7 Fix message parser 2021-08-02 16:37:50 -04:00
Gerzon Z c95c2089fb Remove message-parser lib and add enable message parser field to User model 2021-07-30 14:24:05 -04:00
Gerzon Z f779a0ac31 Remove changes to Xcode project 2021-07-29 16:52:25 -04:00
Gerzon Z 7ec40a1110 Add md to db 2021-07-29 15:55:53 -04:00
Gerzon Z 1cd297c9ad Add message parser to profile view and db 2021-07-29 15:28:42 -04:00
40 changed files with 1427 additions and 42 deletions

View File

@ -203,6 +203,9 @@ export default {
Jitsi_Enable_Channels: {
type: 'valuesAsBoolean'
},
Accounts_Default_User_Preferences_enableMessageParserEarlyAdoption: {
type: 'valueAsBoolean'
},
Canned_Responses_Enable: {
type: 'valueAsBoolean'
}

View File

@ -1,21 +1,23 @@
import React from 'react';
import { Text } from 'react-native';
import { StyleProp, 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: StyleProp<TextStyle>;
channels: {
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 navParam = {

View File

@ -3,6 +3,8 @@ import { Image, Text } from 'react-native';
import { Node, Parser } from 'commonmark';
import Renderer from 'commonmark-react-renderer';
import removeMarkdown from 'remove-markdown';
import { connect } from 'react-redux';
import { MarkdownAST } from '@rocket.chat/message-parser';
import shortnameToUnicode from '../../utils/shortnameToUnicode';
import I18n from '../../i18n';
@ -20,9 +22,21 @@ import MarkdownTableCell from './TableCell';
import mergeTextNodes from './mergeTextNodes';
import styles from './styles';
import { isValidURL } from '../../utils/url';
import { getUserSelector } from '../../selectors/login';
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 +49,9 @@ interface IMarkdownProps {
name: string;
_id: number;
}[];
mentions: object[];
user: {
enableMessageParserEarlyAdoption: boolean;
};
navToRoomInfo: Function;
preview: boolean;
theme: string;
@ -227,8 +243,8 @@ 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 }) => {
@ -329,12 +345,28 @@ 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,
user,
mentions,
channels,
navToRoomInfo
} = this.props;
if (!msg) {
return null;
}
if (user.enableMessageParserEarlyAdoption && md) {
return <NewMarkdown tokens={md} style={style} mentions={mentions} channels={channels} navToRoomInfo={navToRoomInfo} />;
}
let m = formatText(msg);
// Ex: '[ ](https://open.rocket.chat/group/test?msg=abcdef) Test'
@ -366,4 +398,8 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
}
}
export default Markdown;
const mapStateToProps = (state: any) => ({
user: getUserSelector(state)
});
export default connect(mapStateToProps)(Markdown);

View File

@ -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: React.FC<IBigEmojiProps> = ({ value }) => (
<View style={styles.container}>
{value.map(block => (
<Emoji value={block.value} isBigEmoji />
))}
</View>
);
export default BigEmoji;

View File

@ -0,0 +1,37 @@
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';
interface IBoldProps {
value: BoldProps['value'];
}
const styles = StyleSheet.create({
text: {
...sharedStyles.textBold
}
});
const Bold: React.FC<IBoldProps> = ({ value }) => (
<Text style={styles.text}>
{value.map(block => {
switch (block.type) {
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;

View File

@ -0,0 +1,41 @@
import React from 'react';
import { StyleProp, Text, TextStyle } 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'];
style: StyleProp<TextStyle>[];
}
const Code: React.FC<ICodeProps> = ({ value, style }) => {
const { theme } = useTheme();
return (
<Text
style={[
{
...styles.codeBlock,
color: themes[theme].bodyText,
backgroundColor: themes[theme].bannerBackground,
borderColor: themes[theme].borderColor
},
...style
]}>
{value.map(block => {
switch (block.type) {
case 'CODE_LINE':
return <CodeLine value={block.value} />;
default:
return null;
}
})}
</Text>
);
};
export default Code;

View File

@ -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: React.FC<ICodeLineProps> = ({ value }) => {
if (value.type !== 'PLAIN_TEXT') {
return null;
}
return <Text>{value.value}</Text>;
};
export default CodeLine;

View File

@ -0,0 +1,24 @@
import React from 'react';
import { StyleProp, Text, TextStyle } 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';
interface IEmojiProps {
value: EmojiProps['value'];
style?: StyleProp<TextStyle>;
isBigEmoji?: boolean;
}
const Emoji: React.FC<IEmojiProps> = ({ value, style, isBigEmoji }) => {
const { theme } = useTheme();
const emojiUnicode = shortnameToUnicode(`:${value.value}:`);
return (
<Text style={[{ color: themes[theme].bodyText }, isBigEmoji ? styles.textBig : styles.text, style]}>{emojiUnicode}</Text>
);
};
export default Emoji;

View File

@ -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: React.FC<IHeadingProps> = ({ value, level }) => {
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;

View File

@ -0,0 +1,56 @@
import React from 'react';
import { StyleProp, ViewStyle } from 'react-native';
import { Paragraph as ParagraphProps } from '@rocket.chat/message-parser';
import Hashtag from '../Hashtag';
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 Mention from './Mention';
import InlineCode from './InlineCode';
import { UserMention } from '../../message/interfaces';
interface IParagraphProps {
value: ParagraphProps['value'];
mentions?: UserMention[];
channels?: {
name: string;
_id: number;
}[];
navToRoomInfo?: Function;
style?: StyleProp<ViewStyle>[];
}
const Inline: React.FC<IParagraphProps> = ({ value, mentions, channels, navToRoomInfo, style }) => (
<>
{value.map(block => {
switch (block.type) {
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 <Mention value={block.value} navToRoomInfo={navToRoomInfo} mentions={mentions} style={style} />;
case 'EMOJI':
return <Emoji value={block.value} />;
case 'MENTION_CHANNEL':
return <Hashtag hashtag={block.value.value} navToRoomInfo={navToRoomInfo} channels={channels} style={style} />;
case 'INLINE_CODE':
return <InlineCode value={block.value} style={style} />;
default:
return null;
}
})}
</>
);
export default Inline;

View File

@ -0,0 +1,41 @@
import React from 'react';
import { StyleProp, Text, TextStyle } 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';
import Plain from './Plain';
interface IInlineCodeProps {
value: InlineCodeProps['value'];
style: StyleProp<TextStyle>[];
}
const InlineCode: React.FC<IInlineCodeProps> = ({ value, style }) => {
const { theme } = useTheme();
return (
<Text
style={[
{
...styles.codeInline,
color: themes[theme].bodyText,
backgroundColor: themes[theme].bannerBackground,
borderColor: themes[theme].borderColor
},
...style
]}>
{(block => {
switch (block.type) {
case 'PLAIN_TEXT':
return <Plain value={block.value} />;
default:
return null;
}
})(value)}
</Text>
);
};
export default InlineCode;

View File

@ -0,0 +1,36 @@
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';
interface IItalicProps {
value: ItalicProps['value'];
}
const styles = StyleSheet.create({
text: {
fontStyle: 'italic'
}
});
const Italic: React.FC<IItalicProps> = ({ value }) => (
<Text style={styles.text}>
{value.map(block => {
switch (block.type) {
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;

View File

@ -0,0 +1,55 @@
import React 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';
interface ILinkProps {
value: LinkProps['value'];
}
const Link: React.FC<ILinkProps> = ({ value }) => {
const { theme } = useTheme();
const { src, label } = value;
const handlePress = () => {
if (!src.value) {
return;
}
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;

View File

@ -0,0 +1,59 @@
import React from 'react';
import { StyleProp, Text, TextStyle, ViewStyle } from 'react-native';
import { UserMention as UserMentionProps } from '@rocket.chat/message-parser';
import styles from '../styles';
import { events, logEvent } from '../../../utils/log';
import { useTheme } from '../../../theme';
import { themes } from '../../../constants/colors';
import { UserMention } from '../../message/interfaces';
interface IMentionProps {
value: UserMentionProps['value'];
mentions: UserMention[];
navToRoomInfo: Function;
style: StyleProp<ViewStyle>[];
}
const Mention: React.FC<IMentionProps> = ({ value: { value: mention }, mentions, navToRoomInfo, style }) => {
const { theme } = useTheme();
let mentionStyle: StyleProp<TextStyle>;
const notMentionedStyle = [styles.text, { color: themes[theme].bodyText }, ...style];
const mentioned = mentions.find(mentioned => mentioned.username === mention);
if (mention === 'all' || mention === 'here') {
mentionStyle = [
{
color: themes[theme].mentionGroupColor
},
...style
];
} else if (mentioned) {
mentionStyle = {
color: themes[theme].mentionMeColor
};
} else {
mentionStyle = {
color: themes[theme].mentionOtherColor
};
}
const handlePress = () => {
logEvent(events.ROOM_MENTION_GO_USER_INFO);
const navParam = {
t: 'd',
rid: mentioned && mentioned._id
};
navToRoomInfo(navParam);
};
return (
<Text
style={[styles.mention, (mention || mentioned) && mentionStyle, !(mention || mentioned) && notMentionedStyle, ...style]}
onPress={handlePress}>
{mentioned ? mentioned.name || mention : `@{${mention}}`}
</Text>
);
};
export default Mention;

View File

@ -0,0 +1,28 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { OrderedList as OrderedListProps } from '@rocket.chat/message-parser';
import Inline from './Inline';
interface IOrderedListProps {
value: OrderedListProps['value'];
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row'
}
});
const OrderedList: React.FC<IOrderedListProps> = React.memo(({ value }) => (
<>
{value.map((item, index) => (
<View style={styles.container}>
<Text>{index + 1}. </Text>
<Inline value={item.value} />
</View>
))}
</>
));
export default OrderedList;

View File

@ -0,0 +1,31 @@
import React from 'react';
import { StyleProp, Text, ViewStyle } from 'react-native';
import { Paragraph as ParagraphProps } from '@rocket.chat/message-parser';
import { UserMention } from '../../message/interfaces';
import Inline from './Inline';
import styles from '../styles';
import { useTheme } from '../../../theme';
import { themes } from '../../../constants/colors';
interface IParagraphProps {
value: ParagraphProps['value'];
mentions?: UserMention[];
channels?: {
name: string;
_id: number;
}[];
navToRoomInfo?: Function;
style?: StyleProp<ViewStyle>[];
}
const Paragraph: React.FC<IParagraphProps> = ({ value, mentions, channels, navToRoomInfo, style }) => {
const { theme } = useTheme();
return (
<Text style={[styles.text, style, { color: themes[theme].bodyText }]}>
<Inline value={value} mentions={mentions} channels={channels} navToRoomInfo={navToRoomInfo} style={style} />
</Text>
);
};
export default Paragraph;

View File

@ -0,0 +1,11 @@
import React from 'react';
import { Text } from 'react-native';
import { Plain as PlainProps } from '@rocket.chat/message-parser';
interface IPlainProps {
value: PlainProps['value'];
}
const Plain: React.FC<IPlainProps> = ({ value }) => <Text accessibilityLabel={value}>{value}</Text>;
export default Plain;

View File

@ -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: React.FC<IQuoteProps> = ({ value }) => {
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} mentions={[]} />
))}
</View>
</View>
);
};
export default Quote;

View File

@ -0,0 +1,36 @@
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';
interface IStrikeProps {
value: StrikeProps['value'];
}
const styles = StyleSheet.create({
text: {
textDecorationLine: 'line-through'
}
});
const Strike: React.FC<IStrikeProps> = ({ value }) => (
<Text style={styles.text}>
{value.map(block => {
switch (block.type) {
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;

View File

@ -0,0 +1,26 @@
import React from 'react';
import { Text } from 'react-native';
import { Tasks as TasksProps } from '@rocket.chat/message-parser';
import { Checkbox } from 'react-native-ui-lib';
import Inline from './Inline';
interface ITasksProps {
value: TasksProps['value'];
}
const TaskList: React.FC<ITasksProps> = ({ value }) => (
<Text
style={{
marginLeft: 0,
paddingLeft: 0
}}>
{value.map(item => (
<>
<Checkbox checked={item.status} /> <Inline value={item.value} />
</>
))}
</Text>
);
export default TaskList;

View File

@ -0,0 +1,18 @@
import React from 'react';
import { UnorderedList as UnorderedListProps } from '@rocket.chat/message-parser';
import Inline from './Inline';
interface IUnorderedListProps {
value: UnorderedListProps['value'];
}
const UnorderedList: React.FC<IUnorderedListProps> = ({ value }) => (
<>
{value.map(item => (
<Inline value={item.value} />
))}
</>
);
export default UnorderedList;

View File

@ -0,0 +1,67 @@
import React from 'react';
import { StyleProp, ViewStyle } from 'react-native';
import { MarkdownAST, BigEmoji as BigEmojiProps } 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';
interface IBodyProps {
tokens: MarkdownAST;
mentions: UserMention[];
channels: {
name: string;
_id: number;
}[];
navToRoomInfo: Function;
style: StyleProp<ViewStyle>[];
}
const isBigEmoji = (tokens: MarkdownAST): tokens is [BigEmojiProps] => tokens.length === 1 && tokens[0].type === 'BIG_EMOJI';
const Body: React.FC<IBodyProps> = ({ tokens, mentions, channels, navToRoomInfo, style }) => {
if (isBigEmoji(tokens)) {
return <BigEmoji value={tokens[0].value} />;
}
return (
<>
{tokens.map(block => {
switch (block.type) {
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}
navToRoomInfo={navToRoomInfo}
channels={channels}
mentions={mentions}
style={style}
/>
);
case 'CODE':
return <Code value={block.value} style={style} />;
case 'HEADING':
return <Heading value={block.value} level={block.level} />;
default:
return null;
}
})}
</>
);
};
export default Body;

View File

@ -73,7 +73,9 @@ export default StyleSheet.create<any>({
...sharedStyles.textRegular,
...codeFontFamily,
borderWidth: 1,
borderRadius: 4
borderRadius: 4,
paddingLeft: 2,
paddingTop: 2
},
codeBlock: {
...sharedStyles.textRegular,

View File

@ -51,6 +51,7 @@ const Content = React.memo(
// @ts-ignore
<Markdown
msg={props.msg}
md={props.md}
baseUrl={baseUrl}
getCustomEmoji={props.getCustomEmoji}
username={user.username}
@ -103,6 +104,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;
}

View File

@ -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}

View File

@ -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;

View File

@ -773,6 +773,7 @@
"Select_Team_Channels_To_Delete": "Select the Teams Channels you would like to delete, the ones you do not select will be moved to the Workspace. \n\nNotice that public Channels will be public and visible to everyone.",
"You_are_converting_the_team": "You are converting this Team to a Channel",
"creating_discussion": "creating discussion",
"Enable_Message_Parser": "Enable Message Parser",
"Canned_Responses": "Canned Responses",
"No_match_found": "No match found.",
"Check_canned_responses": "Check on canned responses.",

View File

@ -81,4 +81,6 @@ export default class Message extends Model {
@field('e2e') e2e;
@field('tshow') tshow;
@json('md', sanitizer) md;
}

View File

@ -190,6 +190,15 @@ export default schemaMigrations({
]
})
]
},
{
toVersion: 14,
steps: [
addColumns({
table: 'messages',
columns: [{ name: 'md', type: 'string', isOptional: true }]
})
]
}
]
});

View File

@ -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;
}

View File

@ -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 }]
})
]
}
]
});

View File

@ -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({

View File

@ -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({

View File

@ -19,6 +19,10 @@ const changeMessageStatus = async (id, tmid, status, message) => {
if (message) {
m.mentions = message.mentions;
m.channels = message.channels;
if (message.md) {
m.md = message.md;
}
}
})
);
@ -31,6 +35,10 @@ const changeMessageStatus = async (id, tmid, status, message) => {
if (message) {
tm.mentions = message.mentions;
tm.channels = message.channels;
if (message.md) {
tm.md = message.md;
}
}
})
);

View File

@ -622,7 +622,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;
},

View File

@ -1,46 +1,80 @@
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 = () => ({
title: I18n.t('Preferences')
});
const UserPreferencesView = ({ navigation }) => {
const user = useSelector(state => getUserSelector(state));
const isMessageParserServerEnabled = useSelector(
state => state.settings.Accounts_Default_User_Preferences_enableMessageParserEarlyAdoption
);
const [enableParser, setEnableParser] = useState(user.enableMessageParserEarlyAdoption);
static propTypes = {
navigation: PropTypes.object
};
useEffect(() => {
navigation.setOptions({
title: I18n.t('Preferences')
});
}, []);
navigateToScreen = (screen, params) => {
const navigateToScreen = (screen, params) => {
logEvent(events[`SE_GO_${screen.replace('View', '').toUpperCase()}`]);
const { navigation } = this.props;
navigation.navigate(screen, params);
};
render() {
return (
<SafeAreaView testID='preferences-view'>
<StatusBar />
<List.Container>
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 />
<List.Container>
<List.Section>
<List.Separator />
<List.Item
title='Notifications'
onPress={() => navigateToScreen('UserNotificationPrefView')}
showActionIndicator
testID='preferences-view-notifications'
/>
<List.Separator />
</List.Section>
{isMessageParserServerEnabled && (
<List.Section>
<List.Separator />
<List.Item
title='Notifications'
onPress={() => this.navigateToScreen('UserNotificationPrefView')}
showActionIndicator
testID='preferences-view-notifications'
title='Enable_Message_Parser'
testID='preferences-view-enable-message-parser'
right={() => renderMessageParserSwitch()}
/>
<List.Separator />
</List.Section>
</List.Container>
</SafeAreaView>
);
}
}
)}
</List.Container>
</SafeAreaView>
);
};
UserPreferencesView.propTypes = {
navigation: PropTypes.object
};
export default UserPreferencesView;

View File

@ -47,6 +47,7 @@
"@react-navigation/drawer": "5.12.5",
"@react-navigation/native": "5.9.4",
"@react-navigation/stack": "5.14.5",
"@rocket.chat/message-parser": "^0.29.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",

View File

@ -0,0 +1,582 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { storiesOf } from '@storybook/react-native';
import MessageBody from '../../app/containers/markdown/MessageBody';
import { themes } from '../../app/constants/colors';
const stories = storiesOf('MessageBody', module);
const theme = 'light';
const styles = StyleSheet.create({
container: {
marginHorizontal: 15,
backgroundColor: themes[theme].backgroundColor,
marginVertical: 50
},
separator: {
marginHorizontal: 10,
marginVertical: 10
}
});
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: 'PLAIN_TEXT',
value: 'Strong emphasis, aka bold, with '
},
{
type: 'BOLD',
value: [{
type: 'PLAIN_TEXT',
value: 'asterisks'
}]
},
{
type: 'PLAIN_TEXT',
value: ' or '
},
{
type: 'ITALIC',
value: [{
type: 'PLAIN_TEXT',
value: 'underscore'
}]
}
]
}
];
stories.add('Text', () => (
<View style={styles.container}>
<MessageBody tokens={simpleTextMsg} />
<MessageBody tokens={longTextMsg} />
<MessageBody tokens={lineBreakMsg} />
<MessageBody tokens={sequentialEmptySpacesMsg} />
<MessageBody 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}>
<MessageBody tokens={allMentionTokens} mentions={allMentions} navToRoomInfo={() => {}} style={[]} />
<MessageBody tokens={multipleMentionTokens} mentions={multipleMentions} navToRoomInfo={() => {}} style={[]} />
</View>
));
const channelTokens = [
{
type: 'PARAGRAPH',
value: [{
type: 'MENTION_CHANNEL',
value: {
type: 'PLAIN_TEXT',
value: 'text_channel'
}
}]
}
];
const channelMention = [
{
_id: 'text_channel',
name: 'text_channel'
}
];
stories.add('Hashtag', () => (
<View style={styles.container}>
<MessageBody tokens={channelTokens} channels={channelMention} navToRoomInfo={() => {}} />
</View>
));
const bigEmojiTokens = [{
type: 'BIG_EMOJI',
value: [
{
type: 'EMOJI',
value: {
type: 'PLAIN_TEXT',
value: 'green_heart'
}
}
]
}];
const multipleBigEmojiTokens = [{
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'
}
}
]
}];
stories.add('Emoji', () => (
<View style={styles.container}>
<MessageBody tokens={bigEmojiTokens} />
<MessageBody tokens={multipleBigEmojiTokens} />
<MessageBody tokens={emojiTokens} />
</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}>
<MessageBody 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}>
<MessageBody tokens={rocketChatLink} />
<MessageBody tokens={markdownLink} />
</View>
));
stories.add('Headers', () => (
<View style={styles.container}>
<MessageBody
tokens={
[
{
type: 'HEADING',
value: [{
type: 'PLAIN_TEXT',
value: '# Header 1'
}],
level: 1
}
]
}
/>
<MessageBody
tokens={
[
{
type: 'HEADING',
value: [{
type: 'PLAIN_TEXT',
value: '## Header 2'
}],
level: 2
}
]
}
/>
<MessageBody
tokens={
[
{
type: 'HEADING',
value: [{
type: 'PLAIN_TEXT',
value: '### Header 3'
}],
level: 3
}
]
}
/>
<MessageBody
tokens={
[
{
type: 'HEADING',
value: [{
type: 'PLAIN_TEXT',
value: '#### Header 4'
}],
level: 4
}
]
}
/>
<MessageBody
tokens={
[
{
type: 'HEADING',
value: [{
type: 'PLAIN_TEXT',
value: '##### Header 5'
}],
level: 5
}
]
}
/>
<MessageBody
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}>
<MessageBody tokens={inlineCodeToken} style={[]} />
<MessageBody 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}>
<MessageBody tokens={unorederedListToken} />
<MessageBody tokens={orderedListToken} />
</View>
));

View File

@ -12,6 +12,7 @@ import './HeaderButtons';
import './UnreadBadge';
import '../../app/views/ThreadMessagesView/Item.stories.js';
import './Avatar';
import './MessageBody';
import '../../app/containers/BackgroundContainer/index.stories.js';
import '../../app/containers/RoomHeader/RoomHeader.stories.js';
import '../../app/views/RoomView/LoadMore/LoadMore.stories';

View File

@ -3203,6 +3203,11 @@
dependencies:
eslint-plugin-import "^2.17.2"
"@rocket.chat/message-parser@^0.29.0":
version "0.29.0"
resolved "https://registry.yarnpkg.com/@rocket.chat/message-parser/-/message-parser-0.29.0.tgz#9577f354756653e6271b9fe2290fadafcb5d4293"
integrity sha512-2qEPTNSHeWRCFfxCETqz9Asju6bf5dR98+AQUYwNus5911WW4tuehvrfvoqLYrlYqs4w5Qj6q9m2THCXqN27Gw==
"@rocket.chat/react-native-fast-image@^8.2.0":
version "8.2.0"
resolved "https://registry.yarnpkg.com/@rocket.chat/react-native-fast-image/-/react-native-fast-image-8.2.0.tgz#4f48858f95f40afcb10b39cee9b1239c150d6c51"