summaryrefslogtreecommitdiffstats
path: root/webapp/components/post_view/post_message_view
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2017-06-18 14:42:32 -0400
committerGitHub <noreply@github.com>2017-06-18 14:42:32 -0400
commitab67f6e257f6e8f08145a02a7b93550f99641be4 (patch)
treed33d1c58a3d229f7e37db58bc2c397ac3806c503 /webapp/components/post_view/post_message_view
parent0231e95f1c5a8c42ba97875f0d2301815f552974 (diff)
downloadchat-ab67f6e257f6e8f08145a02a7b93550f99641be4.tar.gz
chat-ab67f6e257f6e8f08145a02a7b93550f99641be4.tar.bz2
chat-ab67f6e257f6e8f08145a02a7b93550f99641be4.zip
PLT-6215 Major post list refactor (#6501)
* Major post list refactor * Fix post and thread deletion * Fix preferences not selecting correctly * Fix military time displaying * Fix UP key for editing posts * Fix ESLint error * Various fixes and updates per feedback * Fix for permalink view * Revert to old scrolling method and various fixes * Add floating timestamp, new message indicator, scroll arrows * Update post loading for focus mode and add visibility limit * Fix pinning posts and a react warning * Add loading UI updates from Asaad * Fix refreshing loop * Temporarily bump post visibility limit * Update infinite scrolling * Remove infinite scrolling
Diffstat (limited to 'webapp/components/post_view/post_message_view')
-rw-r--r--webapp/components/post_view/post_message_view/index.js41
-rw-r--r--webapp/components/post_view/post_message_view/post_message_view.jsx139
-rw-r--r--webapp/components/post_view/post_message_view/system_message_helpers.jsx232
3 files changed, 412 insertions, 0 deletions
diff --git a/webapp/components/post_view/post_message_view/index.js b/webapp/components/post_view/post_message_view/index.js
new file mode 100644
index 000000000..cf457a508
--- /dev/null
+++ b/webapp/components/post_view/post_message_view/index.js
@@ -0,0 +1,41 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+import {getCustomEmojisAsMap} from 'mattermost-redux/selectors/entities/emojis';
+import {getBool} from 'mattermost-redux/selectors/entities/preferences';
+import {getCurrentUserMentionKeys, getUsersByUsername} from 'mattermost-redux/selectors/entities/users';
+
+import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
+
+import {Preferences} from 'mattermost-redux/constants';
+import {getSiteURL} from 'utils/url.jsx';
+
+import {EmojiMap} from 'stores/emoji_store.jsx';
+
+import PostMessageView from './post_message_view.jsx';
+
+function makeMapStateToProps() {
+ let emojiMap;
+ let oldCustomEmoji;
+
+ return function mapStateToProps(state, ownProps) {
+ const newCustomEmoji = getCustomEmojisAsMap(state);
+ if (newCustomEmoji !== oldCustomEmoji) {
+ emojiMap = new EmojiMap(newCustomEmoji);
+ }
+ oldCustomEmoji = newCustomEmoji;
+
+ return {
+ ...ownProps,
+ emojis: emojiMap,
+ enableFormatting: getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', true),
+ mentionKeys: getCurrentUserMentionKeys(state),
+ usernameMap: getUsersByUsername(state),
+ team: getCurrentTeam(state),
+ siteUrl: getSiteURL()
+ };
+ };
+}
+
+export default connect(makeMapStateToProps)(PostMessageView);
diff --git a/webapp/components/post_view/post_message_view/post_message_view.jsx b/webapp/components/post_view/post_message_view/post_message_view.jsx
new file mode 100644
index 000000000..66a8d01f8
--- /dev/null
+++ b/webapp/components/post_view/post_message_view/post_message_view.jsx
@@ -0,0 +1,139 @@
+// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import {FormattedMessage} from 'react-intl';
+
+import * as PostUtils from 'utils/post_utils.jsx';
+import * as TextFormatting from 'utils/text_formatting.jsx';
+import * as Utils from 'utils/utils.jsx';
+
+import {getChannelsNameMapInCurrentTeam} from 'mattermost-redux/selectors/entities/channels';
+import {Posts} from 'mattermost-redux/constants';
+import store from 'stores/redux_store.jsx';
+
+import {renderSystemMessage} from './system_message_helpers.jsx';
+
+export default class PostMessageView extends React.PureComponent {
+ static propTypes = {
+
+ /*
+ * The post to render the message for
+ */
+ post: PropTypes.object.isRequired,
+
+ /*
+ * Object using emoji names as keys with custom emojis as the values
+ */
+ emojis: PropTypes.object.isRequired,
+
+ /*
+ * The team the post was made in
+ */
+ team: PropTypes.object.isRequired,
+
+ /*
+ * Set to enable Markdown formatting
+ */
+ enableFormatting: PropTypes.bool,
+
+ /*
+ * An array of words that can be used to mention a user
+ */
+ mentionKeys: PropTypes.arrayOf(PropTypes.string),
+
+ /*
+ * Object mapping usernames to users
+ */
+ usernameMap: PropTypes.object,
+
+ /*
+ * The URL that the app is hosted on
+ */
+ siteUrl: PropTypes.string,
+
+ /*
+ * Options specific to text formatting
+ */
+ options: PropTypes.object,
+
+ /*
+ * Post identifiers for selenium tests
+ */
+ lastPostCount: PropTypes.number
+ };
+
+ static defaultProps = {
+ options: {},
+ mentionKeys: [],
+ usernameMap: {}
+ };
+
+ renderDeletedPost() {
+ return (
+ <p>
+ <FormattedMessage
+ id='post_body.deleted'
+ defaultMessage='(message deleted)'
+ />
+ </p>
+ );
+ }
+
+ renderEditedIndicator() {
+ if (!PostUtils.isEdited(this.props.post)) {
+ return null;
+ }
+
+ return (
+ <span className='post-edited-indicator'>
+ <FormattedMessage
+ id='post_message_view.edited'
+ defaultMessage='(edited)'
+ />
+ </span>
+ );
+ }
+
+ render() {
+ if (this.props.post.state === Posts.POST_DELETED) {
+ return this.renderDeletedPost();
+ }
+
+ if (!this.props.enableFormatting) {
+ return <span>{this.props.post.message}</span>;
+ }
+
+ const options = Object.assign({}, this.props.options, {
+ emojis: this.props.emojis,
+ siteURL: this.props.siteUrl,
+ mentionKeys: this.props.mentionKeys,
+ usernameMap: this.props.usernameMap,
+ channelNamesMap: getChannelsNameMapInCurrentTeam(store.getState()),
+ team: this.props.team
+ });
+
+ const renderedSystemMessage = renderSystemMessage(this.props.post, options);
+ if (renderedSystemMessage) {
+ return <div>{renderedSystemMessage}</div>;
+ }
+
+ let postId = null;
+ if (this.props.lastPostCount >= 0) {
+ postId = Utils.createSafeId('lastPostMessageText' + this.props.lastPostCount);
+ }
+
+ return (
+ <div>
+ <span
+ id={postId}
+ className='post-message__text'
+ onClick={Utils.handleFormattedTextClick}
+ dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message, options)}}
+ />
+ {this.renderEditedIndicator()}
+ </div>
+ );
+ }
+}
diff --git a/webapp/components/post_view/post_message_view/system_message_helpers.jsx b/webapp/components/post_view/post_message_view/system_message_helpers.jsx
new file mode 100644
index 000000000..c134e1a7a
--- /dev/null
+++ b/webapp/components/post_view/post_message_view/system_message_helpers.jsx
@@ -0,0 +1,232 @@
+// Copyright (c) 2017-present 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 renderUsername(value, options) {
+ return renderFormattedText(value, {...options, markdown: false});
+}
+
+function renderFormattedText(value, options) {
+ return <span dangerouslySetInnerHTML={{__html: formatText(value, options)}}/>;
+}
+
+function renderJoinChannelMessage(post, options) {
+ const username = renderUsername(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 = renderUsername(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 = renderUsername(post.props.username, options);
+ const addedUsername = renderUsername(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 = renderUsername(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 = renderUsername(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 = renderUsername(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 = renderUsername(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 = renderUsername(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);
+}