// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React from 'react'; import {Text} from 'react-native'; import PropTypes from 'prop-types'; import Renderer from 'commonmark-react-renderer'; import {Parser} from 'commonmark'; import {injectIntl, intlShape} from 'react-intl'; import {concatStyles, changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {getMarkdownTextStyles} from 'app/utils/markdown'; import CustomPropTypes from 'app/constants/custom_prop_types'; import AtMention from 'app/components/at_mention'; import MarkdownLink from 'app/components/markdown/markdown_link'; const TARGET_BLANK_URL_PREFIX = '!'; /* * Translations component with a similar API to component except the message string * accepts markdown. It supports the following non-block-level markdown: * - *italic* * - **bold** * - `inline code` * - ~~strikethrough~~ * - [link](http://example.com/) * - line\nbreaks * * Note: Line breaks (\n) in a defaultMessage parameter string must be surrounded by curly brackets {} in JSX. Example: * */ class FormattedMarkdownText extends React.PureComponent { static propTypes = { baseTextStyle: CustomPropTypes.Style, defaultMessage: PropTypes.string.isRequired, id: PropTypes.string.isRequired, navigator: PropTypes.object.isRequired, onPostPress: PropTypes.func, style: CustomPropTypes.Style, textStyles: PropTypes.object, theme: PropTypes.object.isRequired, values: PropTypes.object, }; static contextTypes = { intl: intlShape, }; constructor(props) { super(props); this.parser = this.createParser(); this.renderer = this.createRenderer(); this.textStyles = null; this.baseTextStyle = null; } createParser = () => { return new Parser(); } createRenderer = () => { return new Renderer({ renderers: { text: this.renderText, emph: Renderer.forwardChildren, strong: Renderer.forwardChildren, code: this.renderCodeSpan, link: this.renderLink, hardBreak: this.renderBreak, softBreak: this.renderBreak, paragraph: this.renderParagraph, del: Renderer.forwardChildren, html_inline: this.renderHTML, html_block: this.renderHTML, atMention: this.renderAtMention, }, }); } computeTextStyle = (baseStyle, context) => { return concatStyles(baseStyle, context.map((type) => this.textStyles[type])); } renderText = ({context, literal}) => { const style = this.computeTextStyle(this.props.style || this.baseTextStyle, context); return {literal}; } renderCodeSpan = ({context, literal}) => { const style = this.computeTextStyle([this.baseTextStyle, this.textStyles.code], context); return {literal}; } renderLink = ({children, href}) => { var url = href[0] === TARGET_BLANK_URL_PREFIX ? href.substring(1, href.length) : href; return {children}; } renderBreak = () => { return {'\n'}; } renderParagraph = ({children}) => { return {children}; } renderHTML = (props) => { console.warn(`HTML used in FormattedMarkdownText component with id ${this.props.id}`); // eslint-disable-line no-console return this.renderText(props); } renderAtMention = ({context, mentionName}) => { return ( ); } render() { const {id, defaultMessage, values, theme} = this.props; const messageDescriptor = {id, defaultMessage}; const {formatMessage} = this.context.intl; const message = formatMessage(messageDescriptor, values); const ast = this.parser.parse(message); this.textStyles = getMarkdownTextStyles(theme); this.baseTextStyle = getStyleSheet(theme).message; return this.renderer.render(ast); } } const getStyleSheet = makeStyleSheetFromTheme((theme) => { return { message: { color: changeOpacity(theme.centerChannelColor, 0.8), fontSize: 15, lineHeight: 22, }, }; }); export default injectIntl(FormattedMarkdownText);