diff --git a/app/components/post/post.js b/app/components/post/post.js
index af3b9c9c7..c9848589d 100644
--- a/app/components/post/post.js
+++ b/app/components/post/post.js
@@ -18,6 +18,7 @@ import {injectIntl, intlShape} from 'react-intl';
import Icon from 'react-native-vector-icons/Ionicons';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
+import {NavigationTypes} from 'app/constants';
import FileAttachmentList from 'app/components/file_attachment_list';
import FormattedText from 'app/components/formatted_text';
import FormattedTime from 'app/components/formatted_time';
@@ -26,7 +27,7 @@ import Markdown from 'app/components/markdown';
import OptionsContext from 'app/components/options_context';
import ProfilePicture from 'app/components/profile_picture';
import ReplyIcon from 'app/components/reply_icon';
-import {NavigationTypes} from 'app/constants';
+import SlackAttachments from 'app/components/slack_attachments';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
@@ -314,6 +315,30 @@ class Post extends PureComponent {
return attachments;
}
+ renderSlackAttachments = (baseStyle) => {
+ const {post, theme} = this.props;
+
+ if (post.props) {
+ const {attachments} = post.props;
+ const textStyles = getMarkdownTextStyles(theme);
+ const blockStyles = getMarkdownBlockStyles(theme);
+
+ if (attachments && attachments.length) {
+ return (
+
+ );
+ }
+ }
+
+ return null;
+ };
+
renderMessage = (style, messageStyle, replyBar = false) => {
const {formatMessage} = this.props.intl;
const {isFlagged, post, theme} = this.props;
@@ -381,6 +406,7 @@ class Post extends PureComponent {
toggleSelected={this.toggleSelected}
>
{message}
+ {this.renderSlackAttachments(messageStyle)}
{this.renderFileAttachments()}
@@ -419,6 +445,7 @@ class Post extends PureComponent {
actions={actions}
cancelText={formatMessage({id: 'channel_modal.cancel', defaultMessage: 'Cancel'})}
/>
+ {this.renderSlackAttachments(messageStyle)}
{this.renderFileAttachments()}
{post.failed &&
@@ -522,7 +549,7 @@ class Post extends PureComponent {
);
- messageStyle = [style.message, style.webhookMessage];
+ messageStyle = style.message;
} else {
profilePicture = (
@@ -702,11 +729,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
systemMessage: {
opacity: 0.6
},
- webhookMessage: {
- color: theme.centerChannelColor,
- fontSize: 16,
- fontWeight: '600'
- },
selected: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1)
},
diff --git a/app/components/slack_attachments/index.js b/app/components/slack_attachments/index.js
new file mode 100644
index 000000000..2c82cd629
--- /dev/null
+++ b/app/components/slack_attachments/index.js
@@ -0,0 +1,44 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import {View} from 'react-native';
+import PropTypes from 'prop-types';
+
+import CustomPropTypes from 'app/constants/custom_prop_types';
+
+import SlackAttachment from './slack_attachment';
+
+export default class SlackAttachments extends PureComponent {
+ static propTypes = {
+ attachments: PropTypes.array.isRequired,
+ baseTextStyle: CustomPropTypes.Style,
+ blockStyles: PropTypes.object,
+ textStyles: PropTypes.object,
+ theme: PropTypes.object
+ };
+
+ render() {
+ const {attachments, baseTextStyle, blockStyles, textStyles, theme} = this.props;
+ const content = [];
+
+ attachments.forEach((attachment, i) => {
+ content.push(
+
+ );
+ });
+
+ return (
+
+ {content}
+
+ );
+ }
+}
diff --git a/app/components/slack_attachments/slack_attachment.js b/app/components/slack_attachments/slack_attachment.js
new file mode 100644
index 000000000..1e4cc10dc
--- /dev/null
+++ b/app/components/slack_attachments/slack_attachment.js
@@ -0,0 +1,403 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {
+ Image,
+ Linking,
+ StyleSheet,
+ Text,
+ View
+} from 'react-native';
+
+import CustomPropTypes from 'app/constants/custom_prop_types';
+import Markdown from 'app/components/markdown';
+import FormattedText from 'app/components/formatted_text';
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+export default class SlackAttachment extends PureComponent {
+ static propTypes = {
+ attachment: PropTypes.object.isRequired,
+ baseTextStyle: CustomPropTypes.Style,
+ blockStyles: PropTypes.object,
+ textStyles: PropTypes.object,
+ theme: PropTypes.object
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.state = this.getInitState();
+ }
+
+ getCollapsedText = () => {
+ let text = this.props.attachment.text || '';
+ if ((text.match(/\n/g) || []).length >= 5) {
+ text = text.split('\n').splice(0, 5).join('\n');
+ } else if (text.length > 400) {
+ text = text.substr(0, 400);
+ }
+
+ return text;
+ };
+
+ getInitState = () => {
+ const shouldCollapse = this.shouldCollapse();
+ const uncollapsedText = this.props.attachment.text;
+ const collapsedText = shouldCollapse ? this.getCollapsedText() : uncollapsedText;
+
+ return {
+ shouldCollapse,
+ collapsedText,
+ uncollapsedText,
+ text: shouldCollapse ? collapsedText : uncollapsedText,
+ collapsed: shouldCollapse
+ };
+ };
+
+ getFieldsTable = (style) => {
+ const {
+ attachment,
+ baseTextStyle,
+ blockStyles,
+ textStyles
+ } = this.props;
+ const fields = attachment.fields;
+ if (!fields || !fields.length) {
+ return null;
+ }
+
+ const fieldTables = [];
+
+ let fieldInfos = [];
+ let rowPos = 0;
+ let lastWasLong = false;
+ let nrTables = 0;
+
+ fields.forEach((field, i) => {
+ if (rowPos === 2 || !(field.short === true) || lastWasLong) {
+ fieldTables.push(
+
+ {fieldInfos}
+
+ );
+ fieldInfos = [];
+ rowPos = 0;
+ nrTables += 1;
+ lastWasLong = false;
+ }
+
+ fieldInfos.push(
+
+
+
+
+ {field.title}
+
+
+
+
+
+
+
+ );
+
+ rowPos += 1;
+ lastWasLong = !(field.short === true);
+ });
+
+ if (fieldInfos.length > 0) { // Flush last fields
+ fieldTables.push(
+
+ {fieldInfos}
+
+ );
+ }
+
+ return (
+
+ {fieldTables}
+
+ );
+ };
+
+ openLink = (link) => {
+ if (Linking.canOpenURL(link)) {
+ Linking.openURL(link);
+ }
+ };
+
+ shouldCollapse = () => {
+ const text = this.props.attachment.text || '';
+ return (text.match(/\n/g) || []).length >= 5 || text.length > 400;
+ };
+
+ toggleCollapseState = () => {
+ const state = this.state;
+ const text = state.collapsed ? state.uncollapsedText : state.collapsedText;
+ const collapsed = !state.collapsed;
+
+ this.setState({collapsed, text});
+ };
+
+ render() {
+ const {
+ attachment,
+ baseTextStyle,
+ blockStyles,
+ textStyles,
+ theme
+ } = this.props;
+
+ const style = getStyleSheet(theme);
+
+ let preText;
+ if (attachment.pretext) {
+ preText = (
+
+
+
+ );
+ }
+
+ let borderStyle;
+ if (attachment.color && attachment.color[0] === '#') {
+ borderStyle = {borderLeftColor: attachment.color};
+ }
+
+ const author = [];
+ if (attachment.author_name || attachment.author_icon) {
+ if (attachment.author_icon) {
+ author.push(
+
+ );
+ }
+ if (attachment.author_name) {
+ let link;
+ let linkStyle;
+ if (attachment.author_link) {
+ link = () => this.openLink(attachment.author_link);
+ linkStyle = style.authorLink;
+ }
+ author.push(
+
+ {attachment.author_name}
+
+ );
+ }
+ }
+
+ let title;
+ let titleStyle;
+ if (attachment.title) {
+ let titleLink;
+ if (attachment.title_link) {
+ titleStyle = style.titleLink;
+ titleLink = () => this.openLink(attachment.title_link);
+ }
+
+ title = (
+
+ {attachment.title}
+
+ );
+ }
+
+ let thumb;
+ let topStyle;
+ if (attachment.thumb_url) {
+ topStyle = style.topContent;
+ thumb = (
+
+
+
+ );
+ }
+
+ let text;
+ if (attachment.text) {
+ let moreLess;
+ if (this.state.shouldCollapse) {
+ if (this.state.collapsed) {
+ moreLess = (
+
+ );
+ } else {
+ moreLess = (
+
+ );
+ }
+ }
+
+ text = (
+
+
+ {moreLess}
+
+ );
+ }
+
+ const fields = this.getFieldsTable(style);
+
+ let image;
+ if (attachment.image_url) {
+ image = (
+
+
+
+ );
+ }
+
+ return (
+
+ {preText}
+
+
+ {author}
+
+
+ {title}
+
+ {thumb}
+ {text}
+ {fields}
+ {image}
+
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme) => {
+ return StyleSheet.create({
+ container: {
+ borderColor: changeOpacity(theme.centerChannelColor, 0.15),
+ borderWidth: 1,
+ flex: 1,
+ paddingHorizontal: 10,
+ paddingVertical: 7
+ },
+ border: {
+ borderLeftColor: changeOpacity(theme.linkColor, 0.6),
+ borderLeftWidth: 3
+ },
+ author: {
+ color: changeOpacity(theme.centerChannelColor, 0.5),
+ fontSize: 11
+ },
+ authorIcon: {
+ height: 12,
+ marginRight: 3,
+ width: 12
+ },
+ authorLink: {
+ color: changeOpacity(theme.linkColor, 0.5)
+ },
+ title: {
+ color: theme.centerChannelColor,
+ fontWeight: '600',
+ marginVertical: 5
+ },
+ titleLink: {
+ color: theme.linkColor
+ },
+ topContent: {
+ paddingRight: 60
+ },
+ thumbContainer: {
+ position: 'absolute',
+ right: 10,
+ top: 10
+ },
+ thumb: {
+ height: 45,
+ width: 45
+ },
+ moreLess: {
+ color: theme.linkColor,
+ fontSize: 12
+ },
+ headingContainer: {
+ flex: 1,
+ flexDirection: 'row',
+ marginBottom: 5,
+ marginTop: 10
+ },
+ heading: {
+ fontWeight: '600'
+ },
+ bodyContainer: {
+ flex: 1
+ },
+ imageContainer: {
+ borderColor: changeOpacity(theme.centerChannelColor, 0.1),
+ borderWidth: 1,
+ borderRadius: 2,
+ marginTop: 5
+ },
+ image: {
+ flex: 1,
+ height: 50
+ }
+ });
+});