diff --git a/app/components/post_body/__snapshots__/system_message_helpers.test.js.snap b/app/components/post_body/__snapshots__/system_message_helpers.test.js.snap
new file mode 100644
index 000000000..e864ae21a
--- /dev/null
+++ b/app/components/post_body/__snapshots__/system_message_helpers.test.js.snap
@@ -0,0 +1,37 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renderSystemMessage uses renderer for Channel Display Name update 1`] = `
+
+`;
+
+exports[`renderSystemMessage uses renderer for Channel Header update 1`] = `
+
+`;
+
+exports[`renderSystemMessage uses renderer for Channel Purpose update 1`] = `
+
+`;
+
+exports[`renderSystemMessage uses renderer for archived channel 1`] = `
+
+`;
diff --git a/app/components/post_body/post_body.js b/app/components/post_body/post_body.js
index b891ee324..b49a69f92 100644
--- a/app/components/post_body/post_body.js
+++ b/app/components/post_body/post_body.js
@@ -13,6 +13,7 @@ import Icon from 'react-native-vector-icons/Ionicons';
import {Posts} from 'mattermost-redux/constants';
import CombinedSystemMessage from 'app/components/combined_system_message';
+import {renderSystemMessage} from './system_message_helpers';
import FormattedText from 'app/components/formatted_text';
import Markdown from 'app/components/markdown';
import MarkdownEmoji from 'app/components/markdown/markdown_emoji';
@@ -348,6 +349,13 @@ export default class PostBody extends PureComponent {
const messageStyle = isSystemMessage ? [style.message, style.systemMessage] : style.message;
const isPendingOrFailedPost = isPending || isFailed;
+ const messageStyles = {messageStyle, textStyles};
+ const intl = this.context.intl;
+ const systemMessage = renderSystemMessage(this.props, messageStyles, intl);
+ if (systemMessage) {
+ return systemMessage;
+ }
+
let body;
let messageComponent;
if (hasBeenDeleted) {
diff --git a/app/components/post_body/post_body.test.js b/app/components/post_body/post_body.test.js
index 3c299f9ec..c8cb6d92a 100644
--- a/app/components/post_body/post_body.test.js
+++ b/app/components/post_body/post_body.test.js
@@ -9,6 +9,9 @@ import PostBodyAdditionalContent from 'app/components/post_body_additional_conte
import {shallowWithIntl} from 'test/intl-test-helper';
import PostBody from './post_body.js';
+import * as SystemMessageHelpers from './system_message_helpers';
+
+jest.mock('./system_message_helpers');
describe('PostBody', () => {
const baseProps = {
@@ -122,4 +125,9 @@ describe('PostBody', () => {
instance.measurePost(event);
expect(wrapper.state('isLongPost')).toEqual(false);
});
+
+ test('should return system message as post body', () => {
+ shallowWithIntl();
+ expect(SystemMessageHelpers.renderSystemMessage.mock.calls.length).toBe(1);
+ });
});
diff --git a/app/components/post_body/system_message_helpers.js b/app/components/post_body/system_message_helpers.js
new file mode 100644
index 000000000..2a9c2f569
--- /dev/null
+++ b/app/components/post_body/system_message_helpers.js
@@ -0,0 +1,160 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {Posts} from 'mattermost-redux/constants';
+import Markdown from 'app/components/markdown';
+import {t} from 'app/utils/i18n';
+
+const renderUsername = (value) => {
+ return (value[0] === '@') ? value : `@${value}`;
+};
+
+const renderMessage = (postBodyProps, styles, intl, localeHolder, values) => {
+ const {onPress} = postBodyProps;
+ const {messageStyle, textStyles} = styles;
+
+ return (
+
+ );
+};
+
+const renderHeaderChangeMessage = (postBodyProps, styles, intl) => {
+ const {postProps} = postBodyProps;
+ let values;
+
+ if (!postProps.username) {
+ return null;
+ }
+
+ const username = renderUsername(postProps.username);
+ const oldHeader = postProps.old_header;
+ const newHeader = postProps.new_header;
+ let localeHolder;
+
+ if (postProps.new_header) {
+ if (postProps.old_header) {
+ localeHolder = {
+ id: t('mobile.system_message.update_channel_header_message_and_forget.updated_from'),
+ defaultMessage: '{username} updated the channel header from: {oldHeader} to: {newHeader}',
+ };
+
+ values = {username, oldHeader, newHeader};
+ return renderMessage(postBodyProps, styles, intl, localeHolder, values);
+ }
+
+ localeHolder = {
+ id: t('mobile.system_message.update_channel_header_message_and_forget.updated_to'),
+ defaultMessage: '{username} updated the channel header to: {newHeader}',
+ };
+
+ values = {username, oldHeader, newHeader};
+ return renderMessage(postBodyProps, styles, intl, localeHolder, values);
+ } else if (postProps.old_header) {
+ localeHolder = {
+ id: t('mobile.system_message.update_channel_header_message_and_forget.removed'),
+ defaultMessage: '{username} removed the channel header (was: {oldHeader})',
+ };
+
+ values = {username, oldHeader, newHeader};
+ return renderMessage(postBodyProps, styles, intl, localeHolder, values);
+ }
+
+ return null;
+};
+
+const renderPurposeChangeMessage = (postBodyProps, styles, intl) => {
+ const {postProps} = postBodyProps;
+ let values;
+
+ if (!postProps.username) {
+ return null;
+ }
+
+ const username = renderUsername(postProps.username);
+ const oldPurpose = postProps.old_purpose;
+ const newPurpose = postProps.new_purpose;
+ let localeHolder;
+
+ if (postProps.new_purpose) {
+ if (postProps.old_purpose) {
+ localeHolder = {
+ id: t('mobile.system_message.update_channel_purpose_message.updated_from'),
+ defaultMessage: '{username} updated the channel purpose from: {oldPurpose} to: {newPurpose}',
+ };
+
+ values = {username, oldPurpose, newPurpose};
+ return renderMessage(postBodyProps, styles, intl, localeHolder, values);
+ }
+
+ localeHolder = {
+ id: t('mobile.system_message.update_channel_purpose_message.updated_to'),
+ defaultMessage: '{username} updated the channel purpose to: {newPurpose}',
+ };
+
+ values = {username, oldPurpose, newPurpose};
+ return renderMessage(postBodyProps, styles, intl, localeHolder, values);
+ } else if (postProps.old_purpose) {
+ localeHolder = {
+ id: t('mobile.system_message.update_channel_purpose_message.removed'),
+ defaultMessage: '{username} removed the channel purpose (was: {oldPurpose})',
+ };
+
+ values = {username, oldPurpose, newPurpose};
+ return renderMessage(postBodyProps, styles, intl, localeHolder, values);
+ }
+
+ return null;
+};
+
+const renderDisplayNameChangeMessage = (postBodyProps, styles, intl) => {
+ const {postProps} = postBodyProps;
+ const oldDisplayName = postProps.old_displayname;
+ const newDisplayName = postProps.new_displayname;
+
+ if (!(postProps.username && postProps.old_displayname && postProps.new_displayname)) {
+ return null;
+ }
+
+ const username = renderUsername(postProps.username);
+ const localeHolder = {
+ id: t('mobile.system_message.update_channel_displayname_message_and_forget.updated_from'),
+ defaultMessage: '{username} updated the channel display name from: {oldDisplayName} to: {newDisplayName}',
+ };
+
+ const values = {username, oldDisplayName, newDisplayName};
+ return renderMessage(postBodyProps, styles, intl, localeHolder, values);
+};
+
+const renderArchivedMessage = (postBodyProps, styles, intl) => {
+ const {postProps} = postBodyProps;
+
+ const username = renderUsername(postProps.username);
+ const localeHolder = {
+ id: t('mobile.system_message.channel_archived_message'),
+ defaultMessage: '{username} archived the channel',
+ };
+
+ const values = {username};
+ return renderMessage(postBodyProps, styles, intl, localeHolder, values);
+};
+
+const systemMessageRenderers = {
+ [Posts.POST_TYPES.HEADER_CHANGE]: renderHeaderChangeMessage,
+ [Posts.POST_TYPES.DISPLAYNAME_CHANGE]: renderDisplayNameChangeMessage,
+ [Posts.POST_TYPES.PURPOSE_CHANGE]: renderPurposeChangeMessage,
+ [Posts.POST_TYPES.CHANNEL_DELETED]: renderArchivedMessage,
+};
+
+export const renderSystemMessage = (postBodyProps, styles, intl) => {
+ const renderer = systemMessageRenderers[postBodyProps.postType];
+ if (!renderer) {
+ return null;
+ }
+ return renderer(postBodyProps, styles, intl);
+};
diff --git a/app/components/post_body/system_message_helpers.test.js b/app/components/post_body/system_message_helpers.test.js
new file mode 100644
index 000000000..e533edda0
--- /dev/null
+++ b/app/components/post_body/system_message_helpers.test.js
@@ -0,0 +1,90 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import * as SystemMessageHelpers from './system_message_helpers';
+import {Posts} from 'mattermost-redux/constants';
+
+const basePostBodyProps = {
+ postProps: {
+ username: 'username',
+ },
+ onPress: jest.fn(),
+};
+
+const mockStyles = {
+ messageStyle: {},
+ textStyles: {},
+};
+
+const mockIntl = {
+ formatMessage: ({defaultMessage}) => defaultMessage,
+};
+
+describe('renderSystemMessage', () => {
+ test('uses renderer for Channel Header update', () => {
+ const postBodyProps = {
+ ...basePostBodyProps,
+ postProps: {
+ ...basePostBodyProps.postProps,
+ old_header: 'old header',
+ new_header: 'new header',
+ },
+ postType: Posts.POST_TYPES.HEADER_CHANGE,
+ };
+ const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
+ expect(renderedMessage).toMatchSnapshot();
+ });
+
+ test('uses renderer for Channel Display Name update', () => {
+ const postBodyProps = {
+ ...basePostBodyProps,
+ postProps: {
+ ...basePostBodyProps.postProps,
+ old_displayname: 'old displayname',
+ new_displayname: 'new displayname',
+ },
+ postType: Posts.POST_TYPES.DISPLAYNAME_CHANGE,
+ };
+
+ const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
+ expect(renderedMessage).toMatchSnapshot();
+ });
+
+ test('uses renderer for Channel Purpose update', () => {
+ const postBodyProps = {
+ ...basePostBodyProps,
+ postProps: {
+ ...basePostBodyProps.postProps,
+ old_purpose: 'old purpose',
+ new_purpose: 'new purpose',
+ },
+ postType: Posts.POST_TYPES.PURPOSE_CHANGE,
+ };
+
+ const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
+ expect(renderedMessage).toMatchSnapshot();
+ });
+
+ test('uses renderer for archived channel', () => {
+ const postBodyProps = {
+ ...basePostBodyProps,
+ postProps: {
+ ...basePostBodyProps.postProps,
+ },
+ postType: Posts.POST_TYPES.CHANNEL_DELETED,
+ };
+
+ const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
+ expect(renderedMessage).toMatchSnapshot();
+ });
+
+ test('is null for non-qualifying system messages', () => {
+ const postBodyProps = {
+ ...basePostBodyProps,
+ postType: 'not_relevant',
+ };
+
+ const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
+ expect(renderedMessage).toBeNull();
+ });
+});
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index bf85fdfef..061d6bc87 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -463,6 +463,14 @@
"mobile.storage_permission_denied_description": "Upload files to your Mattermost instance. Open Settings to grant Mattermost Read and Write access to files on this device.",
"mobile.storage_permission_denied_title": "{applicationName} would like to access your files",
"mobile.suggestion.members": "Members",
+ "mobile.system_message.channel_archived_message": "{username} archived the channel",
+ "mobile.system_message.update_channel_displayname_message_and_forget.updated_from": "{username} updated the channel display name from: {oldDisplayName} to: {newDisplayName}",
+ "mobile.system_message.update_channel_header_message_and_forget.removed": "{username} removed the channel header (was: {oldHeader})",
+ "mobile.system_message.update_channel_header_message_and_forget.updated_from": "{username} updated the channel header from: {oldHeader} to: {newHeader}",
+ "mobile.system_message.update_channel_header_message_and_forget.updated_to": "{username} updated the channel header to: {newHeader}",
+ "mobile.system_message.update_channel_purpose_message.removed": "{username} removed the channel purpose (was: {oldPurpose})",
+ "mobile.system_message.update_channel_purpose_message.updated_from": "{username} updated the channel purpose from: {oldPurpose} to: {newPurpose}",
+ "mobile.system_message.update_channel_purpose_message.updated_to": "{username} updated the channel purpose to: {newPurpose}",
"mobile.terms_of_service.alert_cancel": "Cancel",
"mobile.terms_of_service.alert_ok": "OK",
"mobile.terms_of_service.alert_retry": "Try Again",