2017-12-02 13:19:58 +00:00
import React from 'react' ;
2019-04-08 12:35:28 +00:00
import { Text , Image } from 'react-native' ;
2017-12-02 13:19:58 +00:00
import PropTypes from 'prop-types' ;
import { emojify } from 'react-emojione' ;
2018-05-29 17:09:20 +00:00
import MarkdownRenderer , { PluginContainer } from 'react-native-markdown-renderer' ;
import MarkdownFlowdock from 'markdown-it-flowdock' ;
2019-05-20 20:43:50 +00:00
2018-01-16 18:48:05 +00:00
import styles from './styles' ;
2018-01-30 19:48:26 +00:00
import CustomEmoji from '../EmojiPicker/CustomEmoji' ;
2018-05-29 17:09:20 +00:00
import MarkdownEmojiPlugin from './MarkdownEmojiPlugin' ;
2019-05-20 20:43:50 +00:00
import I18n from '../../i18n' ;
const EmojiPlugin = new PluginContainer ( MarkdownEmojiPlugin ) ;
const MentionsPlugin = new PluginContainer ( MarkdownFlowdock ) ;
const plugins = [ EmojiPlugin , MentionsPlugin ] ;
2018-04-24 19:34:03 +00:00
2018-05-07 20:41:36 +00:00
// Support <http://link|Text>
2018-09-25 19:28:42 +00:00
const formatText = text => text . replace (
new RegExp ( '(?:<|<)((?:https|http):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)' , 'gm' ) ,
( match , url , title ) => ` [ ${ title } ]( ${ url } ) `
) ;
2018-05-07 20:41:36 +00:00
2019-06-27 16:35:45 +00:00
const emojiRanges = [
'\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]' , // unicode emoji from https://www.regextester.com/106421
':.{1,40}:' , // custom emoji
' |\n' // allow spaces and line breaks
] . join ( '|' ) ;
const removeAllEmoji = str => str . replace ( new RegExp ( emojiRanges , 'g' ) , '' ) ;
const isOnlyEmoji = str => ! removeAllEmoji ( str ) . length ;
const removeOneEmoji = str => str . replace ( new RegExp ( emojiRanges ) , '' ) ;
const emojiCount = ( str ) => {
let oldLength = 0 ;
let counter = 0 ;
while ( oldLength !== str . length ) {
oldLength = str . length ;
str = removeOneEmoji ( str ) ;
if ( oldLength !== str . length ) {
counter += 1 ;
}
}
return counter ;
} ;
2019-04-26 21:13:07 +00:00
const Markdown = React . memo ( ( {
2019-05-20 20:43:50 +00:00
msg , style , rules , baseUrl , username , isEdited , numberOfLines , mentions , channels , getCustomEmoji , useMarkdown = true
2019-04-26 21:13:07 +00:00
} ) => {
if ( ! msg ) {
return null ;
2018-04-24 19:34:03 +00:00
}
2019-04-26 21:13:07 +00:00
let m = formatText ( msg ) ;
if ( m ) {
m = emojify ( m , { output : 'unicode' } ) ;
}
2019-06-27 17:49:44 +00:00
const matched = m . match ( /^\[([^\]]*)\]\(([^)]*)\)\1/ ) ;
if ( matched && matched [ 0 ] !== msg ) {
m = m . replace ( /^\[([^\]]*)\]\(([^)]*)\)/ , '' ) . trim ( ) ;
}
2019-04-26 21:13:07 +00:00
if ( numberOfLines > 0 ) {
m = m . replace ( /[\n]+/g , '\n' ) . trim ( ) ;
}
2019-05-20 20:43:50 +00:00
if ( ! useMarkdown ) {
2019-05-21 12:12:15 +00:00
return < Text style = { styles . text } numberOfLines = { numberOfLines } > { m } < / T e x t > ;
2019-05-20 20:43:50 +00:00
}
2019-06-27 16:35:45 +00:00
const isMessageContainsOnlyEmoji = isOnlyEmoji ( m ) && emojiCount ( m ) <= 3 ;
2019-04-26 21:13:07 +00:00
return (
< MarkdownRenderer
rules = { {
paragraph : ( node , children ) => (
< Text key = { node . key } style = { styles . paragraph } numberOfLines = { numberOfLines } >
{ children }
2019-05-20 20:43:50 +00:00
{ isEdited ? < Text style = { styles . edited } > ( { I18n . t ( 'edited' ) } ) < / T e x t > : n u l l }
2019-04-26 21:13:07 +00:00
< / T e x t >
) ,
mention : ( node ) => {
const { content , key } = node ;
let mentionStyle = styles . mention ;
if ( content === 'all' || content === 'here' ) {
mentionStyle = {
... mentionStyle ,
... styles . mentionAll
} ;
} else if ( content === username ) {
mentionStyle = {
... mentionStyle ,
... styles . mentionLoggedUser
} ;
}
2019-05-20 20:43:50 +00:00
if ( mentions && mentions . length && mentions . findIndex ( mention => mention . username === content ) !== - 1 ) {
return (
< Text style = { mentionStyle } key = { key } >
& nbsp ; { content } & nbsp ;
< / T e x t >
) ;
}
return ` @ ${ content } ` ;
} ,
hashtag : ( node ) => {
const { content , key } = node ;
if ( channels && channels . length && channels . findIndex ( channel => channel . name === content ) !== - 1 ) {
return (
< Text key = { key } style = { styles . mention } >
& nbsp ; # { content } & nbsp ;
< / T e x t >
) ;
}
return ` # ${ content } ` ;
2019-04-26 21:13:07 +00:00
} ,
emoji : ( node ) => {
if ( node . children && node . children . length && node . children [ 0 ] . content ) {
const { content } = node . children [ 0 ] ;
2019-05-20 20:43:50 +00:00
const emoji = getCustomEmoji && getCustomEmoji ( content ) ;
if ( emoji ) {
2019-06-27 16:35:45 +00:00
return (
< CustomEmoji
key = { node . key }
baseUrl = { baseUrl }
style = { isMessageContainsOnlyEmoji ? styles . customEmojiBig : styles . customEmoji }
emoji = { emoji }
/ >
) ;
2018-04-24 19:34:03 +00:00
}
2019-04-26 21:13:07 +00:00
return < Text key = { node . key } > : { content } : < / T e x t > ;
}
return null ;
} ,
hardbreak : ( ) => null ,
blocklink : ( ) => null ,
image : node => (
< Image key = { node . key } style = { styles . inlineImage } source = { { uri : node . attributes . src } } / >
) ,
... rules
} }
style = { {
paragraph : styles . paragraph ,
2019-06-27 16:35:45 +00:00
text : isMessageContainsOnlyEmoji ? styles . textBig : styles . text ,
2019-04-26 21:13:07 +00:00
codeInline : styles . codeInline ,
codeBlock : styles . codeBlock ,
link : styles . link ,
... style
} }
2019-05-20 20:43:50 +00:00
plugins = { plugins }
2019-04-26 21:13:07 +00:00
> { m }
< / M a r k d o w n R e n d e r e r >
) ;
} , ( prevProps , nextProps ) => prevProps . msg === nextProps . msg ) ;
2017-12-02 13:19:58 +00:00
Markdown . propTypes = {
2018-04-24 19:34:03 +00:00
msg : PropTypes . string ,
2019-05-20 20:43:50 +00:00
username : PropTypes . string ,
baseUrl : PropTypes . string ,
2018-05-29 17:09:20 +00:00
style : PropTypes . any ,
2018-09-11 16:32:52 +00:00
rules : PropTypes . object ,
2019-05-20 20:43:50 +00:00
isEdited : PropTypes . bool ,
numberOfLines : PropTypes . number ,
useMarkdown : PropTypes . bool ,
mentions : PropTypes . oneOfType ( [ PropTypes . array , PropTypes . object ] ) ,
channels : PropTypes . oneOfType ( [ PropTypes . array , PropTypes . object ] ) ,
getCustomEmoji : PropTypes . func
2017-12-14 18:12:38 +00:00
} ;
2019-05-20 20:43:50 +00:00
Markdown . displayName = 'MessageMarkdown' ;
2019-04-26 21:13:07 +00:00
export default Markdown ;