From ab67f6e257f6e8f08145a02a7b93550f99641be4 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Sun, 18 Jun 2017 14:42:32 -0400 Subject: 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 --- .../post_view/post_message_view/index.js | 41 ++++ .../post_message_view/post_message_view.jsx | 139 ++++++++++++ .../post_message_view/system_message_helpers.jsx | 232 +++++++++++++++++++++ 3 files changed, 412 insertions(+) create mode 100644 webapp/components/post_view/post_message_view/index.js create mode 100644 webapp/components/post_view/post_message_view/post_message_view.jsx create mode 100644 webapp/components/post_view/post_message_view/system_message_helpers.jsx (limited to 'webapp/components/post_view/post_message_view') 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 ( +

+ +

+ ); + } + + renderEditedIndicator() { + if (!PostUtils.isEdited(this.props.post)) { + return null; + } + + return ( + + + + ); + } + + render() { + if (this.props.post.state === Posts.POST_DELETED) { + return this.renderDeletedPost(); + } + + if (!this.props.enableFormatting) { + return {this.props.post.message}; + } + + 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
{renderedSystemMessage}
; + } + + let postId = null; + if (this.props.lastPostCount >= 0) { + postId = Utils.createSafeId('lastPostMessageText' + this.props.lastPostCount); + } + + return ( +
+ + {this.renderEditedIndicator()} +
+ ); + } +} 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 ; +} + +function renderJoinChannelMessage(post, options) { + const username = renderUsername(post.props.username, options); + + return ( + + ); +} + +function renderLeaveChannelMessage(post, options) { + const username = renderUsername(post.props.username, options); + + return ( + + ); +} + +function renderAddToChannelMessage(post, options) { + const username = renderUsername(post.props.username, options); + const addedUsername = renderUsername(post.props.addedUsername, options); + + return ( + + ); +} + +function renderRemoveFromChannelMessage(post, options) { + const removedUsername = renderUsername(post.props.removedUsername, options); + + return ( + + ); +} + +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 ( + + ); + } + + return ( + + ); + } else if (post.props.old_header) { + return ( + + ); + } + + 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 ( + + ); +} + +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 ( + + ); + } + + return ( + + ); + } else if (post.props.old_purpose) { + return ( + + ); + } + + return null; +} + +function renderChannelDeletedMessage(post, options) { + if (!post.props.username) { + return null; + } + + const username = renderUsername(post.props.username, options); + + return ( + + ); +} + +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); +} -- cgit v1.2.3-1-g7c22