diff options
-rw-r--r-- | webapp/actions/global_actions.jsx | 2 | ||||
-rw-r--r-- | webapp/components/post_view/components/post_body.jsx | 18 | ||||
-rw-r--r-- | webapp/components/post_view/components/post_message_view.jsx | 57 | ||||
-rw-r--r-- | webapp/components/post_view/components/system_message_helpers.jsx | 228 | ||||
-rw-r--r-- | webapp/components/rhs_comment.jsx | 10 | ||||
-rw-r--r-- | webapp/components/rhs_root_post.jsx | 3 | ||||
-rw-r--r-- | webapp/i18n/en.json | 12 | ||||
-rw-r--r-- | webapp/i18n/fr.json | 12 | ||||
-rw-r--r-- | webapp/stores/notification_store.jsx | 3 | ||||
-rw-r--r-- | webapp/stores/post_store.jsx | 6 | ||||
-rw-r--r-- | webapp/utils/constants.jsx | 17 | ||||
-rw-r--r-- | webapp/utils/post_utils.jsx | 2 | ||||
-rw-r--r-- | webapp/utils/utils.jsx | 2 |
13 files changed, 321 insertions, 51 deletions
diff --git a/webapp/actions/global_actions.jsx b/webapp/actions/global_actions.jsx index 23e19f22f..c8da43f24 100644 --- a/webapp/actions/global_actions.jsx +++ b/webapp/actions/global_actions.jsx @@ -397,7 +397,7 @@ export function sendEphemeralPost(message, channelId) { user_id: '0', channel_id: channelId || ChannelStore.getCurrentId(), message, - type: Constants.POST_TYPE_EPHEMERAL, + type: Constants.PostTypes.EPHEMERAL, create_at: timestamp, update_at: timestamp, props: {} diff --git a/webapp/components/post_view/components/post_body.jsx b/webapp/components/post_view/components/post_body.jsx index e690b3702..8b650423f 100644 --- a/webapp/components/post_view/components/post_body.jsx +++ b/webapp/components/post_view/components/post_body.jsx @@ -156,22 +156,6 @@ export default class PostBody extends React.Component { ); } - let message; - if (this.props.post.state === Constants.POST_DELETED) { - message = ( - <p> - <FormattedMessage - id='post_body.deleted' - defaultMessage='(message deleted)' - /> - </p> - ); - } else { - message = ( - <PostMessageContainer post={this.props.post}/> - ); - } - const messageWrapper = ( <div key={`${post.id}_message`} @@ -179,7 +163,7 @@ export default class PostBody extends React.Component { className={postClass} > {loading} - {message} + <PostMessageContainer post={this.props.post}/> </div> ); diff --git a/webapp/components/post_view/components/post_message_view.jsx b/webapp/components/post_view/components/post_message_view.jsx index eff791aec..371dd64eb 100644 --- a/webapp/components/post_view/components/post_message_view.jsx +++ b/webapp/components/post_view/components/post_message_view.jsx @@ -4,9 +4,12 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; +import Constants from 'utils/constants.jsx'; +import * as PostUtils from 'utils/post_utils.jsx'; import * as TextFormatting from 'utils/text_formatting.jsx'; import * as Utils from 'utils/utils.jsx'; -import * as PostUtils from 'utils/post_utils.jsx'; + +import {renderSystemMessage} from './system_message_helpers.jsx'; export default class PostMessageView extends React.Component { static propTypes = { @@ -29,6 +32,14 @@ export default class PostMessageView extends React.Component { return true; } + if (nextProps.post.state !== this.props.post.state) { + return true; + } + + if (nextProps.post.type !== this.props.post.type) { + return true; + } + // emojis are immutable if (nextProps.emojis !== this.props.emojis) { return true; @@ -49,26 +60,43 @@ export default class PostMessageView extends React.Component { return false; } - editedIndicator() { + renderDeletedPost() { return ( - PostUtils.isEdited(this.props.post) ? - <span className='edited'> - <FormattedMessage - id='post_message_view.edited' - defaultMessage='(edited)' - /> - </span> : - '' + <p> + <FormattedMessage + id='post_body.deleted' + defaultMessage='(message deleted)' + /> + </p> + ); + } + + renderEditedIndicator() { + if (!PostUtils.isEdited(this.props.post)) { + return null; + } + + return ( + <span className='edited'> + <FormattedMessage + id='post_message_view.edited' + defaultMessage='(edited)' + /> + </span> ); } render() { + if (this.props.post.state === Constants.POST_DELETED) { + return this.renderDeletedPost(); + } + if (!this.props.enableFormatting) { return ( <span> {this.props.post.message} - {this.editedIndicator()} + {this.renderEditedIndicator()} </span> ); } @@ -82,13 +110,18 @@ export default class PostMessageView extends React.Component { team: this.props.team }); + const renderedSystemMessage = renderSystemMessage(this.props.post, options); + if (renderedSystemMessage) { + return <div>{renderedSystemMessage}</div>; + } + return ( <div> <span onClick={Utils.handleFormattedTextClick} dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message, options)}} /> - {this.editedIndicator()} + {this.renderEditedIndicator()} </div> ); } diff --git a/webapp/components/post_view/components/system_message_helpers.jsx b/webapp/components/post_view/components/system_message_helpers.jsx new file mode 100644 index 000000000..6f6454599 --- /dev/null +++ b/webapp/components/post_view/components/system_message_helpers.jsx @@ -0,0 +1,228 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import {FormattedMessage} from 'react-intl'; + +import {PostTypes} from 'utils/constants.jsx'; +import {formatText} from 'utils/text_formatting.jsx'; + +function renderFormattedText(value, options) { + return <span dangerouslySetInnerHTML={{__html: formatText(value, options)}}/>; +} + +function renderJoinChannelMessage(post, options) { + const username = renderFormattedText(post.props.username, options); + + return ( + <FormattedMessage + id='api.channel.join_channel.post_and_forget' + defaultMessage='{username} has joined the channel.' + values={{username}} + /> + ); +} + +function renderLeaveChannelMessage(post, options) { + const username = renderFormattedText(post.props.username, options); + + return ( + <FormattedMessage + id='api.channel.leave.left' + defaultMessage='{username} has left the channel.' + values={{username}} + /> + ); +} + +function renderAddToChannelMessage(post, options) { + const username = renderFormattedText(post.props.username, options); + const addedUsername = renderFormattedText(post.props.addedUsername, options); + + return ( + <FormattedMessage + id='api.channel.add_member.added' + defaultMessage='{addedUsername} added to the channel by {username}' + values={{ + username, + addedUsername + }} + /> + ); +} + +function renderRemoveFromChannelMessage(post, options) { + const removedUsername = renderFormattedText(post.props.removedUsername, options); + + return ( + <FormattedMessage + id='api.channel.remove_member.removed' + defaultMessage='{removedUsername} was removed from the channel' + values={{ + removedUsername + }} + /> + ); +} + +function renderHeaderChangeMessage(post, options) { + if (!post.props.username) { + return null; + } + + const headerOptions = { + ...options, + singleline: true + }; + + const username = renderFormattedText(post.props.username, options); + const oldHeader = post.props.old_header ? renderFormattedText(post.props.old_header, headerOptions) : null; + const newHeader = post.props.new_header ? renderFormattedText(post.props.new_header, headerOptions) : null; + + if (post.props.new_header) { + if (post.props.old_header) { + return ( + <FormattedMessage + id='api.channel.post_update_channel_header_message_and_forget.updated_from' + defaultMessage='{username} updated the channel header from: {old} to: {new}' + values={{ + username, + old: oldHeader, + new: newHeader + }} + /> + ); + } + + return ( + <FormattedMessage + id='api.channel.post_update_channel_header_message_and_forget.updated_to' + defaultMessage='{username} updated the channel header to: {new}' + values={{ + username, + new: newHeader + }} + /> + ); + } else if (post.props.old_header) { + return ( + <FormattedMessage + id='api.channel.post_update_channel_header_message_and_forget.removed' + defaultMessage='{username} removed the channel header (was: {old})' + values={{ + username, + old: oldHeader + }} + /> + ); + } + + return null; +} + +function renderDisplayNameChangeMessage(post, options) { + if (!(post.props.username && post.props.old_displayname && post.props.new_displayname)) { + return null; + } + + const username = renderFormattedText(post.props.username, options); + const oldDisplayName = post.props.old_displayname; + const newDisplayName = post.props.new_displayname; + + return ( + <FormattedMessage + id='api.channel.post_update_channel_displayname_message_and_forget.updated_from' + defaultMessage='{username} updated the channel display name from: {old} to: {new}' + values={{ + username, + old: oldDisplayName, + new: newDisplayName + }} + /> + ); +} + +function renderPurposeChangeMessage(post, options) { + if (!post.props.username) { + return null; + } + + const username = renderFormattedText(post.props.username, options); + const oldPurpose = post.props.old_purpose; + const newPurpose = post.props.new_purpose; + + if (post.props.new_purpose) { + if (post.props.old_purpose) { + return ( + <FormattedMessage + id='app.channel.post_update_channel_purpose_message.updated_from' + defaultMessage='{username} updated the channel purpose from: {old} to: {new}' + values={{ + username, + old: oldPurpose, + new: newPurpose + }} + /> + ); + } + + return ( + <FormattedMessage + id='app.channel.post_update_channel_purpose_message.updated_to' + defaultMessage='{username} updated the channel purpose to: {new}' + values={{ + username, + new: newPurpose + }} + /> + ); + } else if (post.props.old_purpose) { + return ( + <FormattedMessage + id='app.channel.post_update_channel_purpose_message.removed' + defaultMessage='{username} removed the channel purpose (was: {old})' + values={{ + username, + old: oldPurpose + }} + /> + ); + } + + return null; +} + +function renderChannelDeletedMessage(post, options) { + if (!post.props.username) { + return null; + } + + const username = renderFormattedText(post.props.username, options); + + return ( + <FormattedMessage + id='api.channel.delete_channel.archived' + defaultMessage='{username} has archived the channel.' + values={{username}} + /> + ); +} + +const systemMessageRenderers = { + [PostTypes.JOIN_CHANNEL]: renderJoinChannelMessage, + [PostTypes.LEAVE_CHANNEL]: renderLeaveChannelMessage, + [PostTypes.ADD_TO_CHANNEL]: renderAddToChannelMessage, + [PostTypes.REMOVE_FROM_CHANNEL]: renderRemoveFromChannelMessage, + [PostTypes.HEADER_CHANGE]: renderHeaderChangeMessage, + [PostTypes.DISPLAYNAME_CHANGE]: renderDisplayNameChangeMessage, + [PostTypes.PURPOSE_CHANGE]: renderPurposeChangeMessage, + [PostTypes.CHANNEL_DELETED]: renderChannelDeletedMessage +}; + +export function renderSystemMessage(post, options) { + if (!systemMessageRenderers[post.type]) { + return null; + } + + return systemMessageRenderers[post.type](post, options); +} diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx index 26659c7a1..0eb7717b3 100644 --- a/webapp/components/rhs_comment.jsx +++ b/webapp/components/rhs_comment.jsx @@ -294,7 +294,6 @@ export default class RhsComment extends React.Component { let loading; let postClass = ''; - let message = <PostMessageContainer post={post}/>; if (post.state === Constants.POST_FAILED) { postClass += ' post-fail'; @@ -307,13 +306,6 @@ export default class RhsComment extends React.Component { src={loadingGif} /> ); - } else if (this.props.post.state === Constants.POST_DELETED) { - message = ( - <FormattedMessage - id='post_body.deleted' - defaultMessage='(message deleted)' - /> - ); } let systemMessageClass = ''; @@ -491,7 +483,7 @@ export default class RhsComment extends React.Component { <div className='post__body'> <div className={postClass}> {loading} - {message} + <PostMessageContainer post={post}/> </div> {fileAttachment} <ReactionListContainer diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx index 7d00e2322..a114c7385 100644 --- a/webapp/components/rhs_root_post.jsx +++ b/webapp/components/rhs_root_post.jsx @@ -358,7 +358,6 @@ export default class RhsRootPost extends React.Component { } const profilePicContainer = (<div className='post__img'>{profilePic}</div>); - const messageWrapper = <PostMessageContainer post={post}/>; let flag; let flagFunc; @@ -445,7 +444,7 @@ export default class RhsRootPost extends React.Component { <div className='post__body'> <PostBodyAdditionalContent post={post} - message={messageWrapper} + message={<PostMessageContainer post={post}/>} previewCollapsed={this.props.previewCollapsed} /> {fileAttachment} diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 6aa994bf3..fd2a34f36 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -968,6 +968,18 @@ "analytics.team.title": "Team Statistics for {team}", "analytics.team.totalPosts": "Total Posts", "analytics.team.totalUsers": "Total Users", + "api.channel.add_member.added": "{addedUsername} added to the channel by {username}", + "api.channel.delete_channel.archived": "{username} has archived the channel.", + "api.channel.join_channel.post_and_forget": "{username} has joined the channel.", + "api.channel.leave.left": "{username} has left the channel.", + "api.channel.post_update_channel_displayname_message_and_forget.updated_from": "{username} updated the channel display name from: {old} to: {new}", + "api.channel.post_update_channel_header_message_and_forget.removed": "{username} removed the channel header (was: {old})", + "api.channel.post_update_channel_header_message_and_forget.updated_from": "{username} updated the channel header from: {old} to: {new}", + "api.channel.post_update_channel_header_message_and_forget.updated_to": "{username} updated the channel header to: {new}", + "api.channel.remove_member.removed": "{removedUsername} was removed from the channel", + "app.channel.post_update_channel_purpose_message.removed": "{username} removed the channel purpose (was: {old})", + "app.channel.post_update_channel_purpose_message.updated_from": "{username} updated the channel purpose from: {old} to: {new}", + "app.channel.post_update_channel_purpose_message.updated_to": "{username} updated the channel purpose to: {new}", "audit_table.accountActive": "Account made active", "audit_table.accountInactive": "Account made inactive", "audit_table.action": "Action", diff --git a/webapp/i18n/fr.json b/webapp/i18n/fr.json index b464930b9..80df30f58 100644 --- a/webapp/i18n/fr.json +++ b/webapp/i18n/fr.json @@ -968,6 +968,18 @@ "analytics.team.title": "Statistiques d'équipe pour {team}", "analytics.team.totalPosts": "Nombre de messages", "analytics.team.totalUsers": "Nombre d'utilisateurs", + "api.channel.add_member.added": "{addedUsername} a été ajouté au canal par {username}", + "api.channel.delete_channel.archived": "{username} a archivé le canal.", + "api.channel.join_channel.post_and_forget": "{username} a rejoint le canal.", + "api.channel.leave.left": "{username} a quitté le canal.", + "api.channel.post_update_channel_displayname_message_and_forget.updated_from": "{username} a mis à jour l'en-tête du canal de : {old} en : {new}", + "api.channel.post_update_channel_header_message_and_forget.removed": "{username} a supprimé le titre du canal (était : {old})", + "api.channel.post_update_channel_header_message_and_forget.updated_from": "{username} a mis à jour l'en-tête du canal de : {old} en : {new}", + "api.channel.post_update_channel_header_message_and_forget.updated_to": "{username} a mis à jour l'en-tête du canal en : {new}", + "api.channel.remove_member.removed": "{removedUsername} a été retiré du canal.", + "app.channel.post_update_channel_purpose_message.removed": "{username} a supprimé le titre du canal (était : {old})", + "app.channel.post_update_channel_purpose_message.updated_from": "{username} a mis à jour l'en-tête du canal de : {old} en : {new}", + "app.channel.post_update_channel_purpose_message.updated_to": "{username} a mis à jour l'en-tête du canal en : {new}", "audit_table.accountActive": "Compte activé", "audit_table.accountInactive": "Compte désactivé", "audit_table.action": "Action", diff --git a/webapp/stores/notification_store.jsx b/webapp/stores/notification_store.jsx index 4c89fe480..93d544cd1 100644 --- a/webapp/stores/notification_store.jsx +++ b/webapp/stores/notification_store.jsx @@ -6,7 +6,6 @@ import EventEmitter from 'events'; import Constants from 'utils/constants.jsx'; import UserStore from './user_store.jsx'; import ChannelStore from './channel_store.jsx'; -import PreferenceStore from './preference_store.jsx'; import * as UserAgent from 'utils/user_agent.jsx'; import * as Utils from 'utils/utils.jsx'; import * as PostUtils from 'utils/post_utils.jsx'; @@ -35,8 +34,6 @@ class NotificationStoreClass extends EventEmitter { if ((UserStore.getCurrentId() !== post.user_id || post.props.from_webhook === 'true')) { if (PostUtils.isSystemMessage(post)) { return; - } else if (!PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true) && post.type === Constants.POST_TYPE_JOIN_LEAVE) { - return; } let mentions = []; diff --git a/webapp/stores/post_store.jsx b/webapp/stores/post_store.jsx index 411eaf724..360b7cd72 100644 --- a/webapp/stores/post_store.jsx +++ b/webapp/stores/post_store.jsx @@ -8,7 +8,7 @@ import ChannelStore from 'stores/channel_store.jsx'; import BrowserStore from 'stores/browser_store.jsx'; import UserStore from 'stores/user_store.jsx'; -import Constants from 'utils/constants.jsx'; +import {Constants, PostTypes} from 'utils/constants.jsx'; const ActionTypes = Constants.ActionTypes; const CHANGE_EVENT = 'change'; @@ -616,7 +616,9 @@ class PostStoreClass extends EventEmitter { if (!joinLeave && postsList) { postsList.order = postsList.order.filter((id) => { - if (postsList.posts[id].type === Constants.POST_TYPE_JOIN_LEAVE) { + const post = postsList.posts[id]; + + if (post.type === PostTypes.JOIN_LEAVE || post.type === PostTypes.JOIN_CHANNEL || post.type === PostTypes.LEAVE_CHANNEL) { Reflect.deleteProperty(postsList.posts, id); return false; diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx index 646adbd15..dd9a4486e 100644 --- a/webapp/utils/constants.jsx +++ b/webapp/utils/constants.jsx @@ -228,6 +228,19 @@ export const TutorialSteps = { MENU_POPOVER: 3 }; +export const PostTypes = { + JOIN_LEAVE: 'system_join_leave', + JOIN_CHANNEL: 'system_join_channel', + LEAVE_CHANNEL: 'system_leave_channel', + ADD_TO_CHANNEL: 'system_add_to_channel', + REMOVE_FROM_CHANNEL: 'system_remove_from_channel', + HEADER_CHANGE: 'system_header_change', + DISPLAYNAME_CHANGE: 'system_displayname_change', + PURPOSE_CHANGE: 'system_purpose_change', + CHANNEL_DELETED: 'system_channel_deleted', + EPHEMERAL: 'system_ephemeral' +}; + export const Constants = { Preferences, SocketEvents, @@ -236,6 +249,7 @@ export const Constants = { UserStatuses, UserSearchOptions, TutorialSteps, + PostTypes, PayloadSources: keyMirror({ SERVER_ACTION: null, @@ -349,9 +363,6 @@ export const Constants = { POST_LOADING: 'loading', POST_FAILED: 'failed', POST_DELETED: 'deleted', - POST_TYPE_EPHEMERAL: 'system_ephemeral', - POST_TYPE_JOIN_LEAVE: 'system_join_leave', - POST_TYPE_ATTACHMENT: 'slack_attachment', SYSTEM_MESSAGE_PREFIX: 'system_', SYSTEM_MESSAGE_PROFILE_NAME: 'System', SYSTEM_MESSAGE_PROFILE_IMAGE: logoImage, diff --git a/webapp/utils/post_utils.jsx b/webapp/utils/post_utils.jsx index 20993b95c..0b908c55b 100644 --- a/webapp/utils/post_utils.jsx +++ b/webapp/utils/post_utils.jsx @@ -73,4 +73,4 @@ export function canEditPost(post, editDisableAction) { } } return canEdit; -}
\ No newline at end of file +} diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx index 815e78e53..4e8a05075 100644 --- a/webapp/utils/utils.jsx +++ b/webapp/utils/utils.jsx @@ -1170,7 +1170,7 @@ export function clearFileInput(elm) { } export function isPostEphemeral(post) { - return post.type === Constants.POST_TYPE_EPHEMERAL || post.state === Constants.POST_DELETED; + return post.type === Constants.PostTypes.EPHEMERAL || post.state === Constants.POST_DELETED; } export function getRootId(post) { |