diff --git a/app/components/formatted_text.js b/app/components/formatted_text.js index a84ec74e8..299edf0f9 100644 --- a/app/components/formatted_text.js +++ b/app/components/formatted_text.js @@ -1,7 +1,7 @@ // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import React, {Component} from 'react'; +import {Component, createElement, isValidElement} from 'react'; import PropTypes from 'prop-types'; import {Text} from 'react-native'; import {injectIntl, intlShape} from 'react-intl'; @@ -27,11 +27,67 @@ class FormattedText extends Component { ...props } = this.props; - return ( - - {intl.formatMessage({id, defaultMessage}, values)} - - ); + let tokenDelimiter; + let tokenizedValues; + let elements; + const hasValues = values && Object.keys(values).length > 0; + if (hasValues) { + // Creates a token with a random UID that should not be guessable or + // conflict with other parts of the `message` string. + const uid = Math.floor(Math.random() * 0x10000000000).toString(16); + + const generateToken = (() => { + let counter = 0; + return () => { + const elementId = `ELEMENT-${uid}-${counter += 1}`; + return elementId; + }; + })(); + + // Splitting with a delimiter to support IE8. When using a regex + // with a capture group IE8 does not include the capture group in + // the resulting array. + tokenDelimiter = `@__${uid}__@`; + tokenizedValues = {}; + elements = {}; + + // Iterates over the `props` to keep track of any React Element + // values so they can be represented by the `token` as a placeholder + // when the `message` is formatted. This allows the formatted + // message to then be broken-up into parts with references to the + // React Elements inserted back in. + Object.keys(values).forEach((name) => { + const value = values[name]; + + if (isValidElement(value)) { + const token = generateToken(); + tokenizedValues[name] = tokenDelimiter + token + tokenDelimiter; + elements[token] = value; + } else { + tokenizedValues[name] = value; + } + }); + } + + const descriptor = {id, defaultMessage}; + const formattedMessage = intl.formatMessage(descriptor, tokenizedValues || values); + const hasElements = elements && Object.keys(elements).length > 0; + + let nodes; + if (hasElements) { + // Split the message into parts so the React Element values captured + // above can be inserted back into the rendered message. This + // approach allows messages to render with React Elements while + // keeping React's virtual diffing working properly. + nodes = formattedMessage. + split(tokenDelimiter). + filter((part) => Boolean(part)). + map((part) => elements[part] || part); + } else { + nodes = [formattedMessage]; + } + + return createElement(Text, props, ...nodes); } } diff --git a/app/screens/about/about.js b/app/screens/about/about.js index ad7f533bb..9e432256e 100644 --- a/app/screens/about/about.js +++ b/app/screens/about/about.js @@ -18,6 +18,7 @@ import StatusBar from 'app/components/status_bar'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import MattermostIcon from 'app/components/mattermost_icon'; +import Config from 'assets/config'; export default class About extends PureComponent { static propTypes = { @@ -34,6 +35,14 @@ export default class About extends PureComponent { Linking.openURL('http://about.mattermost.com/'); }; + handlePlatformNotice = () => { + Linking.openURL(Config.PlatformNoticeURL); + }; + + handleMobileNotice = () => { + Linking.openURL(Config.MobileNoticeURL); + }; + render() { const {theme, config, license} = this.props; const style = getStyleSheet(theme); @@ -206,6 +215,33 @@ export default class About extends PureComponent { currentYear: new Date().getFullYear() }} /> + + + + ), + mobile: ( + + ) + }} + /> + + { flexDirection: 'row', marginVertical: 20 }, + noticeContainer: { + flex: 1, + flexDirection: 'column', + marginTop: 10 + }, + noticeLink: { + color: theme.linkColor, + fontSize: 11, + lineHeight: 13 + }, hashContainer: { flex: 1, flexDirection: 'column', diff --git a/assets/base/config.json b/assets/base/config.json index 72164bbdc..b897f9757 100644 --- a/assets/base/config.json +++ b/assets/base/config.json @@ -3,5 +3,7 @@ "TestServerUrl": "http://localhost:8065", "DefaultTheme": "default", "ShowErrorsList": false, - "MinServerVersion": "3.10.0" + "MinServerVersion": "3.10.0", + "PlatformNoticeURL": "https://about.mattermost.com/platform-notice-txt/", + "MobileNoticeURL": "https://about.mattermost.com/mobile-notice-txt/" } diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 99eb35bbe..642fb1504 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -1768,6 +1768,9 @@ "mobile.login_options.choose_title": "Choose your login method", "mobile.more_dms.start": "Start", "mobile.more_dms.title": "New Conversation", + "mobile.notice_text": "Mattermost is made possible by the open source software in our {platform} and {mobile}.", + "mobile.notice_mobile_link": "mobile apps", + "mobile.notice_platform_link": "platform", "mobile.notification.in": " in ", "mobile.offlineIndicator.connected": "Connected", "mobile.offlineIndicator.connecting": "Connecting...",