From 76a99519eca9fca88a475c3948646ec8f1af204a Mon Sep 17 00:00:00 2001 From: Gerzon Z Date: Wed, 20 Oct 2021 12:32:58 -0400 Subject: [PATCH] [NEW] Support new message parser (#3313) * Add message parser to profile view and db * Add md to db * Remove changes to Xcode project * Remove message-parser lib and add enable message parser field to User model * Fix message parser * Remove admin enableMessageParserEarlyAdoption * Add NewMarkdown component * Remove NewMarkdown component and add specific components for new message parser * Add new parser components * Fix BigEmoji * Updated components and added more Code components * update components and add storybooks * Update Code component and add it to storybooks * Update Mention component * Minor tweaks * Add server message parser validation * Renamed folder, add @rocket.chat/message-parser, migrate some files to TypeScript * Migrate components to TypeScript and fix styling * Change interfaces and add TaskListComponent and styles * Fix new markdown and styles * Fix inlinecode * Stop using server setting * Use enableMessageParserEarlyAdoption on mapStateToProps * Remove React.FC * add link to bold, italic and strike * Update parser components * Fix missing components * Minor tweak * Fix lint and add getCustomEmojis * Fix customEmojis * Update emojis * Minor tweak * disconnect markdown from store * Use @rocket.chat/message-parser@0.30.0 * Fix link style * Unify lists and styles * Remove style prop * Use big emoji as a normal token * Remove unnecessary memo * Fix code styles * Update tests * Conditionally create renderer * Use Context instead of prop drill * Fix Link component * Fix plain text regression and update tests Co-authored-by: Diego Mello --- .../__snapshots__/Storyshots.test.js.snap | 1380 +++++++++++++++++ app/containers/markdown/AtMention.tsx | 15 +- app/containers/markdown/Hashtag.tsx | 17 +- app/containers/markdown/index.tsx | 64 +- app/containers/markdown/new/BigEmoji.tsx | 25 + app/containers/markdown/new/Bold.tsx | 40 + app/containers/markdown/new/Code.tsx | 39 + app/containers/markdown/new/CodeLine.tsx | 17 + app/containers/markdown/new/Emoji.tsx | 29 + app/containers/markdown/new/Heading.tsx | 32 + app/containers/markdown/new/Image.tsx | 41 + app/containers/markdown/new/Inline.tsx | 62 + app/containers/markdown/new/InlineCode.tsx | 38 + app/containers/markdown/new/Italic.tsx | 39 + app/containers/markdown/new/Link.tsx | 60 + .../markdown/new/MarkdownContext.ts | 29 + app/containers/markdown/new/OrderedList.tsx | 28 + app/containers/markdown/new/Paragraph.tsx | 23 + app/containers/markdown/new/Plain.tsx | 22 + app/containers/markdown/new/Quote.tsx | 28 + app/containers/markdown/new/Strike.tsx | 39 + app/containers/markdown/new/TaskList.tsx | 28 + app/containers/markdown/new/UnorderedList.tsx | 28 + app/containers/markdown/new/index.tsx | 77 + app/containers/markdown/styles.ts | 10 +- app/containers/message/Content.tsx | 5 + app/containers/message/index.tsx | 4 +- app/containers/message/interfaces.ts | 13 +- app/containers/message/styles.ts | 1 - app/i18n/locales/en.json | 3 +- app/lib/database/model/Message.js | 2 + app/lib/database/model/migrations.js | 9 + app/lib/database/model/servers/User.js | 2 + app/lib/database/model/servers/migrations.js | 9 + app/lib/database/schema/app.js | 5 +- app/lib/database/schema/servers.js | 5 +- app/lib/rocketchat.js | 3 +- app/views/UserPreferencesView/index.js | 91 +- package.json | 1 + storybook/stories/NewMarkdown.js | 627 ++++++++ storybook/stories/index.js | 1 + yarn.lock | 5 + 42 files changed, 2934 insertions(+), 62 deletions(-) create mode 100644 app/containers/markdown/new/BigEmoji.tsx create mode 100644 app/containers/markdown/new/Bold.tsx create mode 100644 app/containers/markdown/new/Code.tsx create mode 100644 app/containers/markdown/new/CodeLine.tsx create mode 100644 app/containers/markdown/new/Emoji.tsx create mode 100644 app/containers/markdown/new/Heading.tsx create mode 100644 app/containers/markdown/new/Image.tsx create mode 100644 app/containers/markdown/new/Inline.tsx create mode 100644 app/containers/markdown/new/InlineCode.tsx create mode 100644 app/containers/markdown/new/Italic.tsx create mode 100644 app/containers/markdown/new/Link.tsx create mode 100644 app/containers/markdown/new/MarkdownContext.ts create mode 100644 app/containers/markdown/new/OrderedList.tsx create mode 100644 app/containers/markdown/new/Paragraph.tsx create mode 100644 app/containers/markdown/new/Plain.tsx create mode 100644 app/containers/markdown/new/Quote.tsx create mode 100644 app/containers/markdown/new/Strike.tsx create mode 100644 app/containers/markdown/new/TaskList.tsx create mode 100644 app/containers/markdown/new/UnorderedList.tsx create mode 100644 app/containers/markdown/new/index.tsx create mode 100644 storybook/stories/NewMarkdown.js diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap index 8b5073224..ae596da06 100644 --- a/__tests__/__snapshots__/Storyshots.test.js.snap +++ b/__tests__/__snapshots__/Storyshots.test.js.snap @@ -15665,7 +15665,10 @@ exports[`Storyshots Markdown Code 1`] = ` "borderWidth": 1, "color": "#2f343d", "fontFamily": "Courier New", + "fontSize": 16, "fontWeight": "400", + "paddingLeft": 2, + "paddingTop": 2, "textAlign": "left", }, ] @@ -15728,7 +15731,10 @@ exports[`Storyshots Markdown Code 1`] = ` "borderWidth": 1, "color": "#2f343d", "fontFamily": "Courier New", + "fontSize": 16, "fontWeight": "400", + "paddingLeft": 2, + "paddingTop": 2, "textAlign": "left", }, ] @@ -15773,7 +15779,10 @@ exports[`Storyshots Markdown Code 1`] = ` "borderWidth": 1, "color": "#2f343d", "fontFamily": "Courier New", + "fontSize": 16, "fontWeight": "400", + "paddingLeft": 2, + "paddingTop": 2, "textAlign": "left", }, ] @@ -15819,6 +15828,7 @@ exports[`Storyshots Markdown Code 1`] = ` "borderWidth": 1, "color": "#2f343d", "fontFamily": "Courier New", + "fontSize": 16, "fontWeight": "400", "padding": 4, "textAlign": "left", @@ -16881,7 +16891,10 @@ exports[`Storyshots Markdown Links 1`] = ` "borderWidth": 1, "color": "#2f343d", "fontFamily": "Courier New", + "fontSize": 16, "fontWeight": "400", + "paddingLeft": 2, + "paddingTop": 2, "textAlign": "left", }, ] @@ -16993,7 +17006,10 @@ exports[`Storyshots Markdown Links 1`] = ` "borderWidth": 1, "color": "#2f343d", "fontFamily": "Courier New", + "fontSize": 16, "fontWeight": "400", + "paddingLeft": 2, + "paddingTop": 2, "textAlign": "left", }, ] @@ -50287,6 +50303,1370 @@ exports[`Storyshots Message Without header 1`] = ` `; +exports[`Storyshots NewMarkdown Block quote 1`] = ` + + + + + + + Rocket.Chat to the moon + + + + + +`; + +exports[`Storyshots NewMarkdown Code 1`] = ` + + + + + inline code + + + + + + Multi line + + + Code + + + +`; + +exports[`Storyshots NewMarkdown Emoji 1`] = ` + + + + 💚 + + + 😂 + + + 😁 + + + + + 🚀 + + + 🤦 + + + + + + + + + +`; + +exports[`Storyshots NewMarkdown Hashtag 1`] = ` + + + + #text_channel + + + and + + + #not_a_channel + + + +`; + +exports[`Storyshots NewMarkdown Headers 1`] = ` + + + # Header 1 + + + ## Header 2 + + + ### Header 3 + + + #### Header 4 + + + ##### Header 5 + + + ###### Header 6 + + +`; + +exports[`Storyshots NewMarkdown Links 1`] = ` + + + + https://rocket.chat + + + + + Markdown link + + + +`; + +exports[`Storyshots NewMarkdown Lists 1`] = ` + + + + + - + + + Open Source + + + + + - + + + Rocket.Chat + + + + + + + 1 + . + + + Open Source + + + + + 2 + . + + + Rocket.Chat + + + + +`; + +exports[`Storyshots NewMarkdown Mentions 1`] = ` + + + + rocket.cat + + + + + name + + + + + + rocket.cat + + + + + + here + + + + + + all + + + +`; + +exports[`Storyshots NewMarkdown Text 1`] = ` + + + + This is Rocket.Chat + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + + + + a + + + b + + + c + + + + + + d + + + + + + + + + e + + + + + a b c + + + + + + This is bold + + + + and + + + + this is italic + + + + +`; + exports[`Storyshots Room Item Alerts 1`] = ` { +const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, style = [], useRealName }: IAtMention) => { + const { theme } = useTheme(); if (mention === 'all' || mention === 'here') { return ( @@ -34,11 +35,11 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl let mentionStyle = {}; if (mention === username) { mentionStyle = { - color: themes[theme].mentionMeColor + color: themes[theme!].mentionMeColor }; } else { mentionStyle = { - color: themes[theme].mentionOtherColor + color: themes[theme!].mentionOtherColor }; } @@ -61,7 +62,7 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl ); } - return {`@${mention}`}; + return {`@${mention}`}; }); export default AtMention; diff --git a/app/containers/markdown/Hashtag.tsx b/app/containers/markdown/Hashtag.tsx index 872b8782a..b20794de5 100644 --- a/app/containers/markdown/Hashtag.tsx +++ b/app/containers/markdown/Hashtag.tsx @@ -1,23 +1,26 @@ import React from 'react'; -import { Text } from 'react-native'; +import { Text, TextStyle } from 'react-native'; import { themes } from '../../constants/colors'; +import { useTheme } from '../../theme'; import styles from './styles'; interface IHashtag { hashtag: string; navToRoomInfo: Function; - style: []; - theme: string; + style?: TextStyle[]; channels: { + [index: number]: string | number; name: string; _id: number; }[]; } -const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [], theme }: IHashtag) => { +const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IHashtag) => { + const { theme } = useTheme(); + const handlePress = () => { - const index = channels.findIndex(channel => channel.name === hashtag); + const index = channels?.findIndex(channel => channel.name === hashtag); const navParam = { t: 'c', rid: channels[index]._id @@ -31,7 +34,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [], them style={[ styles.mention, { - color: themes[theme].mentionOtherColor + color: themes[theme!].mentionOtherColor }, ...style ]} @@ -40,7 +43,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [], them ); } - return {`#${hashtag}`}; + return {`#${hashtag}`}; }); export default Hashtag; diff --git a/app/containers/markdown/index.tsx b/app/containers/markdown/index.tsx index d3ce8453e..d84dc7dc8 100644 --- a/app/containers/markdown/index.tsx +++ b/app/containers/markdown/index.tsx @@ -3,6 +3,7 @@ import { Image, Text } from 'react-native'; import { Node, Parser } from 'commonmark'; import Renderer from 'commonmark-react-renderer'; import removeMarkdown from 'remove-markdown'; +import { MarkdownAST } from '@rocket.chat/message-parser'; import shortnameToUnicode from '../../utils/shortnameToUnicode'; import I18n from '../../i18n'; @@ -20,9 +21,20 @@ import MarkdownTableCell from './TableCell'; import mergeTextNodes from './mergeTextNodes'; import styles from './styles'; import { isValidURL } from '../../utils/url'; +import NewMarkdown from './new'; + +interface IUser { + _id: string; + username: string; + name: string; +} + +type UserMention = Pick; interface IMarkdownProps { msg: string; + md: MarkdownAST; + mentions: UserMention[]; getCustomEmoji: Function; baseUrl: string; username: string; @@ -35,7 +47,7 @@ interface IMarkdownProps { name: string; _id: number; }[]; - mentions: object[]; + enableMessageParser: boolean; navToRoomInfo: Function; preview: boolean; theme: string; @@ -97,7 +109,9 @@ class Markdown extends PureComponent { constructor(props: IMarkdownProps) { super(props); - this.renderer = this.createRenderer(); + if (!this.isNewMarkdown) { + this.renderer = this.createRenderer(); + } } createRenderer = () => @@ -139,6 +153,11 @@ class Markdown extends PureComponent { renderParagraphsInLists: true }); + get isNewMarkdown(): boolean { + const { md, enableMessageParser } = this.props; + return enableMessageParser && !!md; + } + editedMessage = (ast: any) => { const { isEdited } = this.props; if (isEdited) { @@ -227,12 +246,12 @@ class Markdown extends PureComponent { }; renderHashtag = ({ hashtag }: { hashtag: string }) => { - const { channels, navToRoomInfo, style, theme } = this.props; - return ; + const { channels, navToRoomInfo, style } = this.props; + return ; }; renderAtMention = ({ mentionName }: { mentionName: string }) => { - const { username, mentions, navToRoomInfo, useRealName, style, theme } = this.props; + const { username, mentions, navToRoomInfo, useRealName, style } = this.props; return ( { useRealName={useRealName} username={username} navToRoomInfo={navToRoomInfo} - theme={theme} style={style} /> ); @@ -329,12 +347,44 @@ class Markdown extends PureComponent { }; render() { - const { msg, numberOfLines, preview = false, theme, style = [], testID } = this.props; + const { + msg, + md, + numberOfLines, + preview = false, + theme, + style = [], + testID, + mentions, + channels, + navToRoomInfo, + useRealName, + username, + getCustomEmoji, + baseUrl, + onLinkPress + } = this.props; if (!msg) { return null; } + if (this.isNewMarkdown) { + return ( + + ); + } + let m = formatText(msg); // Ex: '[ ](https://open.rocket.chat/group/test?msg=abcdef) Test' diff --git a/app/containers/markdown/new/BigEmoji.tsx b/app/containers/markdown/new/BigEmoji.tsx new file mode 100644 index 000000000..c8f6a3beb --- /dev/null +++ b/app/containers/markdown/new/BigEmoji.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { BigEmoji as BigEmojiProps } from '@rocket.chat/message-parser'; + +import Emoji from './Emoji'; + +interface IBigEmojiProps { + value: BigEmojiProps['value']; +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row' + } +}); + +const BigEmoji = ({ value }: IBigEmojiProps): JSX.Element => ( + + {value.map(block => ( + + ))} + +); + +export default BigEmoji; diff --git a/app/containers/markdown/new/Bold.tsx b/app/containers/markdown/new/Bold.tsx new file mode 100644 index 000000000..9cb1f4fdb --- /dev/null +++ b/app/containers/markdown/new/Bold.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { StyleSheet, Text } from 'react-native'; +import { Bold as BoldProps } from '@rocket.chat/message-parser'; + +import sharedStyles from '../../../views/Styles'; +import Strike from './Strike'; +import Italic from './Italic'; +import Plain from './Plain'; +import Link from './Link'; + +interface IBoldProps { + value: BoldProps['value']; +} + +const styles = StyleSheet.create({ + text: { + ...sharedStyles.textBold + } +}); + +const Bold = ({ value }: IBoldProps): JSX.Element => ( + + {value.map(block => { + switch (block.type) { + case 'LINK': + return ; + case 'PLAIN_TEXT': + return ; + case 'STRIKE': + return ; + case 'ITALIC': + return ; + default: + return null; + } + })} + +); + +export default Bold; diff --git a/app/containers/markdown/new/Code.tsx b/app/containers/markdown/new/Code.tsx new file mode 100644 index 000000000..d2a164367 --- /dev/null +++ b/app/containers/markdown/new/Code.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Text } from 'react-native'; +import { Code as CodeProps } from '@rocket.chat/message-parser'; + +import styles from '../styles'; +import { themes } from '../../../constants/colors'; +import { useTheme } from '../../../theme'; +import CodeLine from './CodeLine'; + +interface ICodeProps { + value: CodeProps['value']; +} + +const Code = ({ value }: ICodeProps): JSX.Element => { + const { theme } = useTheme(); + + return ( + + {value.map(block => { + switch (block.type) { + case 'CODE_LINE': + return ; + default: + return null; + } + })} + + ); +}; + +export default Code; diff --git a/app/containers/markdown/new/CodeLine.tsx b/app/containers/markdown/new/CodeLine.tsx new file mode 100644 index 000000000..c05066f9c --- /dev/null +++ b/app/containers/markdown/new/CodeLine.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Text } from 'react-native'; +import { CodeLine as CodeLineProps } from '@rocket.chat/message-parser'; + +interface ICodeLineProps { + value: CodeLineProps['value']; +} + +const CodeLine = ({ value }: ICodeLineProps): JSX.Element | null => { + if (value.type !== 'PLAIN_TEXT') { + return null; + } + + return {value.value}; +}; + +export default CodeLine; diff --git a/app/containers/markdown/new/Emoji.tsx b/app/containers/markdown/new/Emoji.tsx new file mode 100644 index 000000000..0800b8874 --- /dev/null +++ b/app/containers/markdown/new/Emoji.tsx @@ -0,0 +1,29 @@ +import React, { useContext } from 'react'; +import { Text } from 'react-native'; +import { Emoji as EmojiProps } from '@rocket.chat/message-parser'; + +import shortnameToUnicode from '../../../utils/shortnameToUnicode'; +import { themes } from '../../../constants/colors'; +import { useTheme } from '../../../theme'; +import styles from '../styles'; +import CustomEmoji from '../../EmojiPicker/CustomEmoji'; +import MarkdownContext from './MarkdownContext'; + +interface IEmojiProps { + value: EmojiProps['value']; + isBigEmoji?: boolean; +} + +const Emoji = ({ value, isBigEmoji }: IEmojiProps): JSX.Element => { + const { theme } = useTheme(); + const { baseUrl, getCustomEmoji } = useContext(MarkdownContext); + const emojiUnicode = shortnameToUnicode(`:${value.value}:`); + const emoji = getCustomEmoji?.(value.value); + + if (emoji) { + return ; + } + return {emojiUnicode}; +}; + +export default Emoji; diff --git a/app/containers/markdown/new/Heading.tsx b/app/containers/markdown/new/Heading.tsx new file mode 100644 index 000000000..2e810d376 --- /dev/null +++ b/app/containers/markdown/new/Heading.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Text } from 'react-native'; +import { Heading as HeadingProps } from '@rocket.chat/message-parser'; + +import { themes } from '../../../constants/colors'; +import styles from '../styles'; +import { useTheme } from '../../../theme'; + +interface IHeadingProps { + value: HeadingProps['value']; + level: HeadingProps['level']; +} + +const Heading = ({ value, level }: IHeadingProps): JSX.Element => { + const { theme } = useTheme(); + const textStyle = styles[`heading${level}`]; + + return ( + + {value.map(block => { + switch (block.type) { + case 'PLAIN_TEXT': + return block.value; + default: + return null; + } + })} + + ); +}; + +export default Heading; diff --git a/app/containers/markdown/new/Image.tsx b/app/containers/markdown/new/Image.tsx new file mode 100644 index 000000000..fb9f95d28 --- /dev/null +++ b/app/containers/markdown/new/Image.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { Image as ImageProps } from '@rocket.chat/message-parser'; +import { createImageProgress } from 'react-native-image-progress'; +import * as Progress from 'react-native-progress'; +import FastImage from '@rocket.chat/react-native-fast-image'; + +import { useTheme } from '../../../theme'; +import { themes } from '../../../constants/colors'; +import styles from '../../message/styles'; + +interface IImageProps { + value: ImageProps['value']; +} + +type TMessageImage = { + img: string; + theme: string; +}; + +const ImageProgress = createImageProgress(FastImage); + +const MessageImage = ({ img, theme }: TMessageImage) => ( + +); + +const Image = ({ value }: IImageProps): JSX.Element => { + const { theme } = useTheme(); + const { src } = value; + + return ; +}; + +export default Image; diff --git a/app/containers/markdown/new/Inline.tsx b/app/containers/markdown/new/Inline.tsx new file mode 100644 index 000000000..08c4b1e5f --- /dev/null +++ b/app/containers/markdown/new/Inline.tsx @@ -0,0 +1,62 @@ +import React, { useContext } from 'react'; +import { Paragraph as ParagraphProps } from '@rocket.chat/message-parser'; + +import Hashtag from '../Hashtag'; +import AtMention from '../AtMention'; +import Link from './Link'; +import Plain from './Plain'; +import Bold from './Bold'; +import Strike from './Strike'; +import Italic from './Italic'; +import Emoji from './Emoji'; +import InlineCode from './InlineCode'; +import Image from './Image'; +import MarkdownContext from './MarkdownContext'; + +interface IParagraphProps { + value: ParagraphProps['value']; +} + +const Inline = ({ value }: IParagraphProps): JSX.Element => { + const { useRealName, username, navToRoomInfo, mentions, channels } = useContext(MarkdownContext); + return ( + <> + {value.map(block => { + switch (block.type) { + case 'IMAGE': + return ; + case 'PLAIN_TEXT': + return ; + case 'BOLD': + return ; + case 'STRIKE': + return ; + case 'ITALIC': + return ; + case 'LINK': + return ; + case 'MENTION_USER': + return ( + + ); + case 'EMOJI': + return ; + case 'MENTION_CHANNEL': + return ; + case 'INLINE_CODE': + return ; + default: + return null; + } + })} + + ); +}; + +export default Inline; diff --git a/app/containers/markdown/new/InlineCode.tsx b/app/containers/markdown/new/InlineCode.tsx new file mode 100644 index 000000000..cf90f2cb3 --- /dev/null +++ b/app/containers/markdown/new/InlineCode.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { Text } from 'react-native'; +import { InlineCode as InlineCodeProps } from '@rocket.chat/message-parser'; + +import styles from '../styles'; +import { themes } from '../../../constants/colors'; +import { useTheme } from '../../../theme'; + +interface IInlineCodeProps { + value: InlineCodeProps['value']; +} + +const InlineCode = ({ value }: IInlineCodeProps): JSX.Element => { + const { theme } = useTheme(); + + return ( + + {(block => { + switch (block.type) { + case 'PLAIN_TEXT': + return {block.value}; + default: + return null; + } + })(value)} + + ); +}; + +export default InlineCode; diff --git a/app/containers/markdown/new/Italic.tsx b/app/containers/markdown/new/Italic.tsx new file mode 100644 index 000000000..fc1432e5a --- /dev/null +++ b/app/containers/markdown/new/Italic.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { StyleSheet, Text } from 'react-native'; +import { Italic as ItalicProps } from '@rocket.chat/message-parser'; + +import Strike from './Strike'; +import Bold from './Bold'; +import Plain from './Plain'; +import Link from './Link'; + +interface IItalicProps { + value: ItalicProps['value']; +} + +const styles = StyleSheet.create({ + text: { + fontStyle: 'italic' + } +}); + +const Italic = ({ value }: IItalicProps): JSX.Element => ( + + {value.map(block => { + switch (block.type) { + case 'LINK': + return ; + case 'PLAIN_TEXT': + return ; + case 'STRIKE': + return ; + case 'BOLD': + return ; + default: + return null; + } + })} + +); + +export default Italic; diff --git a/app/containers/markdown/new/Link.tsx b/app/containers/markdown/new/Link.tsx new file mode 100644 index 000000000..e29c4f2c0 --- /dev/null +++ b/app/containers/markdown/new/Link.tsx @@ -0,0 +1,60 @@ +import React, { useContext } from 'react'; +import { Text, Clipboard } from 'react-native'; +import { Link as LinkProps } from '@rocket.chat/message-parser'; + +import styles from '../styles'; +import I18n from '../../../i18n'; +import { LISTENER } from '../../Toast'; +import { useTheme } from '../../../theme'; +import openLink from '../../../utils/openLink'; +import EventEmitter from '../../../utils/events'; +import { themes } from '../../../constants/colors'; +import Strike from './Strike'; +import Italic from './Italic'; +import Bold from './Bold'; +import MarkdownContext from './MarkdownContext'; + +interface ILinkProps { + value: LinkProps['value']; +} + +const Link = ({ value }: ILinkProps): JSX.Element => { + const { theme } = useTheme(); + const { onLinkPress } = useContext(MarkdownContext); + const { src, label } = value; + const handlePress = () => { + if (!src.value) { + return; + } + if (onLinkPress) { + return onLinkPress(src.value); + } + openLink(src.value, theme); + }; + + const onLongPress = () => { + Clipboard.setString(src.value); + EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); + }; + + return ( + + {(block => { + switch (block.type) { + case 'PLAIN_TEXT': + return block.value; + case 'STRIKE': + return ; + case 'ITALIC': + return ; + case 'BOLD': + return ; + default: + return null; + } + })(label)} + + ); +}; + +export default Link; diff --git a/app/containers/markdown/new/MarkdownContext.ts b/app/containers/markdown/new/MarkdownContext.ts new file mode 100644 index 000000000..b22f15614 --- /dev/null +++ b/app/containers/markdown/new/MarkdownContext.ts @@ -0,0 +1,29 @@ +import React from 'react'; + +import { UserMention } from '../../message/interfaces'; + +interface IMarkdownContext { + mentions: UserMention[]; + channels: { + name: string; + _id: number; + }[]; + useRealName: boolean; + username: string; + baseUrl: string; + navToRoomInfo: Function; + getCustomEmoji?: Function; + onLinkPress?: Function; +} + +const defaultState = { + mentions: [], + channels: [], + useRealName: false, + username: '', + baseUrl: '', + navToRoomInfo: () => {} +}; + +const MarkdownContext = React.createContext(defaultState); +export default MarkdownContext; diff --git a/app/containers/markdown/new/OrderedList.tsx b/app/containers/markdown/new/OrderedList.tsx new file mode 100644 index 000000000..c5ae25125 --- /dev/null +++ b/app/containers/markdown/new/OrderedList.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { View, Text } from 'react-native'; +import { OrderedList as OrderedListProps } from '@rocket.chat/message-parser'; + +import Inline from './Inline'; +import styles from '../styles'; +import { themes } from '../../../constants/colors'; +import { useTheme } from '../../../theme'; + +interface IOrderedListProps { + value: OrderedListProps['value']; +} + +const OrderedList = ({ value }: IOrderedListProps): JSX.Element => { + const { theme } = useTheme(); + return ( + + {value.map((item, index) => ( + + {index + 1}. + + + ))} + + ); +}; + +export default OrderedList; diff --git a/app/containers/markdown/new/Paragraph.tsx b/app/containers/markdown/new/Paragraph.tsx new file mode 100644 index 000000000..2f7649bb9 --- /dev/null +++ b/app/containers/markdown/new/Paragraph.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Text } from 'react-native'; +import { Paragraph as ParagraphProps } from '@rocket.chat/message-parser'; + +import Inline from './Inline'; +import styles from '../styles'; +import { useTheme } from '../../../theme'; +import { themes } from '../../../constants/colors'; + +interface IParagraphProps { + value: ParagraphProps['value']; +} + +const Paragraph = ({ value }: IParagraphProps): JSX.Element => { + const { theme } = useTheme(); + return ( + + + + ); +}; + +export default Paragraph; diff --git a/app/containers/markdown/new/Plain.tsx b/app/containers/markdown/new/Plain.tsx new file mode 100644 index 000000000..9eca2e0c7 --- /dev/null +++ b/app/containers/markdown/new/Plain.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Text } from 'react-native'; +import { Plain as PlainProps } from '@rocket.chat/message-parser'; + +import styles from '../styles'; +import { useTheme } from '../../../theme'; +import { themes } from '../../../constants/colors'; + +interface IPlainProps { + value: PlainProps['value']; +} + +const Plain = ({ value }: IPlainProps): JSX.Element => { + const { theme } = useTheme(); + return ( + + {value} + + ); +}; + +export default Plain; diff --git a/app/containers/markdown/new/Quote.tsx b/app/containers/markdown/new/Quote.tsx new file mode 100644 index 000000000..27d6a01d1 --- /dev/null +++ b/app/containers/markdown/new/Quote.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { View } from 'react-native'; +import { Quote as QuoteProps } from '@rocket.chat/message-parser'; + +import { themes } from '../../../constants/colors'; +import { useTheme } from '../../../theme'; +import styles from '../styles'; +import Paragraph from './Paragraph'; + +interface IQuoteProps { + value: QuoteProps['value']; +} + +const Quote = ({ value }: IQuoteProps): JSX.Element => { + const { theme } = useTheme(); + return ( + + + + {value.map(item => ( + + ))} + + + ); +}; + +export default Quote; diff --git a/app/containers/markdown/new/Strike.tsx b/app/containers/markdown/new/Strike.tsx new file mode 100644 index 000000000..4d1cf5ea8 --- /dev/null +++ b/app/containers/markdown/new/Strike.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { StyleSheet, Text } from 'react-native'; +import { Strike as StrikeProps } from '@rocket.chat/message-parser'; + +import Bold from './Bold'; +import Italic from './Italic'; +import Plain from './Plain'; +import Link from './Link'; + +interface IStrikeProps { + value: StrikeProps['value']; +} + +const styles = StyleSheet.create({ + text: { + textDecorationLine: 'line-through' + } +}); + +const Strike = ({ value }: IStrikeProps): JSX.Element => ( + + {value.map(block => { + switch (block.type) { + case 'LINK': + return ; + case 'PLAIN_TEXT': + return ; + case 'BOLD': + return ; + case 'ITALIC': + return ; + default: + return null; + } + })} + +); + +export default Strike; diff --git a/app/containers/markdown/new/TaskList.tsx b/app/containers/markdown/new/TaskList.tsx new file mode 100644 index 000000000..8f46af965 --- /dev/null +++ b/app/containers/markdown/new/TaskList.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Text, View } from 'react-native'; +import { Tasks as TasksProps } from '@rocket.chat/message-parser'; + +import Inline from './Inline'; +import styles from '../styles'; +import { themes } from '../../../constants/colors'; +import { useTheme } from '../../../theme'; + +interface ITasksProps { + value: TasksProps['value']; +} + +const TaskList = ({ value = [] }: ITasksProps): JSX.Element => { + const { theme } = useTheme(); + return ( + + {value.map(item => ( + + {item.status ? '- [x] ' : '- [ ] '} + + + ))} + + ); +}; + +export default TaskList; diff --git a/app/containers/markdown/new/UnorderedList.tsx b/app/containers/markdown/new/UnorderedList.tsx new file mode 100644 index 000000000..51c9b2188 --- /dev/null +++ b/app/containers/markdown/new/UnorderedList.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { UnorderedList as UnorderedListProps } from '@rocket.chat/message-parser'; +import { View, Text } from 'react-native'; + +import Inline from './Inline'; +import styles from '../styles'; +import { themes } from '../../../constants/colors'; +import { useTheme } from '../../../theme'; + +interface IUnorderedListProps { + value: UnorderedListProps['value']; +} + +const UnorderedList = ({ value }: IUnorderedListProps): JSX.Element => { + const { theme } = useTheme(); + return ( + + {value.map(item => ( + + - + + + ))} + + ); +}; + +export default UnorderedList; diff --git a/app/containers/markdown/new/index.tsx b/app/containers/markdown/new/index.tsx new file mode 100644 index 000000000..a56b66f54 --- /dev/null +++ b/app/containers/markdown/new/index.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { MarkdownAST } from '@rocket.chat/message-parser'; + +import Quote from './Quote'; +import Paragraph from './Paragraph'; +import Heading from './Heading'; +import Code from './Code'; +import BigEmoji from './BigEmoji'; +import OrderedList from './OrderedList'; +import UnorderedList from './UnorderedList'; +import { UserMention } from '../../message/interfaces'; +import TaskList from './TaskList'; +import MarkdownContext from './MarkdownContext'; + +interface IBodyProps { + tokens: MarkdownAST; + mentions: UserMention[]; + channels: { + name: string; + _id: number; + }[]; + getCustomEmoji?: Function; + onLinkPress?: Function; + navToRoomInfo: Function; + useRealName: boolean; + username: string; + baseUrl: string; +} + +const Body = ({ + tokens, + mentions, + channels, + useRealName, + username, + navToRoomInfo, + getCustomEmoji, + baseUrl, + onLinkPress +}: IBodyProps): JSX.Element => ( + + {tokens.map(block => { + switch (block.type) { + case 'BIG_EMOJI': + return ; + case 'UNORDERED_LIST': + return ; + case 'ORDERED_LIST': + return ; + case 'TASKS': + return ; + case 'QUOTE': + return ; + case 'PARAGRAPH': + return ; + case 'CODE': + return ; + case 'HEADING': + return ; + default: + return null; + } + })} + +); + +export default Body; diff --git a/app/containers/markdown/styles.ts b/app/containers/markdown/styles.ts index d7eef0502..7a9cdebb5 100644 --- a/app/containers/markdown/styles.ts +++ b/app/containers/markdown/styles.ts @@ -30,6 +30,10 @@ export default StyleSheet.create({ del: { textDecorationLine: 'line-through' }, + plainText: { + fontSize: 16, + flexShrink: 1 + }, text: { fontSize: 16, ...sharedStyles.textRegular @@ -70,12 +74,16 @@ export default StyleSheet.create({ resizeMode: 'contain' }, codeInline: { + fontSize: 16, ...sharedStyles.textRegular, ...codeFontFamily, borderWidth: 1, - borderRadius: 4 + borderRadius: 4, + paddingLeft: 2, + paddingTop: 2 }, codeBlock: { + fontSize: 16, ...sharedStyles.textRegular, ...codeFontFamily, borderWidth: 1, diff --git a/app/containers/message/Content.tsx b/app/containers/message/Content.tsx index ee3b8c931..b9aaf9620 100644 --- a/app/containers/message/Content.tsx +++ b/app/containers/message/Content.tsx @@ -51,8 +51,10 @@ const Content = React.memo( // @ts-ignore { unread, blocks, autoTranslate: autoTranslateMessage, - replies + replies, + md } = item; let message = msg; @@ -391,6 +392,7 @@ class MessageContainer extends React.Component { ; + 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; diff --git a/app/containers/message/styles.ts b/app/containers/message/styles.ts index 3d4334f7c..49aa273eb 100644 --- a/app/containers/message/styles.ts +++ b/app/containers/message/styles.ts @@ -106,7 +106,6 @@ export default StyleSheet.create({ }, image: { width: '100%', - // maxWidth: 400, minHeight: isTablet ? 300 : 200, borderRadius: 4, borderWidth: 1, diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 7b76ef75b..0d66cc698 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -779,5 +779,6 @@ "Shortcut": "Shortcut", "Content": "Content", "Sharing": "Sharing", - "No_canned_responses": "No canned responses" + "No_canned_responses": "No canned responses", + "Enable_Message_Parser": "Enable Message Parser" } diff --git a/app/lib/database/model/Message.js b/app/lib/database/model/Message.js index a03902a26..20134733b 100644 --- a/app/lib/database/model/Message.js +++ b/app/lib/database/model/Message.js @@ -81,4 +81,6 @@ export default class Message extends Model { @field('e2e') e2e; @field('tshow') tshow; + + @json('md', sanitizer) md; } diff --git a/app/lib/database/model/migrations.js b/app/lib/database/model/migrations.js index fe32ec4f3..15c1331fe 100644 --- a/app/lib/database/model/migrations.js +++ b/app/lib/database/model/migrations.js @@ -190,6 +190,15 @@ export default schemaMigrations({ ] }) ] + }, + { + toVersion: 14, + steps: [ + addColumns({ + table: 'messages', + columns: [{ name: 'md', type: 'string', isOptional: true }] + }) + ] } ] }); diff --git a/app/lib/database/model/servers/User.js b/app/lib/database/model/servers/User.js index 6d78c27fa..30bd5f57d 100644 --- a/app/lib/database/model/servers/User.js +++ b/app/lib/database/model/servers/User.js @@ -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; } diff --git a/app/lib/database/model/servers/migrations.js b/app/lib/database/model/servers/migrations.js index 51ec2b73b..d1f24f125 100644 --- a/app/lib/database/model/servers/migrations.js +++ b/app/lib/database/model/servers/migrations.js @@ -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 }] + }) + ] } ] }); diff --git a/app/lib/database/schema/app.js b/app/lib/database/schema/app.js index 33bde61de..043ddb8e9 100644 --- a/app/lib/database/schema/app.js +++ b/app/lib/database/schema/app.js @@ -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({ diff --git a/app/lib/database/schema/servers.js b/app/lib/database/schema/servers.js index 1105cf165..1d849b874 100644 --- a/app/lib/database/schema/servers.js +++ b/app/lib/database/schema/servers.js @@ -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({ diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 40722348f..275cc1cf1 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -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; }, diff --git a/app/views/UserPreferencesView/index.js b/app/views/UserPreferencesView/index.js index 573ab58f9..476c49a7d 100644 --- a/app/views/UserPreferencesView/index.js +++ b/app/views/UserPreferencesView/index.js @@ -1,46 +1,75 @@ -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 [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 ( - - - - - - this.navigateToScreen('UserNotificationPrefView')} - showActionIndicator - testID='preferences-view-notifications' - /> - - - - - ); - } -} + const toggleMessageParser = async value => { + try { + await RocketChat.saveUserPreferences({ id: user.id, enableMessageParserEarlyAdoption: value }); + setEnableParser(value); + } catch (e) { + log(e); + } + }; + + const renderMessageParserSwitch = () => ( + + ); + + return ( + + + + + + navigateToScreen('UserNotificationPrefView')} + showActionIndicator + testID='preferences-view-notifications' + /> + + + + + renderMessageParserSwitch()} + /> + + + + + ); +}; + +UserPreferencesView.propTypes = { + navigation: PropTypes.object +}; export default UserPreferencesView; diff --git a/package.json b/package.json index 5786cd036..dcf82c98d 100644 --- a/package.json +++ b/package.json @@ -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.30.0", "@rocket.chat/react-native-fast-image": "^8.2.0", "@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile", "@rocket.chat/ui-kit": "0.13.0", diff --git a/storybook/stories/NewMarkdown.js b/storybook/stories/NewMarkdown.js new file mode 100644 index 000000000..17684bb91 --- /dev/null +++ b/storybook/stories/NewMarkdown.js @@ -0,0 +1,627 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { storiesOf } from '@storybook/react-native'; + +import NewMarkdown from '../../app/containers/markdown/new'; +import { themes } from '../../app/constants/colors'; + +const stories = storiesOf('NewMarkdown', module); + +const theme = 'light'; + +const styles = StyleSheet.create({ + container: { + marginHorizontal: 15, + backgroundColor: themes[theme].backgroundColor, + marginVertical: 50 + }, + separator: { + marginHorizontal: 10, + marginVertical: 10 + } +}); + +const getCustomEmoji = content => { + const customEmoji = { + marioparty: { name: content, extension: 'gif' }, + nyan_rocket: { name: content, extension: 'png' } + }[content]; + return customEmoji; +}; +const baseUrl = 'https://open.rocket.chat'; + +const simpleTextMsg = [ + { + type: 'PARAGRAPH', + value: [ + { + type: 'PLAIN_TEXT', + value: 'This is Rocket.Chat' + } + ] + } +]; + +const longTextMsg = [ + { + type: 'PARAGRAPH', + value: [ + { + type: 'PLAIN_TEXT', + value: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' + } + ] + } +]; + +const lineBreakMsg = [ + { + type: 'PARAGRAPH', + value: [ + { + type: 'PLAIN_TEXT', + value: 'a' + }, + { + type: 'PLAIN_TEXT', + value: 'b' + }, + { + type: 'PLAIN_TEXT', + value: 'c' + }, + { + type: 'PLAIN_TEXT', + value: '' + }, + { + type: 'PLAIN_TEXT', + value: 'd' + }, + { + type: 'PLAIN_TEXT', + value: '' + }, + { + type: 'PLAIN_TEXT', + value: '' + }, + { + type: 'PLAIN_TEXT', + value: 'e' + } + ] + } +]; + +const sequentialEmptySpacesMsg = [ + { + type: 'PARAGRAPH', + value: [ + { + type: 'PLAIN_TEXT', + value: 'a b c' + } + ] + } +]; + +const boldOrUnderscoreMsg = [ + { + type: 'PARAGRAPH', + value: [ + { + type: 'BOLD', + value: [ + { + type: 'PLAIN_TEXT', + value: 'This is bold' + } + ] + }, + { + type: 'PLAIN_TEXT', + value: ' and ' + }, + { + type: 'ITALIC', + value: [ + { + type: 'PLAIN_TEXT', + value: 'this is italic' + } + ] + } + ] + } +]; + +stories.add('Text', () => ( + + + + + + + +)); + +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', () => ( + + {}} style={[]} /> + {}} + style={[]} + username='rocket.cat' + /> + +)); + +const channelTokens = [ + { + type: 'PARAGRAPH', + value: [ + { + type: 'MENTION_CHANNEL', + value: { + type: 'PLAIN_TEXT', + value: 'text_channel' + } + }, + { + type: 'PLAIN_TEXT', + value: ' and ' + }, + { + type: 'MENTION_CHANNEL', + value: { + type: 'PLAIN_TEXT', + value: 'not_a_channel' + } + } + ] + } +]; + +const channelMention = [ + { + _id: 'text_channel', + name: 'text_channel' + } +]; + +stories.add('Hashtag', () => ( + + {}} /> + +)); + +const bigEmojiTokens = [ + { + type: 'BIG_EMOJI', + value: [ + { + type: 'EMOJI', + value: { + type: 'PLAIN_TEXT', + value: 'green_heart' + } + }, + { + type: 'EMOJI', + value: { + type: 'PLAIN_TEXT', + value: 'joy' + } + }, + { + type: 'EMOJI', + value: { + type: 'PLAIN_TEXT', + value: 'grin' + } + } + ] + } +]; + +const emojiTokens = [ + { + type: 'PARAGRAPH', + value: [ + { + type: 'EMOJI', + value: { + type: 'PLAIN_TEXT', + value: 'rocket' + } + }, + { + type: 'EMOJI', + value: { + type: 'PLAIN_TEXT', + value: 'facepalm' + } + }, + { + type: 'EMOJI', + value: { + type: 'PLAIN_TEXT', + value: 'nyan_rocket' + } + }, + { + type: 'EMOJI', + value: { + type: 'PLAIN_TEXT', + value: 'marioparty' + } + } + ] + } +]; + +stories.add('Emoji', () => ( + + + + +)); + +const blockQuoteTokens = [ + { + type: 'QUOTE', + value: [ + { + type: 'PARAGRAPH', + value: [ + { + type: 'PLAIN_TEXT', + value: 'Rocket.Chat to the moon' + } + ] + } + ] + } +]; + +stories.add('Block quote', () => ( + + + +)); + +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', () => ( + + + + +)); + +stories.add('Headers', () => ( + + + + + + + + +)); + +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', () => ( + + + + +)); + +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', () => ( + + + + +)); diff --git a/storybook/stories/index.js b/storybook/stories/index.js index 4ff6450b0..c8209d6f6 100644 --- a/storybook/stories/index.js +++ b/storybook/stories/index.js @@ -12,6 +12,7 @@ import './HeaderButtons'; import './UnreadBadge'; import '../../app/views/ThreadMessagesView/Item.stories.js'; import './Avatar'; +import './NewMarkdown'; import '../../app/containers/BackgroundContainer/index.stories.js'; import '../../app/containers/RoomHeader/RoomHeader.stories.js'; import '../../app/views/RoomView/LoadMore/LoadMore.stories'; diff --git a/yarn.lock b/yarn.lock index 7352e3388..388162383 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3748,6 +3748,11 @@ dependencies: eslint-plugin-import "^2.17.2" +"@rocket.chat/message-parser@0.30.0": + version "0.30.0" + resolved "https://registry.yarnpkg.com/@rocket.chat/message-parser/-/message-parser-0.30.0.tgz#63a25aa7fa17724d55db80f95f7f8d6a99ae42ff" + integrity sha512-pI7ajaojv+GqhQBMnFiBOWerE7zIlJywWFaLzJlIC/wsJ9LgX6YaKY2wqc909nkr+E4qZY1luJ61ErXGGSF9Zw== + "@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"