summaryrefslogtreecommitdiffstats
path: root/webapp/components/post_view
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/components/post_view')
-rw-r--r--webapp/components/post_view/commented_on_files_message/commented_on_files_message.jsx61
-rw-r--r--webapp/components/post_view/commented_on_files_message/index.js36
-rw-r--r--webapp/components/post_view/date_separator.jsx32
-rw-r--r--webapp/components/post_view/failed_post_options/failed_post_options.jsx94
-rw-r--r--webapp/components/post_view/failed_post_options/index.js24
-rw-r--r--webapp/components/post_view/floating_timestamp.jsx53
-rw-r--r--webapp/components/post_view/index.js54
-rw-r--r--webapp/components/post_view/new_message_indicator.jsx72
-rw-r--r--webapp/components/post_view/post/index.js34
-rw-r--r--webapp/components/post_view/post/post.jsx300
-rw-r--r--webapp/components/post_view/post_attachment.jsx359
-rw-r--r--webapp/components/post_view/post_attachment_list.jsx41
-rw-r--r--webapp/components/post_view/post_attachment_opengraph/index.js28
-rw-r--r--webapp/components/post_view/post_attachment_opengraph/post_attachment_opengraph.jsx328
-rw-r--r--webapp/components/post_view/post_body/index.js30
-rw-r--r--webapp/components/post_view/post_body/post_body.jsx200
-rw-r--r--webapp/components/post_view/post_body_additional_content.jsx273
-rw-r--r--webapp/components/post_view/post_flag_icon.jsx95
-rw-r--r--webapp/components/post_view/post_header/index.js18
-rw-r--r--webapp/components/post_view/post_header/post_header.jsx159
-rw-r--r--webapp/components/post_view/post_image.jsx105
-rw-r--r--webapp/components/post_view/post_info/index.js31
-rw-r--r--webapp/components/post_view/post_info/post_info.jsx275
-rw-r--r--webapp/components/post_view/post_list.jsx582
-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.jsx215
-rw-r--r--webapp/components/post_view/post_message_view/system_message_helpers.jsx232
-rw-r--r--webapp/components/post_view/post_time.jsx96
-rw-r--r--webapp/components/post_view/reaction/index.js52
-rw-r--r--webapp/components/post_view/reaction/reaction.jsx254
-rw-r--r--webapp/components/post_view/reaction_list/index.js33
-rw-r--r--webapp/components/post_view/reaction_list/reaction_list.jsx88
-rw-r--r--webapp/components/post_view/scroll_to_bottom_arrows.jsx37
33 files changed, 0 insertions, 4332 deletions
diff --git a/webapp/components/post_view/commented_on_files_message/commented_on_files_message.jsx b/webapp/components/post_view/commented_on_files_message/commented_on_files_message.jsx
deleted file mode 100644
index 1755d68e4..000000000
--- a/webapp/components/post_view/commented_on_files_message/commented_on_files_message.jsx
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PropTypes from 'prop-types';
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-export default class CommentedOnFilesMessage extends React.PureComponent {
- static propTypes = {
-
- /*
- * The id of the post that was commented on
- */
- parentPostId: PropTypes.string.isRequired,
-
- /*
- * An array of file metadata for the parent post
- */
- fileInfos: PropTypes.arrayOf(PropTypes.object),
-
- actions: PropTypes.shape({
-
- /*
- * Function to get file metadata for a post
- */
- getFilesForPost: PropTypes.func.isRequired
- }).isRequired
- }
-
- componentDidMount() {
- if (!this.props.fileInfos || this.props.fileInfos.length === 0) {
- this.props.actions.getFilesForPost(this.props.parentPostId);
- }
- }
-
- render() {
- if (!this.props.fileInfos || this.props.fileInfos.length === 0) {
- return null;
- }
-
- let plusMore = null;
- if (this.props.fileInfos.length > 1) {
- plusMore = (
- <FormattedMessage
- id='post_body.plusMore'
- defaultMessage=' plus {count, number} other {count, plural, one {file} other {files}}'
- values={{
- count: this.props.fileInfos.length - 1
- }}
- />
- );
- }
-
- return (
- <span>
- {this.props.fileInfos[0].name}
- {plusMore}
- </span>
- );
- }
-}
diff --git a/webapp/components/post_view/commented_on_files_message/index.js b/webapp/components/post_view/commented_on_files_message/index.js
deleted file mode 100644
index d55cff16b..000000000
--- a/webapp/components/post_view/commented_on_files_message/index.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getFilesForPost} from 'mattermost-redux/actions/files';
-
-import {makeGetFilesForPost} from 'mattermost-redux/selectors/entities/files';
-
-import CommentedOnFilesMessage from './commented_on_files_message.jsx';
-
-function makeMapStateToProps() {
- const selectFileInfosForPost = makeGetFilesForPost();
-
- return function mapStateToProps(state, ownProps) {
- let fileInfos;
- if (ownProps.parentPostId) {
- fileInfos = selectFileInfosForPost(state, ownProps.parentPostId);
- }
-
- return {
- ...ownProps,
- fileInfos
- };
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getFilesForPost
- }, dispatch)
- };
-}
-
-export default connect(makeMapStateToProps, mapDispatchToProps)(CommentedOnFilesMessage);
diff --git a/webapp/components/post_view/date_separator.jsx b/webapp/components/post_view/date_separator.jsx
deleted file mode 100644
index 3f5184dbf..000000000
--- a/webapp/components/post_view/date_separator.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedDate} from 'react-intl';
-
-export default class DateSeparator extends React.PureComponent {
- static propTypes = {
-
- /*
- * The date to display in the separator
- */
- date: PropTypes.instanceOf(Date)
- }
-
- render() {
- return (
- <div
- className='date-separator'
- >
- <hr className='separator__hr'/>
- <div className='separator__text'>
- <FormattedDate
- value={this.props.date}
- weekday='short'
- month='short'
- day='2-digit'
- year='numeric'
- />
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/failed_post_options/failed_post_options.jsx b/webapp/components/post_view/failed_post_options/failed_post_options.jsx
deleted file mode 100644
index f28de343b..000000000
--- a/webapp/components/post_view/failed_post_options/failed_post_options.jsx
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {createPost} from 'actions/post_actions.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-
-export default class FailedPostOptions extends React.Component {
- static propTypes = {
-
- /*
- * The failed post
- */
- post: PropTypes.object.isRequired,
- actions: PropTypes.shape({
-
- /**
- * The function to delete the post
- */
- removePost: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.retryPost = this.retryPost.bind(this);
- this.cancelPost = this.cancelPost.bind(this);
-
- this.submitting = false;
-
- this.state = {};
- }
-
- retryPost(e) {
- e.preventDefault();
-
- if (this.submitting) {
- return;
- }
-
- this.submitting = true;
-
- const post = {...this.props.post};
- Reflect.deleteProperty(post, 'id');
- createPost(post,
- () => {
- this.submitting = false;
- },
- (err) => {
- if (err.id === 'api.post.create_post.root_id.app_error') {
- this.showPostDeletedModal();
- } else {
- this.forceUpdate();
- }
-
- this.submitting = false;
- }
- );
- }
-
- cancelPost(e) {
- e.preventDefault();
- this.props.actions.removePost(this.props.post);
- }
-
- render() {
- return (<span className='pending-post-actions'>
- <a
- className='post-retry'
- href='#'
- onClick={this.retryPost}
- >
- <FormattedMessage
- id='pending_post_actions.retry'
- defaultMessage='Retry'
- />
- </a>
- {' - '}
- <a
- className='post-cancel'
- href='#'
- onClick={this.cancelPost}
- >
- <FormattedMessage
- id='pending_post_actions.cancel'
- defaultMessage='Cancel'
- />
- </a>
- </span>);
- }
-}
diff --git a/webapp/components/post_view/failed_post_options/index.js b/webapp/components/post_view/failed_post_options/index.js
deleted file mode 100644
index bb8dde893..000000000
--- a/webapp/components/post_view/failed_post_options/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {removePost} from 'mattermost-redux/actions/posts';
-
-import FailedPostOptions from './failed_post_options.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- removePost
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(FailedPostOptions);
diff --git a/webapp/components/post_view/floating_timestamp.jsx b/webapp/components/post_view/floating_timestamp.jsx
deleted file mode 100644
index f0f6af60e..000000000
--- a/webapp/components/post_view/floating_timestamp.jsx
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedDate} from 'react-intl';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class FloatingTimestamp extends React.PureComponent {
- static propTypes = {
- isScrolling: PropTypes.bool.isRequired,
- isMobile: PropTypes.bool,
- createAt: PropTypes.number,
- isRhsPost: PropTypes.bool
- }
-
- render() {
- if (!this.props.isMobile) {
- return <noscript/>;
- }
-
- if (this.props.createAt === 0) {
- return <noscript/>;
- }
-
- const dateString = (
- <FormattedDate
- value={this.props.createAt}
- weekday='short'
- day='2-digit'
- month='short'
- year='numeric'
- />
- );
-
- let className = 'post-list__timestamp';
- if (this.props.isScrolling) {
- className += ' scrolling';
- }
-
- if (this.props.isRhsPost) {
- className += ' rhs';
- }
-
- return (
- <div className={className}>
- <div>
- <span>{dateString}</span>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/index.js b/webapp/components/post_view/index.js
deleted file mode 100644
index ad0270cdd..000000000
--- a/webapp/components/post_view/index.js
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-
-import {makeGetPostsInChannel, makeGetPostsAroundPost} from 'mattermost-redux/selectors/entities/posts';
-import {get} from 'mattermost-redux/selectors/entities/preferences';
-import {getChannel} from 'mattermost-redux/selectors/entities/channels';
-import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
-import {getPosts, getPostsBefore, getPostsAfter, getPostThread} from 'mattermost-redux/actions/posts';
-import {increasePostVisibility} from 'actions/post_actions.jsx';
-import {Preferences} from 'utils/constants.jsx';
-
-import PostList from './post_list.jsx';
-
-function makeMapStateToProps() {
- const getPostsInChannel = makeGetPostsInChannel();
- const getPostsAroundPost = makeGetPostsAroundPost();
-
- return function mapStateToProps(state, ownProps) {
- let posts;
- if (ownProps.focusedPostId) {
- posts = getPostsAroundPost(state, ownProps.focusedPostId, ownProps.channelId);
- } else {
- posts = getPostsInChannel(state, ownProps.channelId);
- }
-
- return {
- channel: getChannel(state, ownProps.channelId),
- lastViewedAt: state.views.channel.lastChannelViewTime[ownProps.channelId],
- posts,
- postVisibility: state.views.channel.postVisibility[ownProps.channelId],
- loadingPosts: state.views.channel.loadingPosts[ownProps.channelId],
- focusedPostId: ownProps.focusedPostId,
- currentUserId: getCurrentUserId(state),
- fullWidth: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN
- };
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getPosts,
- getPostsBefore,
- getPostsAfter,
- getPostThread,
- increasePostVisibility
- }, dispatch)
- };
-}
-
-export default connect(makeMapStateToProps, mapDispatchToProps)(PostList);
diff --git a/webapp/components/post_view/new_message_indicator.jsx b/webapp/components/post_view/new_message_indicator.jsx
deleted file mode 100644
index 537520c29..000000000
--- a/webapp/components/post_view/new_message_indicator.jsx
+++ /dev/null
@@ -1,72 +0,0 @@
-// 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 Constants from 'utils/constants.jsx';
-import {FormattedMessage} from 'react-intl';
-
-export default class NewMessageIndicator extends React.PureComponent {
- static propTypes = {
- onClick: PropTypes.func.isRequired,
- newMessages: PropTypes.number
- }
-
- constructor(props) {
- super(props);
- this.state = {
- visible: false,
- rendered: false
- };
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.newMessages > 0) {
- this.setState({rendered: true}, () => {
- this.setState({visible: true});
- });
- } else {
- this.setState({visible: false});
- }
- }
-
- render() {
- const unreadIcon = Constants.UNREAD_ICON_SVG;
- let className = 'new-messages__button';
- if (this.state.visible > 0) {
- className += ' visible';
- }
- if (!this.state.rendered) {
- className += ' disabled';
- }
- return (
- <div
- className={className}
- onTransitionEnd={this.setRendered.bind(this)}
- ref='indicator'
- >
- <div onClick={this.props.onClick}>
- <FormattedMessage
- id='posts_view.newMsgBelow'
- defaultMessage='New {count, plural, one {message} other {messages}}'
- values={{count: this.props.newMessages}}
- />
- <span
- className='icon icon__unread'
- dangerouslySetInnerHTML={{__html: unreadIcon}}
- />
- </div>
- </div>
- );
- }
-
- // Sync 'rendered' state with visibility param, only after transitions
- // have ended
- setRendered() {
- this.setState({rendered: this.state.visible});
- }
-}
-
-NewMessageIndicator.defaultProps = {
- newMessages: 0
-};
diff --git a/webapp/components/post_view/post/index.js b/webapp/components/post_view/post/index.js
deleted file mode 100644
index 2e7125f34..000000000
--- a/webapp/components/post_view/post/index.js
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-
-import {getCurrentUser, getUser, getStatusForUserId} from 'mattermost-redux/selectors/entities/users';
-import {get} from 'mattermost-redux/selectors/entities/preferences';
-import {getPost} from 'mattermost-redux/selectors/entities/posts';
-
-import {Preferences} from 'utils/constants.jsx';
-
-import Post from './post.jsx';
-
-function mapStateToProps(state, ownProps) {
- const detailedPost = ownProps.post || {};
-
- return {
- post: getPost(state, detailedPost.id),
- lastPostCount: ownProps.lastPostCount,
- user: getUser(state, detailedPost.user_id),
- status: getStatusForUserId(state, detailedPost.user_id),
- currentUser: getCurrentUser(state),
- isFirstReply: Boolean(detailedPost.isFirstReply && detailedPost.commentedOnPost),
- highlight: detailedPost.highlight,
- consecutivePostByUser: detailedPost.consecutivePostByUser,
- previousPostIsComment: detailedPost.previousPostIsComment,
- replyCount: detailedPost.replyCount,
- isCommentMention: detailedPost.isCommentMention,
- center: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
- compactDisplay: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT
- };
-}
-
-export default connect(mapStateToProps)(Post);
diff --git a/webapp/components/post_view/post/post.jsx b/webapp/components/post_view/post/post.jsx
deleted file mode 100644
index 25d23c690..000000000
--- a/webapp/components/post_view/post/post.jsx
+++ /dev/null
@@ -1,300 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PostHeader from 'components/post_view/post_header';
-import PostBody from 'components/post_view/post_body';
-import ProfilePicture from 'components/profile_picture.jsx';
-
-import Constants from 'utils/constants.jsx';
-const ActionTypes = Constants.ActionTypes;
-import {Posts} from 'mattermost-redux/constants';
-
-import * as Utils from 'utils/utils.jsx';
-import * as PostUtils from 'utils/post_utils.jsx';
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class Post extends React.PureComponent {
- static propTypes = {
-
- /**
- * The post to render
- */
- post: PropTypes.object.isRequired,
-
- /**
- * The user who created the post
- */
- user: PropTypes.object,
-
- /**
- * The status of the poster
- */
- status: PropTypes.string,
-
- /**
- * The logged in user
- */
- currentUser: PropTypes.object.isRequired,
-
- /**
- * Set to center the post
- */
- center: PropTypes.bool,
-
- /**
- * Set to render post compactly
- */
- compactDisplay: PropTypes.bool,
-
- /**
- * Set to render a preview of the parent post above this reply
- */
- isFirstReply: PropTypes.bool,
-
- /**
- * Set to highlight the background of the post
- */
- highlight: PropTypes.bool,
-
- /**
- * Set to render this post as if it was attached to the previous post
- */
- consecutivePostByUser: PropTypes.bool,
-
- /**
- * Set if the previous post is a comment
- */
- previousPostIsComment: PropTypes.bool,
-
- /**
- * Set to render this comment as a mention
- */
- isCommentMention: PropTypes.bool,
-
- /**
- * The number of replies in the same thread as this post
- */
- replyCount: PropTypes.number,
-
- /**
- * Set to mark the poster as in a webrtc call
- */
- isBusy: PropTypes.bool,
-
- /**
- * The post count used for selenium tests
- */
- lastPostCount: PropTypes.number,
-
- /**
- * Function to get the post list HTML element
- */
- getPostList: PropTypes.func.isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- dropdownOpened: false
- };
- }
-
- handleCommentClick = (e) => {
- e.preventDefault();
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_POST_SELECTED,
- postId: Utils.getRootId(this.props.post)
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_SEARCH,
- results: null
- });
- }
-
- handleDropdownOpened = (opened) => {
- this.setState({
- dropdownOpened: opened
- });
- }
-
- forceUpdateInfo = () => {
- this.refs.info.forceUpdate();
- this.refs.header.forceUpdate();
- }
-
- getClassName = (post, isSystemMessage, fromWebhook) => {
- let className = 'post';
-
- if (post.failed || post.state === Posts.POST_DELETED) {
- className += ' post--hide-controls';
- }
-
- if (this.props.highlight) {
- className += ' post--highlight';
- }
-
- let rootUser = '';
- if (this.props.isFirstReply) {
- rootUser = 'other--root';
- } else if (!post.root_id && !this.props.previousPostIsComment && this.props.consecutivePostByUser) {
- rootUser = 'same--root';
- } else if (post.root_id) {
- rootUser = 'same--root';
- } else {
- rootUser = 'other--root';
- }
-
- let currentUserCss = '';
- if (this.props.currentUser.id === post.user_id && !fromWebhook && !isSystemMessage) {
- currentUserCss = 'current--user';
- }
-
- let sameUserClass = '';
- if (this.props.consecutivePostByUser) {
- sameUserClass = 'same--user';
- }
-
- let postType = '';
- if (post.root_id && post.root_id.length > 0) {
- postType = 'post--comment';
- } else if (this.props.replyCount > 0) {
- postType = 'post--root';
- sameUserClass = '';
- rootUser = '';
- }
-
- if (isSystemMessage) {
- className += ' post--system';
- sameUserClass = '';
- currentUserCss = '';
- postType = '';
- rootUser = '';
- }
-
- if (this.props.compactDisplay) {
- className += ' post--compact';
- }
-
- if (this.state.dropdownOpened) {
- className += ' post--hovered';
- }
-
- if (post.is_pinned) {
- className += ' post--pinned';
- }
-
- return className + ' ' + sameUserClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss;
- }
-
- render() {
- const post = this.props.post;
- const mattermostLogo = Constants.MATTERMOST_ICON_SVG;
-
- const isSystemMessage = PostUtils.isSystemMessage(post);
- const fromWebhook = post.props && post.props.from_webhook === 'true';
-
- let status = this.props.status;
- if (fromWebhook) {
- status = null;
- }
-
- let profilePic = (
- <ProfilePicture
- src={PostUtils.getProfilePicSrcForPost(post, this.props.user)}
- status={status}
- user={this.props.user}
- isBusy={this.props.isBusy}
- hasMention={true}
- />
- );
-
- if (fromWebhook) {
- profilePic = (
- <ProfilePicture
- src={PostUtils.getProfilePicSrcForPost(post, this.props.user)}
- />
- );
- } else if (PostUtils.isSystemMessage(post)) {
- profilePic = (
- <span
- className='icon'
- dangerouslySetInnerHTML={{__html: mattermostLogo}}
- />
- );
- }
-
- let centerClass = '';
- if (this.props.center) {
- centerClass = 'center';
- }
-
- if (this.props.compactDisplay) {
- if (fromWebhook) {
- profilePic = (
- <ProfilePicture
- src=''
- status={status}
- isBusy={this.props.isBusy}
- user={this.props.user}
- />
- );
- } else {
- profilePic = (
- <ProfilePicture
- src=''
- status={status}
- />
- );
- }
- }
-
- const profilePicContainer = (<div className='post__img'>{profilePic}</div>);
-
- return (
- <div
- ref={(div) => {
- this.domNode = div;
- }}
- >
- <div
- id={'post_' + post.id}
- className={this.getClassName(this.props.post, isSystemMessage, fromWebhook)}
- >
- <div className={'post__content ' + centerClass}>
- {profilePicContainer}
- <div>
- <PostHeader
- ref='header'
- post={post}
- handleCommentClick={this.handleCommentClick}
- handleDropdownOpened={this.handleDropdownOpened}
- user={this.props.user}
- currentUser={this.props.currentUser}
- compactDisplay={this.props.compactDisplay}
- status={this.props.status}
- isBusy={this.props.isBusy}
- lastPostCount={this.props.lastPostCount}
- replyCount={this.props.replyCount}
- consecutivePostByUser={this.props.consecutivePostByUser}
- getPostList={this.props.getPostList}
- />
- <PostBody
- post={post}
- handleCommentClick={this.handleCommentClick}
- compactDisplay={this.props.compactDisplay}
- lastPostCount={this.props.lastPostCount}
- isCommentMention={this.props.isCommentMention}
- />
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_attachment.jsx b/webapp/components/post_view/post_attachment.jsx
deleted file mode 100644
index cc7aa509c..000000000
--- a/webapp/components/post_view/post_attachment.jsx
+++ /dev/null
@@ -1,359 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as TextFormatting from 'utils/text_formatting.jsx';
-import {localizeMessage} from 'utils/utils.jsx';
-
-import * as PostActions from 'actions/post_actions.jsx';
-
-import $ from 'jquery';
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class PostAttachment extends React.PureComponent {
- static propTypes = {
-
- /**
- * The post id
- */
- postId: PropTypes.string.isRequired,
-
- /**
- * The attachment to render
- */
- attachment: PropTypes.object.isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.handleActionButtonClick = this.handleActionButtonClick.bind(this);
- this.getActionView = this.getActionView.bind(this);
- this.getFieldsTable = this.getFieldsTable.bind(this);
- this.getInitState = this.getInitState.bind(this);
- this.shouldCollapse = this.shouldCollapse.bind(this);
- this.toggleCollapseState = this.toggleCollapseState.bind(this);
- }
-
- componentDidMount() {
- $(this.refs.attachment).on('click', '.attachment-link-more', this.toggleCollapseState);
- }
-
- componentWillUnmount() {
- $(this.refs.attachment).off('click', '.attachment-link-more', this.toggleCollapseState);
- }
-
- componentWillMount() {
- this.setState(this.getInitState());
- }
-
- getInitState() {
- const shouldCollapse = this.shouldCollapse();
- const text = TextFormatting.formatText(this.props.attachment.text || '');
- const uncollapsedText = text + (shouldCollapse ? `<div><a class="attachment-link-more" href="#">${localizeMessage('post_attachment.collapse', 'Show less...')}</a></div>` : '');
- const collapsedText = shouldCollapse ? this.getCollapsedText() : text;
-
- return {
- shouldCollapse,
- collapsedText,
- uncollapsedText,
- text: shouldCollapse ? collapsedText : uncollapsedText,
- collapsed: shouldCollapse
- };
- }
-
- toggleCollapseState(e) {
- e.preventDefault();
- this.setState((prevState) => {
- return {
- text: prevState.collapsed ? prevState.uncollapsedText : prevState.collapsedText,
- collapsed: !prevState.collapsed
- };
- });
- }
-
- shouldCollapse() {
- const text = this.props.attachment.text || '';
- return (text.match(/\n/g) || []).length >= 5 || text.length > 700;
- }
-
- getCollapsedText() {
- let text = this.props.attachment.text || '';
- if ((text.match(/\n/g) || []).length >= 5) {
- text = text.split('\n').splice(0, 5).join('\n');
- }
- if (text.length > 300) {
- text = text.substr(0, 300);
- }
-
- return TextFormatting.formatText(text) + `<div><a class="attachment-link-more" href="#">${localizeMessage('post_attachment.more', 'Show more...')}</a></div>`;
- }
-
- getActionView() {
- const actions = this.props.attachment.actions;
- if (!actions || !actions.length) {
- return '';
- }
-
- const buttons = [];
-
- actions.forEach((action) => {
- if (!action.id || !action.name) {
- return;
- }
- buttons.push(
- <button
- key={action.id}
- onClick={() => this.handleActionButtonClick(action.id)}
- >
- {action.name}
- </button>
- );
- });
-
- return (
- <div
- className='attachment-actions'
- >
- {buttons}
- </div>
- );
- }
-
- handleActionButtonClick(actionId) {
- PostActions.doPostAction(this.props.postId, actionId);
- }
-
- getFieldsTable() {
- const fields = this.props.attachment.fields;
- if (!fields || !fields.length) {
- return '';
- }
-
- const fieldTables = [];
-
- let headerCols = [];
- let bodyCols = [];
- let rowPos = 0;
- let lastWasLong = false;
- let nrTables = 0;
-
- fields.forEach((field, i) => {
- if (rowPos === 2 || !(field.short === true) || lastWasLong) {
- fieldTables.push(
- <table
- className='attachment-fields'
- key={'attachment__table__' + nrTables}
- >
- <thead>
- <tr>
- {headerCols}
- </tr>
- </thead>
- <tbody>
- <tr>
- {bodyCols}
- </tr>
- </tbody>
- </table>
- );
- headerCols = [];
- bodyCols = [];
- rowPos = 0;
- nrTables += 1;
- lastWasLong = false;
- }
- headerCols.push(
- <th
- className='attachment-field__caption'
- key={'attachment__field-caption-' + i + '__' + nrTables}
- width='50%'
- >
- {field.title}
- </th>
- );
- bodyCols.push(
- <td
- className='attachment-field'
- key={'attachment__field-' + i + '__' + nrTables}
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(field.value || '')}}
- />
- );
- rowPos += 1;
- lastWasLong = !(field.short === true);
- });
- if (headerCols.length > 0) { // Flush last fields
- fieldTables.push(
- <table
- className='attachment-fields'
- key={'attachment__table__' + nrTables}
- >
- <thead>
- <tr>
- {headerCols}
- </tr>
- </thead>
- <tbody>
- <tr>
- {bodyCols}
- </tr>
- </tbody>
- </table>
- );
- }
- return (
- <div>
- {fieldTables}
- </div>
- );
- }
-
- render() {
- const data = this.props.attachment;
-
- let preText;
- if (data.pretext) {
- preText = (
- <div
- className='attachment__thumb-pretext'
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(data.pretext)}}
- />
- );
- }
-
- let author = [];
- if (data.author_name || data.author_icon) {
- if (data.author_icon) {
- author.push(
- <img
- className='attachment__author-icon'
- src={data.author_icon}
- key={'attachment__author-icon'}
- height='14'
- width='14'
- />
- );
- }
- if (data.author_name) {
- author.push(
- <span
- className='attachment__author-name'
- key={'attachment__author-name'}
- >
- {data.author_name}
- </span>
- );
- }
- }
- if (data.author_link) {
- author = (
- <a
- href={data.author_link}
- target='_blank'
- rel='noopener noreferrer'
- >
- {author}
- </a>
- );
- }
-
- let title;
- if (data.title) {
- if (data.title_link) {
- title = (
- <h1
- className='attachment__title'
- >
- <a
- className='attachment__title-link'
- href={data.title_link}
- target='_blank'
- rel='noopener noreferrer'
- >
- {data.title}
- </a>
- </h1>
- );
- } else {
- title = (
- <h1
- className='attachment__title'
- >
- {data.title}
- </h1>
- );
- }
- }
-
- let text;
- if (data.text) {
- text = (
- <div
- className='attachment__text'
- dangerouslySetInnerHTML={{__html: this.state.text}}
- />
- );
- }
-
- let image;
- if (data.image_url) {
- image = (
- <img
- className='attachment__image'
- src={data.image_url}
- />
- );
- }
-
- let thumb;
- if (data.thumb_url) {
- thumb = (
- <div
- className='attachment__thumb-container'
- >
- <img
- src={data.thumb_url}
- />
- </div>
- );
- }
-
- const fields = this.getFieldsTable();
- const actions = this.getActionView();
-
- let useBorderStyle;
- if (data.color && data.color[0] === '#') {
- useBorderStyle = {borderLeftColor: data.color};
- }
-
- return (
- <div
- className='attachment'
- ref='attachment'
- >
- {preText}
- <div className='attachment__content'>
- <div
- className={useBorderStyle ? 'clearfix attachment__container' : 'clearfix attachment__container attachment__container--' + data.color}
- style={useBorderStyle}
- >
- {author}
- {title}
- <div>
- <div
- className={thumb ? 'attachment__body' : 'attachment__body attachment__body--no_thumb'}
- >
- {text}
- {image}
- {fields}
- {actions}
- </div>
- {thumb}
- <div style={{clear: 'both'}}/>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_attachment_list.jsx b/webapp/components/post_view/post_attachment_list.jsx
deleted file mode 100644
index ce60a0155..000000000
--- a/webapp/components/post_view/post_attachment_list.jsx
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PostAttachment from './post_attachment.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class PostAttachmentList extends React.PureComponent {
- static propTypes = {
-
- /**
- * The post id
- */
- postId: PropTypes.string.isRequired,
-
- /**
- * Array of attachments to render
- */
- attachments: PropTypes.array.isRequired
- }
-
- render() {
- const content = [];
- this.props.attachments.forEach((attachment, i) => {
- content.push(
- <PostAttachment
- attachment={attachment}
- postId={this.props.postId}
- key={'att_' + i}
- />
- );
- });
-
- return (
- <div className='attachment_list'>
- {content}
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_attachment_opengraph/index.js b/webapp/components/post_view/post_attachment_opengraph/index.js
deleted file mode 100644
index 1f889f1d6..000000000
--- a/webapp/components/post_view/post_attachment_opengraph/index.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
-import {bindActionCreators} from 'redux';
-import {getOpenGraphMetadata} from 'mattermost-redux/actions/posts';
-import {getOpenGraphMetadataForUrl} from 'mattermost-redux/selectors/entities/posts';
-
-import PostAttachmentOpenGraph from './post_attachment_opengraph.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- openGraphData: getOpenGraphMetadataForUrl(state, ownProps.link),
- currentUser: getCurrentUser(state),
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getOpenGraphMetadata
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(PostAttachmentOpenGraph);
diff --git a/webapp/components/post_view/post_attachment_opengraph/post_attachment_opengraph.jsx b/webapp/components/post_view/post_attachment_opengraph/post_attachment_opengraph.jsx
deleted file mode 100644
index 729084637..000000000
--- a/webapp/components/post_view/post_attachment_opengraph/post_attachment_opengraph.jsx
+++ /dev/null
@@ -1,328 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import {postListScrollChange} from 'actions/global_actions.jsx';
-import {updatePost} from 'actions/post_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import * as CommonUtils from 'utils/commons.jsx';
-import {PostTypes} from 'utils/constants.jsx';
-
-export default class PostAttachmentOpenGraph extends React.PureComponent {
- static propTypes = {
-
- /**
- * The link to display the open graph data for
- */
- link: PropTypes.string.isRequired,
-
- /**
- * The current user viewing the post
- */
- currentUser: PropTypes.object,
-
- /**
- * The post where this link is included
- */
- post: PropTypes.object,
-
- /**
- * The open graph data to render
- */
- openGraphData: PropTypes.object,
-
- /**
- * Set to collapse the preview
- */
- previewCollapsed: PropTypes.string,
- actions: PropTypes.shape({
-
- /**
- * The function to get open graph data for a link
- */
- getOpenGraphMetadata: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
- this.largeImageMinWidth = 150;
- this.imageDimentions = { // Image dimentions in pixels.
- height: 80,
- width: 80
- };
- this.textMaxLenght = 300;
- this.textEllipsis = '...';
- this.largeImageMinRatio = 16 / 9;
- this.smallImageContainerLeftPadding = 15;
-
- this.imageRatio = null;
-
- this.smallImageContainer = null;
- this.smallImageElement = null;
-
- this.IMAGE_LOADED = {
- LOADING: 'loading',
- YES: 'yes',
- ERROR: 'error'
- };
-
- this.fetchData = this.fetchData.bind(this);
- this.toggleImageVisibility = this.toggleImageVisibility.bind(this);
- this.onImageLoad = this.onImageLoad.bind(this);
- this.onImageError = this.onImageError.bind(this);
- this.handleRemovePreview = this.handleRemovePreview.bind(this);
- }
-
- componentWillMount() {
- const removePreview = this.isRemovePreview(this.props.post, this.props.currentUser);
-
- this.setState({
- imageLoaded: this.IMAGE_LOADED.LOADING,
- imageVisible: this.props.previewCollapsed.startsWith('false'),
- hasLargeImage: false,
- removePreview
- });
- this.fetchData(this.props.link);
- }
-
- componentWillReceiveProps(nextProps) {
- if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
- const removePreview = this.isRemovePreview(nextProps.post, nextProps.currentUser);
- this.setState({
- removePreview
- });
- }
- if (nextProps.link !== this.props.link) {
- this.fetchData(nextProps.link);
- }
- if (nextProps.previewCollapsed !== this.props.previewCollapsed) {
- this.setState({
- imageVisible: nextProps.previewCollapsed.startsWith('false')
- });
- }
- }
-
- componentDidUpdate() {
- setTimeout(postListScrollChange, 0);
- }
-
- fetchData(url) {
- if (!this.props.openGraphData) {
- this.props.actions.getOpenGraphMetadata(url);
- }
- }
-
- getBestImageUrl(data) {
- if (Utils.isEmptyObject(data.images)) {
- return null;
- }
-
- const bestImage = CommonUtils.getNearestPoint(this.imageDimentions, data.images, 'width', 'height');
- return bestImage.secure_url || bestImage.url;
- }
-
- toggleImageVisibility() {
- this.setState({imageVisible: !this.state.imageVisible});
- }
-
- onImageLoad(image) {
- this.imageRatio = image.target.naturalWidth / image.target.naturalHeight;
- if (
- image.target.naturalWidth >= this.largeImageMinWidth &&
- this.imageRatio >= this.largeImageMinRatio &&
- !this.state.hasLargeImage
- ) {
- this.setState({
- hasLargeImage: true
- });
- }
- this.setState({
- imageLoaded: this.IMAGE_LOADED.YES
- });
- }
-
- onImageError() {
- this.setState({imageLoaded: this.IMAGE_LOADED.ERROR});
- }
-
- loadImage(src) {
- const img = new Image();
- img.onload = this.onImageLoad;
- img.onerror = this.onImageError;
- img.src = src;
- }
-
- imageToggleAnchoreTag(imageUrl) {
- if (imageUrl && this.state.hasLargeImage) {
- return (
- <a
- className={'post__embed-visibility'}
- data-expanded={this.state.imageVisible}
- aria-label='Toggle Embed Visibility'
- onClick={this.toggleImageVisibility}
- />
- );
- }
- return null;
- }
-
- wrapInSmallImageContainer(imageElement) {
- return (
- <div
- className='attachment__image__container--openraph'
- ref={(div) => {
- this.smallImageContainer = div;
- }}
- >
- {imageElement}
- </div>
- );
- }
-
- imageTag(imageUrl, renderingForLargeImage = false) {
- var element = null;
- if (
- imageUrl && renderingForLargeImage === this.state.hasLargeImage &&
- (!renderingForLargeImage || (renderingForLargeImage && this.state.imageVisible))
- ) {
- if (this.state.imageLoaded === this.IMAGE_LOADED.LOADING) {
- if (renderingForLargeImage) {
- element = <img className={'attachment__image attachment__image--openraph loading large_image'}/>;
- } else {
- element = this.wrapInSmallImageContainer(
- <img className={'attachment__image attachment__image--openraph loading '}/>
- );
- }
- } else if (this.state.imageLoaded === this.IMAGE_LOADED.YES) {
- if (renderingForLargeImage) {
- element = (
- <img
- className={'attachment__image attachment__image--openraph large_image'}
- src={imageUrl}
- />
- );
- } else {
- element = this.wrapInSmallImageContainer(
- <img
- className={'attachment__image attachment__image--openraph'}
- src={imageUrl}
- ref={(img) => {
- this.smallImageElement = img;
- }}
- />
- );
- }
- } else if (this.state.imageLoaded === this.IMAGE_LOADED.ERROR) {
- return null;
- }
- }
- return element;
- }
-
- truncateText(text, maxLength = this.textMaxLenght, ellipsis = this.textEllipsis) {
- if (text.length > maxLength) {
- return text.substring(0, maxLength - ellipsis.length) + ellipsis;
- }
- return text;
- }
-
- handleRemovePreview() {
- const props = Object.assign({}, this.props.post.props);
- props[PostTypes.REMOVE_LINK_PREVIEW] = 'true';
-
- const patchedPost = ({
- id: this.props.post.id,
- props
- });
-
- updatePost(patchedPost, () => {
- this.setState({removePreview: true});
- });
- }
-
- isRemovePreview(post, currentUser) {
- if (post && post.props && currentUser.id === post.user_id) {
- return post.props[PostTypes.REMOVE_LINK_PREVIEW] && post.props[PostTypes.REMOVE_LINK_PREVIEW] === 'true';
- }
-
- return false;
- }
-
- render() {
- const data = this.props.openGraphData;
- if (!data || Utils.isEmptyObject(data.description) || this.state.removePreview) {
- return null;
- }
-
- let removePreviewButton;
- if (this.props.currentUser.id === this.props.post.user_id) {
- removePreviewButton = (
- <button
- id='removePreviewButton'
- type='button'
- className='btn-close'
- aria-label='Close'
- onClick={this.handleRemovePreview}
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- );
- }
-
- const imageUrl = this.getBestImageUrl(data);
- if (imageUrl) {
- this.loadImage(imageUrl);
- }
-
- return (
- <div
- className='attachment attachment--opengraph'
- ref='attachment'
- >
- <div className='attachment__content'>
- <div
- className={'clearfix attachment__container attachment__container--opengraph'}
- >
- <div
- className={'attachment__body__wrap attachment__body__wrap--opengraph'}
- >
- <span className='sitename'>{this.truncateText(data.site_name)}</span>
- {removePreviewButton}
- <h1
- className={'attachment__title attachment__title--opengraph' + (data.title ? '' : ' is-url')}
- >
- <a
- className='attachment__title-link attachment__title-link--opengraph'
- href={data.url || this.props.link}
- target='_blank'
- rel='noopener noreferrer'
- title={data.title || data.url || this.props.link}
- >
- {this.truncateText(data.title || data.url || this.props.link)}
- </a>
- </h1>
- <div >
- <div
- className={'attachment__body attachment__body--opengraph'}
- >
- <div>
- <div>
- {this.truncateText(data.description)} &nbsp;
- {this.imageToggleAnchoreTag(imageUrl)}
- </div>
- {this.imageTag(imageUrl, true)}
- </div>
- </div>
- </div>
- </div>
- {this.imageTag(imageUrl, false)}
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_body/index.js b/webapp/components/post_view/post_body/index.js
deleted file mode 100644
index 90f04e0f9..000000000
--- a/webapp/components/post_view/post_body/index.js
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-
-import {getUser} from 'mattermost-redux/selectors/entities/users';
-import {get} from 'mattermost-redux/selectors/entities/preferences';
-import {getPost} from 'mattermost-redux/selectors/entities/posts';
-
-import {Preferences} from 'utils/constants.jsx';
-
-import PostBody from './post_body.jsx';
-
-function mapStateToProps(state, ownProps) {
- let parentPost;
- let parentPostUser;
- if (ownProps.post.root_id) {
- parentPost = getPost(state, ownProps.post.root_id);
- parentPostUser = parentPost ? getUser(state, parentPost.user_id) : null;
- }
-
- return {
- ...ownProps,
- parentPost,
- parentPostUser,
- previewCollapsed: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLLAPSE_DISPLAY, 'false')
- };
-}
-
-export default connect(mapStateToProps)(PostBody);
diff --git a/webapp/components/post_view/post_body/post_body.jsx b/webapp/components/post_view/post_body/post_body.jsx
deleted file mode 100644
index 1eab74cf2..000000000
--- a/webapp/components/post_view/post_body/post_body.jsx
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PropTypes from 'prop-types';
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-import * as PostActions from 'actions/post_actions.jsx';
-
-import FileAttachmentListContainer from 'components/file_attachment_list';
-import CommentedOnFilesMessage from 'components/post_view/commented_on_files_message';
-import PostBodyAdditionalContent from 'components/post_view/post_body_additional_content.jsx';
-import FailedPostOptions from 'components/post_view/failed_post_options';
-import PostMessageView from 'components/post_view/post_message_view';
-import ReactionListContainer from 'components/post_view/reaction_list';
-
-import * as Utils from 'utils/utils.jsx';
-import * as PostUtils from 'utils/post_utils.jsx';
-
-import {Posts} from 'mattermost-redux/constants';
-
-export default class PostBody extends React.PureComponent {
- static propTypes = {
-
- /**
- * The post to render the body of
- */
- post: PropTypes.object.isRequired,
-
- /**
- * The parent post of the thread this post is in
- */
- parentPost: PropTypes.object,
-
- /**
- * The poster of the parent post, if exists
- */
- parentPostUser: PropTypes.object,
-
- /**
- * The function called when the comment icon is clicked
- */
- handleCommentClick: PropTypes.func.isRequired,
-
- /**
- * Set to render post body compactly
- */
- compactDisplay: PropTypes.bool,
-
- /**
- * Set to highlight comment as a mention
- */
- isCommentMention: PropTypes.bool,
-
- /**
- * Set to collapse image and video previews
- */
- previewCollapsed: PropTypes.string,
-
- /**
- * Post identifiers for selenium tests
- */
- lastPostCount: PropTypes.number
- }
-
- render() {
- const post = this.props.post;
- const parentPost = this.props.parentPost;
-
- let comment = '';
- let postClass = '';
-
- if (parentPost && !Utils.isPostEphemeral(post)) {
- const profile = this.props.parentPostUser;
-
- let apostrophe = '';
- let name = '...';
- if (profile != null) {
- let username = Utils.displayUsernameForUser(profile);
- if (parentPost.props &&
- parentPost.props.from_webhook &&
- parentPost.props.override_username &&
- global.window.mm_config.EnablePostUsernameOverride === 'true') {
- username = parentPost.props.override_username;
- }
-
- if (username.slice(-1) === 's') {
- apostrophe = '\'';
- } else {
- apostrophe = '\'s';
- }
- name = (
- <a
- className='theme'
- onClick={PostActions.searchForTerm.bind(null, username)}
- >
- {username}
- </a>
- );
- }
-
- let message = '';
- if (parentPost.message) {
- message = Utils.replaceHtmlEntities(parentPost.message);
- } else if (parentPost.file_ids && parentPost.file_ids.length > 0) {
- message = (
- <CommentedOnFilesMessage
- parentPostId={parentPost.id}
- />
- );
- }
-
- comment = (
- <div className='post__link'>
- <span>
- <FormattedMessage
- id='post_body.commentedOn'
- defaultMessage='Commented on {name}{apostrophe} message: '
- values={{
- name,
- apostrophe
- }}
- />
- <a
- className='theme'
- onClick={this.props.handleCommentClick}
- >
- {message}
- </a>
- </span>
- </div>
- );
- }
-
- let failedOptions;
- if (this.props.post.failed) {
- postClass += ' post--fail';
- failedOptions = <FailedPostOptions post={this.props.post}/>;
- }
-
- if (PostUtils.isEdited(this.props.post)) {
- postClass += ' post--edited';
- }
-
- let fileAttachmentHolder = null;
- if (((post.file_ids && post.file_ids.length > 0) || (post.filenames && post.filenames.length > 0)) && this.props.post.state !== Posts.POST_DELETED) {
- fileAttachmentHolder = (
- <FileAttachmentListContainer
- post={post}
- compactDisplay={this.props.compactDisplay}
- />
- );
- }
-
- const messageWrapper = (
- <div
- key={`${post.id}_message`}
- id={`${post.id}_message`}
- className={postClass}
- >
- {failedOptions}
- <PostMessageView
- lastPostCount={this.props.lastPostCount}
- post={this.props.post}
- compactDisplay={this.props.compactDisplay}
- hasMention={true}
- />
- </div>
- );
-
- let messageWithAdditionalContent;
- if (this.props.post.state === Posts.POST_DELETED) {
- messageWithAdditionalContent = messageWrapper;
- } else {
- messageWithAdditionalContent = (
- <PostBodyAdditionalContent
- post={this.props.post}
- message={messageWrapper}
- previewCollapsed={this.props.previewCollapsed}
- />
- );
- }
-
- let mentionHighlightClass = '';
- if (this.props.isCommentMention) {
- mentionHighlightClass = 'mention-comment';
- }
-
- return (
- <div>
- {comment}
- <div className={'post__body ' + mentionHighlightClass}>
- {messageWithAdditionalContent}
- {fileAttachmentHolder}
- <ReactionListContainer post={post}/>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_body_additional_content.jsx b/webapp/components/post_view/post_body_additional_content.jsx
deleted file mode 100644
index 88e8f2ba8..000000000
--- a/webapp/components/post_view/post_body_additional_content.jsx
+++ /dev/null
@@ -1,273 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PostAttachmentList from './post_attachment_list.jsx';
-import PostAttachmentOpenGraph from './post_attachment_opengraph';
-import PostImage from './post_image.jsx';
-import YoutubeVideo from 'components/youtube_video';
-
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-import BrowserStore from 'stores/browser_store.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class PostBodyAdditionalContent extends React.PureComponent {
- static propTypes = {
-
- /**
- * The post to render the content of
- */
- post: PropTypes.object.isRequired,
-
- /**
- * The post's message
- */
- message: PropTypes.element.isRequired,
-
- /**
- * Set to collapse image and video previews
- */
- previewCollapsed: PropTypes.string
- }
-
- static defaultProps = {
- previewCollapsed: ''
- }
-
- constructor(props) {
- super(props);
-
- this.getSlackAttachment = this.getSlackAttachment.bind(this);
- this.generateToggleableEmbed = this.generateToggleableEmbed.bind(this);
- this.generateStaticEmbed = this.generateStaticEmbed.bind(this);
- this.toggleEmbedVisibility = this.toggleEmbedVisibility.bind(this);
- this.isLinkToggleable = this.isLinkToggleable.bind(this);
- this.handleLinkLoadError = this.handleLinkLoadError.bind(this);
- this.handleLinkLoaded = this.handleLinkLoaded.bind(this);
-
- this.state = {
- embedVisible: PostBodyAdditionalContent.isEmbedVisible(props),
- link: Utils.extractFirstLink(props.post.message),
- linkLoadError: false,
- linkLoaded: false
- };
- }
-
- componentDidMount() {
- // check the availability of the image rendered(if any) in the first render.
- this.preCheckImageLink();
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.previewCollapsed !== this.props.previewCollapsed || nextProps.post.message !== this.props.post.message) {
- this.setState({
- embedVisible: PostBodyAdditionalContent.isEmbedVisible(nextProps),
- link: Utils.extractFirstLink(nextProps.post.message)
- }, () => {
- // check the availability of the image link
- this.preCheckImageLink();
- });
- }
- }
-
- toggleEmbedVisibility() {
- // save the taggle info in the localstorage
- BrowserStore.setItem(`isVisible-${this.props.post.id}`, !this.state.embedVisible);
-
- this.setState((prevState) => {
- return {embedVisible: !prevState.embedVisible};
- });
- }
-
- getSlackAttachment() {
- let attachments = [];
- if (this.props.post.props && this.props.post.props.attachments) {
- attachments = this.props.post.props.attachments;
- }
-
- return (
- <PostAttachmentList
- attachments={attachments}
- postId={this.props.post.id}
- key={this.props.post.id}
- />
- );
- }
-
- // when image links are collapsed, check if the link is a valid image url and it is available
- preCheckImageLink() {
- // check only if embedVisible is false i.e the image are by default hidden/collapsed
- // if embedVisible is true, the image is rendered, during which image load error is captured
- if (!this.state.embedVisible && this.isLinkImage(this.state.link)) {
- const image = new Image();
- image.src = this.state.link;
-
- image.onload = () => {
- this.handleLinkLoaded();
- };
-
- image.onerror = () => {
- this.handleLinkLoadError();
- };
- }
- }
-
- isLinkImage(link) {
- const regex = /.+\/(.+\.(?:jpg|gif|bmp|png|jpeg))(?:\?.*)?$/i;
- const match = link.match(regex);
- if (match && match[1]) {
- return true;
- }
-
- return false;
- }
-
- isLinkToggleable() {
- const link = this.state.link;
- if (!link) {
- return false;
- }
-
- if (YoutubeVideo.isYoutubeLink(link)) {
- return true;
- }
-
- if (this.isLinkImage(link)) {
- return true;
- }
-
- return false;
- }
-
- handleLinkLoadError() {
- this.setState({
- linkLoadError: true
- });
- }
-
- handleLinkLoaded() {
- this.setState({
- linkLoaded: true
- });
- }
-
- generateToggleableEmbed() {
- const link = this.state.link;
- if (!link) {
- return null;
- }
-
- if (YoutubeVideo.isYoutubeLink(link)) {
- return (
- <YoutubeVideo
- channelId={this.props.post.channel_id}
- link={link}
- show={this.state.embedVisible}
- onLinkLoaded={this.handleLinkLoaded}
- />
- );
- }
-
- if (this.isLinkImage(link)) {
- return (
- <PostImage
- channelId={this.props.post.channel_id}
- link={link}
- onLinkLoadError={this.handleLinkLoadError}
- onLinkLoaded={this.handleLinkLoaded}
- />
- );
- }
-
- return null;
- }
-
- generateStaticEmbed() {
- if (this.props.post.props && this.props.post.props.attachments) {
- return this.getSlackAttachment();
- }
-
- const link = Utils.extractFirstLink(this.props.post.message);
- if (link && Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMBED_PREVIEW) && global.window.mm_config.EnableLinkPreviews === 'true') {
- return (
- <PostAttachmentOpenGraph
- link={link}
- previewCollapsed={this.props.previewCollapsed}
- post={this.props.post}
- />
- );
- }
-
- return null;
- }
-
- render() {
- if (this.isLinkToggleable() && !this.state.linkLoadError) {
- // if message has only one line and starts with a link place toggle in this only line
- // else - place it in new line between message and embed
- const prependToggle = (/^\s*https?:\/\/.*$/).test(this.props.post.message);
-
- const toggle = (
- <a
- key='toggle'
- className={`post__embed-visibility ${prependToggle ? 'pull-left' : ''}`}
- data-expanded={this.state.embedVisible}
- aria-label='Toggle Embed Visibility'
- onClick={this.toggleEmbedVisibility}
- />
- );
- const message = (
- <div key='message'>
- {this.props.message}
- </div>
- );
-
- const contents = [message];
-
- if (this.state.linkLoaded || YoutubeVideo.isYoutubeLink(this.state.link)) {
- if (prependToggle) {
- contents.unshift(toggle);
- } else {
- contents.push(toggle);
- }
- }
-
- if (this.state.embedVisible) {
- contents.push(
- <div
- key='embed'
- className='post__embed-container'
- >
- {this.generateToggleableEmbed()}
- </div>
- );
- }
-
- return (
- <div>
- {contents}
- </div>
- );
- }
-
- const staticEmbed = this.generateStaticEmbed();
-
- if (staticEmbed) {
- return (
- <div>
- {this.props.message}
- {staticEmbed}
- </div>
- );
- }
-
- return this.props.message;
- }
-
- static isEmbedVisible(props) {
- // check first in localstorage, if not present, consider previewCollapsed from the parent component
- return BrowserStore.getItem(`isVisible-${props.post.id}`, props.previewCollapsed.startsWith('false'));
- }
-}
diff --git a/webapp/components/post_view/post_flag_icon.jsx b/webapp/components/post_view/post_flag_icon.jsx
deleted file mode 100644
index 02f8feb53..000000000
--- a/webapp/components/post_view/post_flag_icon.jsx
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2015-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 {Tooltip, OverlayTrigger} from 'react-bootstrap';
-
-import {flagPost, unflagPost} from 'actions/post_actions.jsx';
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-function flagToolTip(isFlagged) {
- return (
- <Tooltip id='flagTooltip'>
- <FormattedMessage
- id={isFlagged ? 'flag_post.unflag' : 'flag_post.flag'}
- defaultMessage={isFlagged ? 'Unflag' : 'Flag for follow up'}
- />
- </Tooltip>
- );
-}
-
-function flagIcon(isFlagged) {
- let flagIconSvg = Constants.FLAG_ICON_SVG;
-
- if (isFlagged) {
- flagIconSvg = Constants.FLAG_FILLED_ICON_SVG;
- }
-
- return (
- <span
- className='icon'
- dangerouslySetInnerHTML={{__html: flagIconSvg}}
- />
- );
-}
-
-export default function PostFlagIcon(props) {
- function onFlagPost(e) {
- e.preventDefault();
- flagPost(props.postId);
- }
-
- function onUnflagPost(e) {
- e.preventDefault();
- unflagPost(props.postId);
- }
-
- const flagFunc = props.isFlagged ? onUnflagPost : onFlagPost;
- const flagVisible = props.isFlagged ? 'visible' : '';
-
- let flagIconId = null;
- if (props.idCount > -1) {
- flagIconId = Utils.createSafeId(props.idPrefix + props.idCount);
- }
-
- if (!props.isEphemeral) {
- return (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- key={'flagtooltipkey' + flagVisible}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={flagToolTip(props.isFlagged)}
- >
- <a
- id={flagIconId}
- href='#'
- className={'flag-icon__container ' + flagVisible}
- onClick={flagFunc}
- >
- {flagIcon(props.isFlagged)}
- </a>
- </OverlayTrigger>
- );
- }
-
- return null;
-}
-
-PostFlagIcon.propTypes = {
- idPrefix: PropTypes.string.isRequired,
- idCount: PropTypes.number,
- postId: PropTypes.string.isRequired,
- isFlagged: PropTypes.bool.isRequired,
- isEphemeral: PropTypes.bool
-};
-
-PostFlagIcon.defaultProps = {
- idCount: -1,
- postId: '',
- isFlagged: false,
- isEphemeral: false
-};
diff --git a/webapp/components/post_view/post_header/index.js b/webapp/components/post_view/post_header/index.js
deleted file mode 100644
index d7aaef1d5..000000000
--- a/webapp/components/post_view/post_header/index.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-
-import {get} from 'mattermost-redux/selectors/entities/preferences';
-import {Preferences} from 'mattermost-redux/constants';
-
-import PostHeader from './post_header.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- displayNameType: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false')
- };
-}
-
-export default connect(mapStateToProps)(PostHeader);
diff --git a/webapp/components/post_view/post_header/post_header.jsx b/webapp/components/post_view/post_header/post_header.jsx
deleted file mode 100644
index 0715f047c..000000000
--- a/webapp/components/post_view/post_header/post_header.jsx
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import UserProfile from 'components/user_profile.jsx';
-import PostInfo from 'components/post_view/post_info';
-import {FormattedMessage} from 'react-intl';
-
-import * as PostUtils from 'utils/post_utils.jsx';
-
-import Constants from 'utils/constants.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class PostHeader extends React.PureComponent {
- static propTypes = {
-
- /*
- * The post to render the header for
- */
- post: PropTypes.object.isRequired,
-
- /*
- * The user who created the post
- */
- user: PropTypes.object,
-
- /*
- * Function called when the comment icon is clicked
- */
- handleCommentClick: PropTypes.func.isRequired,
-
- /*
- * Function called when the post options dropdown is opened
- */
- handleDropdownOpened: PropTypes.func.isRequired,
-
- /*
- * Set to render compactly
- */
- compactDisplay: PropTypes.bool,
-
- /*
- * Set to render the post as if it was part of the previous post
- */
- consecutivePostByUser: PropTypes.bool,
-
- /*
- * The method for displaying the post creator's name
- */
- displayNameType: PropTypes.string,
-
- /*
- * The status of the user who created the post
- */
- status: PropTypes.string,
-
- /*
- * Set if the post creator is currenlty in a WebRTC call
- */
- isBusy: PropTypes.bool,
-
- /*
- * The number of replies in the same thread as this post
- */
- replyCount: PropTypes.number,
-
- /*
- * Post identifiers for selenium tests
- */
- lastPostCount: PropTypes.number,
-
- /**
- * Function to get the post list HTML element
- */
- getPostList: PropTypes.func.isRequired
- }
-
- constructor(props) {
- super(props);
- this.state = {};
- }
-
- render() {
- const post = this.props.post;
- const isSystemMessage = PostUtils.isSystemMessage(post);
-
- let userProfile = (
- <UserProfile
- user={this.props.user}
- displayNameType={this.props.displayNameType}
- status={this.props.status}
- isBusy={this.props.isBusy}
- hasMention={true}
- />
- );
- let botIndicator;
- let colon;
-
- if (post.props && post.props.from_webhook) {
- if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') {
- userProfile = (
- <UserProfile
- user={this.props.user}
- overwriteName={post.props.override_username}
- disablePopover={true}
- />
- );
- } else {
- userProfile = (
- <UserProfile
- user={this.props.user}
- displayNameType={this.props.displayNameType}
- disablePopover={true}
- />
- );
- }
-
- botIndicator = <div className='bot-indicator'>{Constants.BOT_NAME}</div>;
- } else if (isSystemMessage) {
- userProfile = (
- <UserProfile
- user={{}}
- overwriteName={
- <FormattedMessage
- id='post_info.system'
- defaultMessage='System'
- />
- }
- overwriteImage={Constants.SYSTEM_MESSAGE_PROFILE_IMAGE}
- disablePopover={true}
- />
- );
- }
-
- if (this.props.compactDisplay) {
- colon = (<strong className='colon'>{':'}</strong>);
- }
-
- return (
- <div className='post__header'>
- <div className='col col__name'>{userProfile}{colon}</div>
- {botIndicator}
- <div className='col'>
- <PostInfo
- post={post}
- handleCommentClick={this.props.handleCommentClick}
- handleDropdownOpened={this.props.handleDropdownOpened}
- compactDisplay={this.props.compactDisplay}
- lastPostCount={this.props.lastPostCount}
- replyCount={this.props.replyCount}
- consecutivePostByUser={this.props.consecutivePostByUser}
- getPostList={this.props.getPostList}
- />
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_image.jsx b/webapp/components/post_view/post_image.jsx
deleted file mode 100644
index 322742305..000000000
--- a/webapp/components/post_view/post_image.jsx
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import {postListScrollChange} from 'actions/global_actions.jsx';
-
-export default class PostImageEmbed extends React.PureComponent {
- static propTypes = {
-
- /**
- * The link to load the image from
- */
- link: PropTypes.string.isRequired,
-
- /**
- * Function to call when image is loaded
- */
- onLinkLoaded: PropTypes.func,
-
- /**
- * The function to call if image load fails
- */
- onLinkLoadError: PropTypes.func
- }
-
- constructor(props) {
- super(props);
-
- this.handleLoadComplete = this.handleLoadComplete.bind(this);
- this.handleLoadError = this.handleLoadError.bind(this);
-
- this.state = {
- loaded: false,
- errored: false
- };
- }
-
- componentWillMount() {
- this.loadImg(this.props.link);
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.link !== this.props.link) {
- this.setState({
- loaded: false,
- errored: false
- });
- }
- }
-
- componentDidUpdate(prevProps) {
- if (!this.state.loaded && prevProps.link !== this.props.link) {
- this.loadImg(this.props.link);
- }
- }
-
- loadImg(src) {
- const img = new Image();
- img.onload = this.handleLoadComplete;
- img.onerror = this.handleLoadError;
- img.src = src;
- }
-
- handleLoadComplete() {
- this.setState({
- loaded: true,
- errored: false
- });
-
- postListScrollChange();
-
- if (this.props.onLinkLoaded) {
- this.props.onLinkLoaded();
- }
- }
-
- handleLoadError() {
- this.setState({
- errored: true,
- loaded: true
- });
- if (this.props.onLinkLoadError) {
- this.props.onLinkLoadError();
- }
- }
-
- render() {
- if (this.state.errored || !this.state.loaded) {
- return null;
- }
-
- return (
- <div
- className='post__embed-container'
- >
- <img
- className='img-div'
- src={this.props.link}
- />
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_info/index.js b/webapp/components/post_view/post_info/index.js
deleted file mode 100644
index 041080da8..000000000
--- a/webapp/components/post_view/post_info/index.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {removePost, addReaction} from 'mattermost-redux/actions/posts';
-
-import {get, getBool} from 'mattermost-redux/selectors/entities/preferences';
-
-import {Preferences} from 'utils/constants.jsx';
-
-import PostInfo from './post_info.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- useMilitaryTime: getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false),
- isFlagged: get(state, Preferences.CATEGORY_FLAGGED_POST, ownProps.post.id, null) != null
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- removePost,
- addReaction
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(PostInfo);
diff --git a/webapp/components/post_view/post_info/post_info.jsx b/webapp/components/post_view/post_info/post_info.jsx
deleted file mode 100644
index cc3133764..000000000
--- a/webapp/components/post_view/post_info/post_info.jsx
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PostTime from 'components/post_view/post_time.jsx';
-import PostFlagIcon from 'components/post_view/post_flag_icon.jsx';
-import CommentIcon from 'components/common/comment_icon.jsx';
-import EmojiPickerOverlay from 'components/emoji_picker/emoji_picker_overlay.jsx';
-import DotMenu from 'components/dot_menu';
-
-import * as Utils from 'utils/utils.jsx';
-import * as PostUtils from 'utils/post_utils.jsx';
-import * as ReduxPostUtils from 'mattermost-redux/utils/post_utils';
-import {emitEmojiPosted} from 'actions/post_actions.jsx';
-import Constants from 'utils/constants.jsx';
-import {Posts} from 'mattermost-redux/constants';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-
-export default class PostInfo extends React.PureComponent {
- static propTypes = {
-
- /*
- * The post to render the info for
- */
- post: PropTypes.object.isRequired,
-
- /*
- * Function called when the comment icon is clicked
- */
- handleCommentClick: PropTypes.func.isRequired,
-
- /*
- * Funciton called when the post options dropdown is opened
- */
- handleDropdownOpened: PropTypes.func.isRequired,
-
- /*
- * Set to display in 24 hour format
- */
- useMilitaryTime: PropTypes.bool.isRequired,
-
- /*
- * Set to mark the post as flagged
- */
- isFlagged: PropTypes.bool,
-
- /*
- * The number of replies in the same thread as this post
- */
- replyCount: PropTypes.number,
-
- /*
- * Post identifiers for selenium tests
- */
- lastPostCount: PropTypes.number,
-
- /**
- * Set to render in compact view
- */
- compactDisplay: PropTypes.bool,
-
- /**
- * Function to get the post list HTML element
- */
- getPostList: PropTypes.func.isRequired,
-
- actions: PropTypes.shape({
-
- /*
- * Function to remove the post
- */
- removePost: PropTypes.func.isRequired,
-
- /*
- * Function to add a reaction to the post
- */
- addReaction: PropTypes.func.isRequired
- }).isRequired
- };
-
- constructor(props) {
- super(props);
-
- this.removePost = this.removePost.bind(this);
- this.reactEmojiClick = this.reactEmojiClick.bind(this);
-
- this.state = {
- showEmojiPicker: false,
- reactionPickerOffset: 21
- };
- }
-
- toggleEmojiPicker = () => {
- const showEmojiPicker = !this.state.showEmojiPicker;
-
- this.setState({showEmojiPicker});
- this.props.handleDropdownOpened(showEmojiPicker);
- };
-
- hideEmojiPicker = () => {
- this.setState({showEmojiPicker: false});
- this.props.handleDropdownOpened(false);
- };
-
- removePost() {
- this.props.actions.removePost(this.props.post);
- }
-
- createRemovePostButton() {
- return (
- <a
- href='#'
- className='post__remove theme'
- type='button'
- onClick={this.removePost}
- >
- {'×'}
- </a>
- );
- }
-
- reactEmojiClick(emoji) {
- const pickerOffset = 21;
- this.setState({showEmojiPicker: false, reactionPickerOffset: pickerOffset});
- const emojiName = emoji.name || emoji.aliases[0];
- this.props.actions.addReaction(this.props.post.id, emojiName);
- emitEmojiPosted(emojiName);
- this.props.handleDropdownOpened(false);
- }
-
- getDotMenu = () => {
- return this.refs.dotMenu;
- };
-
- render() {
- const post = this.props.post;
-
- let idCount = -1;
- if (this.props.lastPostCount >= 0 && this.props.lastPostCount < Constants.TEST_ID_COUNT) {
- idCount = this.props.lastPostCount;
- }
-
- const isEphemeral = Utils.isPostEphemeral(post);
- const isSystemMessage = PostUtils.isSystemMessage(post);
-
- let comments = null;
- let react = null;
- if (!isEphemeral && !post.failed && !isSystemMessage) {
- comments = (
- <CommentIcon
- idPrefix='commentIcon'
- idCount={idCount}
- handleCommentClick={this.props.handleCommentClick}
- commentCount={this.props.replyCount}
- id={post.channel_id + '_' + post.id}
- />
- );
-
- if (window.mm_config.EnableEmojiPicker === 'true') {
- react = (
- <span>
- <EmojiPickerOverlay
- show={this.state.showEmojiPicker}
- container={this.props.getPostList}
- target={this.getDotMenu}
- onHide={this.hideEmojiPicker}
- onEmojiClick={this.reactEmojiClick}
- rightOffset={7}
- />
- <a
- href='#'
- className='reacticon__container'
- onClick={this.toggleEmojiPicker}
- >
- <span
- className='icon icon--emoji'
- dangerouslySetInnerHTML={{__html: Constants.EMOJI_ICON_SVG}}
- />
- </a>
- </span>
-
- );
- }
- }
-
- let options;
- if (isEphemeral) {
- options = (
- <div className='col col__remove'>
- {this.createRemovePostButton()}
- </div>
- );
- } else if (!post.failed) {
- const dotMenu = (
- <DotMenu
- idPrefix={Constants.CENTER}
- idCount={idCount}
- post={this.props.post}
- commentCount={this.props.replyCount}
- isFlagged={this.props.isFlagged}
- handleCommentClick={this.props.handleCommentClick}
- handleDropdownOpened={this.props.handleDropdownOpened}
- />
- );
-
- if (PostUtils.shouldShowDotMenu(this.props.post)) {
- options = (
- <div
- ref='dotMenu'
- className='col col__reply'
- >
- {dotMenu}
- {react}
- {comments}
- </div>
- );
- }
- }
-
- let visibleMessage;
- if (isEphemeral && !this.props.compactDisplay && post.state !== Posts.POST_DELETED) {
- visibleMessage = (
- <span className='post__visibility'>
- <FormattedMessage
- id='post_info.message.visible'
- defaultMessage='(Only visible to you)'
- />
- </span>
- );
- }
-
- let pinnedBadge;
- if (post.is_pinned) {
- pinnedBadge = (
- <span className='post__pinned-badge'>
- <FormattedMessage
- id='post_info.pinned'
- defaultMessage='Pinned'
- />
- </span>
- );
- }
-
- // timestamp should not be a permalink if the post has been deleted, is ephemeral message, or is pending
- const isPermalink = !(isEphemeral ||
- Posts.POST_DELETED === this.props.post.state ||
- ReduxPostUtils.isPostPendingOrFailed(this.props.post));
-
- return (
- <div className='post__header--info'>
- <div className='col'>
- <PostTime
- isPermalink={isPermalink}
- eventTime={post.create_at}
- useMilitaryTime={this.props.useMilitaryTime}
- postId={post.id}
- />
- {pinnedBadge}
- {this.state.showEmojiPicker}
- <PostFlagIcon
- idPrefix={'centerPostFlag'}
- idCount={idCount}
- postId={post.id}
- isFlagged={this.props.isFlagged}
- isEphemeral={isEphemeral}
- />
- {visibleMessage}
- </div>
- {options}
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_list.jsx b/webapp/components/post_view/post_list.jsx
deleted file mode 100644
index c13de7096..000000000
--- a/webapp/components/post_view/post_list.jsx
+++ /dev/null
@@ -1,582 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Post from './post';
-import LoadingScreen from 'components/loading_screen.jsx';
-import FloatingTimestamp from './floating_timestamp.jsx';
-import ScrollToBottomArrows from './scroll_to_bottom_arrows.jsx';
-import NewMessageIndicator from './new_message_indicator.jsx';
-
-import * as UserAgent from 'utils/user_agent.jsx';
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-import {createChannelIntroMessage} from 'utils/channel_intro_messages.jsx';
-import DelayedAction from 'utils/delayed_action.jsx';
-import EventTypes from 'utils/event_types.jsx';
-import GlobalEventEmitter from 'utils/global_event_emitter.jsx';
-
-import {FormattedDate, FormattedMessage} from 'react-intl';
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-import PropTypes from 'prop-types';
-
-const CLOSE_TO_BOTTOM_SCROLL_MARGIN = 10;
-const POSTS_PER_PAGE = Constants.POST_CHUNK_SIZE / 2;
-
-export default class PostList extends React.PureComponent {
- static propTypes = {
-
- /**
- * Array of posts in the channel, ordered from oldest to newest
- */
- posts: PropTypes.array,
-
- /**
- * The number of posts that should be rendered
- */
- postVisibility: PropTypes.number,
-
- /**
- * The channel the posts are in
- */
- channel: PropTypes.object.isRequired,
-
- /**
- * The last time the channel was viewed, sets the new message separator
- */
- lastViewedAt: PropTypes.number,
-
- /**
- * Set if more posts are being loaded
- */
- loadingPosts: PropTypes.bool,
-
- /**
- * The user id of the logged in user
- */
- currentUserId: PropTypes.string,
-
- /**
- * Set to focus this post
- */
- focusedPostId: PropTypes.array,
-
- /**
- * Whether to display the channel intro at full width
- */
- fullWidth: PropTypes.bool,
-
- actions: PropTypes.shape({
-
- /**
- * Function to get posts in the channel
- */
- getPosts: PropTypes.func.isRequired,
-
- /**
- * Function to get posts in the channel older than the focused post
- */
- getPostsBefore: PropTypes.func.isRequired,
-
- /**
- * Function to get posts in the channel newer than the focused post
- */
- getPostsAfter: PropTypes.func.isRequired,
-
- /**
- * Function to get the post thread for the focused post
- */
- getPostThread: PropTypes.func.isRequired,
-
- /**
- * Function to increase the number of posts being rendered
- */
- increasePostVisibility: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.scrollStopAction = new DelayedAction(this.handleScrollStop);
-
- this.previousScrollTop = Number.MAX_SAFE_INTEGER;
- this.previousScrollHeight = 0;
- this.previousClientHeight = 0;
- this.atBottom = false;
-
- this.state = {
- atEnd: false,
- unViewedCount: 0,
- isScrolling: false,
- lastViewed: props.lastViewedAt
- };
- }
-
- componentDidMount() {
- this.loadPosts(this.props.channel.id, this.props.focusedPostId);
- GlobalEventEmitter.addListener(EventTypes.POST_LIST_SCROLL_CHANGE, this.handleResize);
-
- window.addEventListener('resize', () => this.handleResize());
- }
-
- componentWillUnmount() {
- GlobalEventEmitter.removeListener(EventTypes.POST_LIST_SCROLL_CHANGE, this.handleResize);
- window.removeEventListener('resize', () => this.handleResize());
- }
-
- componentWillReceiveProps(nextProps) {
- // Focusing on a new post so load posts around it
- if (nextProps.focusedPostId && this.props.focusedPostId !== nextProps.focusedPostId) {
- this.hasScrolledToFocusedPost = false;
- this.hasScrolledToNewMessageSeparator = false;
- this.setState({atEnd: false});
- this.loadPosts(nextProps.channel.id, nextProps.focusedPostId);
- return;
- }
-
- const channel = this.props.channel || {};
- const nextChannel = nextProps.channel || {};
-
- if (nextProps.focusedPostId == null) {
- // Channel changed so load posts for new channel
- if (channel.id !== nextChannel.id) {
- this.hasScrolled = false;
- this.hasScrolledToFocusedPost = false;
- this.hasScrolledToNewMessageSeparator = false;
- this.atBottom = false;
- this.setState({atEnd: false, lastViewed: nextProps.lastViewedAt});
-
- if (nextChannel.id) {
- this.loadPosts(nextChannel.id);
- }
- }
-
- const nextPosts = nextProps.posts || [];
- const posts = this.props.posts || [];
- const hasNewPosts = (posts.length === 0 && nextPosts.length > 0) || (posts.length > 0 && nextPosts.length > 0 && posts[0].id !== nextPosts[0].id);
-
- if (!this.checkBottom() && hasNewPosts) {
- this.setUnreadsBelow(nextPosts, nextProps.currentUserId);
- }
- }
- }
-
- componentWillUpdate() {
- if (this.refs.postlist) {
- this.previousScrollTop = this.refs.postlist.scrollTop;
- this.previousScrollHeight = this.refs.postlist.scrollHeight;
- this.previousClientHeight = this.refs.postlist.clientHeight;
- }
- }
-
- componentDidUpdate(prevProps, prevState) {
- // Do not update scrolling unless posts, visibility or intro message change
- if (this.props.posts === prevProps.posts && this.props.postVisibility === prevProps.postVisibility && this.state.atEnd === prevState.atEnd) {
- return;
- }
-
- const prevPosts = prevProps.posts;
- const posts = this.props.posts;
- const postList = this.refs.postlist;
-
- if (!postList) {
- return;
- }
-
- // Scroll to focused post on first load
- const focusedPost = this.refs[this.props.focusedPostId];
- if (focusedPost && this.props.posts) {
- if (!this.hasScrolledToFocusedPost) {
- const element = ReactDOM.findDOMNode(focusedPost);
- const rect = element.getBoundingClientRect();
- const listHeight = postList.clientHeight / 2;
- postList.scrollTop += rect.top - listHeight;
- } else if (this.previousScrollHeight !== postList.scrollHeight && posts[0].id === prevPosts[0].id) {
- postList.scrollTop = this.previousScrollTop + (postList.scrollHeight - this.previousScrollHeight);
- }
- return;
- }
-
- // Scroll to new message indicator or bottom on first load
- const messageSeparator = this.refs.newMessageSeparator;
- if (messageSeparator && !this.hasScrolledToNewMessageSeparator) {
- const element = ReactDOM.findDOMNode(messageSeparator);
- element.scrollIntoView();
- if (!this.checkBottom()) {
- this.setUnreadsBelow(posts, this.props.currentUserId);
- }
- return;
- } else if (postList && !this.hasScrolledToNewMessageSeparator) {
- postList.scrollTop = postList.scrollHeight;
- this.atBottom = true;
- return;
- }
-
- if (postList && prevPosts && posts && posts[0] && prevPosts[0]) {
- // A new message was posted, so scroll to bottom if user
- // was already scrolled close to bottom
- let doScrollToBottom = false;
- const postId = posts[0].id;
- const prevPostId = prevPosts[0].id;
- const pendingPostId = posts[0].pending_post_id;
- if (postId !== prevPostId && pendingPostId !== prevPostId) {
- // If already scrolled to bottom
- if (this.atBottom) {
- doScrollToBottom = true;
- }
-
- // If new post was ephemeral
- if (Utils.isPostEphemeral(posts[0])) {
- doScrollToBottom = true;
- }
- }
-
- if (doScrollToBottom) {
- this.atBottom = true;
- postList.scrollTop = postList.scrollHeight;
- return;
- }
-
- // New posts added at the top, maintain scroll position
- if (this.previousScrollHeight !== postList.scrollHeight && posts[0].id === prevPosts[0].id) {
- postList.scrollTop = this.previousScrollTop + (postList.scrollHeight - this.previousScrollHeight);
- }
- }
- }
-
- setUnreadsBelow = (posts, currentUserId) => {
- const unViewedCount = posts.reduce((count, post) => {
- if (post.create_at > this.state.lastViewed &&
- post.user_id !== currentUserId &&
- post.state !== Constants.POST_DELETED) {
- return count + 1;
- }
- return count;
- }, 0);
- this.setState({unViewedCount});
- }
-
- handleScrollStop = () => {
- this.setState({
- isScrolling: false
- });
- }
-
- checkBottom = () => {
- if (!this.refs.postlist) {
- return true;
- }
-
- // No scroll bar so we're at the bottom
- if (this.refs.postlist.scrollHeight <= this.refs.postlist.clientHeight) {
- return true;
- }
-
- return this.refs.postlist.clientHeight + this.refs.postlist.scrollTop >= this.refs.postlist.scrollHeight - CLOSE_TO_BOTTOM_SCROLL_MARGIN;
- }
-
- handleResize = (forceScrollToBottom) => {
- const postList = this.refs.postlist;
- const messageSeparator = this.refs.newMessageSeparator;
- const doScrollToBottom = this.atBottom || forceScrollToBottom;
-
- if (postList) {
- if (doScrollToBottom) {
- postList.scrollTop = postList.scrollHeight;
- } else if (!this.hasScrolled && messageSeparator) {
- const element = ReactDOM.findDOMNode(messageSeparator);
- element.scrollIntoView();
- }
-
- this.previousScrollHeight = postList.scrollHeight;
- this.previousScrollTop = postList.scrollTop;
- this.previousClientHeight = postList.clientHeight;
-
- this.atBottom = this.checkBottom();
- }
- }
-
- loadPosts = async (channelId, focusedPostId) => {
- let posts;
- if (focusedPostId) {
- const getPostThreadAsync = this.props.actions.getPostThread(focusedPostId, false);
- const getPostsBeforeAsync = this.props.actions.getPostsBefore(channelId, focusedPostId, 0, POSTS_PER_PAGE);
- const getPostsAfterAsync = this.props.actions.getPostsAfter(channelId, focusedPostId, 0, POSTS_PER_PAGE);
-
- posts = await getPostsBeforeAsync;
- await getPostsAfterAsync;
- await getPostThreadAsync;
-
- this.hasScrolledToFocusedPost = true;
- } else {
- posts = await this.props.actions.getPosts(channelId, 0, POSTS_PER_PAGE);
- this.hasScrolledToNewMessageSeparator = true;
- }
-
- if (posts && posts.order.length < POSTS_PER_PAGE) {
- this.setState({atEnd: true});
- }
- }
-
- loadMorePosts = (e) => {
- if (e) {
- e.preventDefault();
- }
-
- this.props.actions.increasePostVisibility(this.props.channel.id, this.props.focusedPostId).then((moreToLoad) => {
- this.setState({atEnd: !moreToLoad && this.props.posts.length < this.props.postVisibility});
- });
- }
-
- handleScroll = () => {
- // Only count as user scroll if we've already performed our first load scroll
- this.hasScrolled = this.hasScrolledToNewMessageSeparator || this.hasScrolledToFocusedPost;
- if (!this.refs.postlist) {
- return;
- }
-
- this.previousScrollTop = this.refs.postlist.scrollTop;
-
- if (this.refs.postlist.scrollHeight === this.previousScrollHeight) {
- this.atBottom = this.checkBottom();
- }
-
- this.updateFloatingTimestamp();
-
- if (!this.state.isScrolling) {
- this.setState({
- isScrolling: true
- });
- }
-
- if (this.checkBottom()) {
- this.setState({
- lastViewed: new Date().getTime(),
- unViewedCount: 0,
- isScrolling: false
- });
- }
-
- this.scrollStopAction.fireAfter(Constants.SCROLL_DELAY);
- }
-
- updateFloatingTimestamp = () => {
- // skip this in non-mobile view since that's when the timestamp is visible
- if (!Utils.isMobile()) {
- return;
- }
-
- if (this.props.posts) {
- // iterate through posts starting at the bottom since users are more likely to be viewing newer posts
- for (let i = 0; i < this.props.posts.length; i++) {
- const post = this.props.posts[i];
- const element = this.refs[post.id];
-
- if (!element || !element.domNode || element.domNode.offsetTop + element.domNode.clientHeight <= this.refs.postlist.scrollTop) {
- // this post is off the top of the screen so the last one is at the top of the screen
- let topPost;
-
- if (i > 0) {
- topPost = this.props.posts[i - 1];
- } else {
- // the first post we look at should always be on the screen, but handle that case anyway
- topPost = post;
- }
-
- if (!this.state.topPost || topPost.id !== this.state.topPost.id) {
- this.setState({
- topPost
- });
- }
-
- break;
- }
- }
- }
- }
-
- scrollToBottom = () => {
- if (this.refs.postlist) {
- this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
- }
- }
-
- createPosts = (posts) => {
- const postCtls = [];
- let previousPostDay = new Date(0);
- const currentUserId = this.props.currentUserId;
- const lastViewed = this.props.lastViewedAt || 0;
-
- let renderedLastViewed = false;
-
- for (let i = posts.length - 1; i >= 0; i--) {
- const post = posts[i];
-
- if (post == null) {
- continue;
- }
-
- const postCtl = (
- <Post
- ref={post.id}
- key={'post ' + (post.id || post.pending_post_id)}
- post={post}
- lastPostCount={(i >= 0 && i < Constants.TEST_ID_COUNT) ? i : -1}
- getPostList={this.getPostList}
- />
- );
-
- const currentPostDay = Utils.getDateForUnixTicks(post.create_at);
- if (currentPostDay.toDateString() !== previousPostDay.toDateString()) {
- postCtls.push(
- <div
- key={currentPostDay.toDateString()}
- className='date-separator'
- >
- <hr className='separator__hr'/>
- <div className='separator__text'>
- <FormattedDate
- value={currentPostDay}
- weekday='short'
- month='short'
- day='2-digit'
- year='numeric'
- />
- </div>
- </div>
- );
- }
-
- if (post.user_id !== currentUserId &&
- lastViewed !== 0 &&
- post.create_at > lastViewed &&
- !Utils.isPostEphemeral(post) &&
- !renderedLastViewed) {
- renderedLastViewed = true;
-
- // Temporary fix to solve ie11 rendering issue
- let newSeparatorId = '';
- if (!UserAgent.isInternetExplorer()) {
- newSeparatorId = 'new_message_' + post.id;
- }
- postCtls.push(
- <div
- id={newSeparatorId}
- key='unviewed'
- ref='newMessageSeparator'
- className='new-separator'
- >
- <hr
- className='separator__hr'
- />
- <div className='separator__text'>
- <FormattedMessage
- id='posts_view.newMsg'
- defaultMessage='New Messages'
- />
- </div>
- </div>
- );
- }
-
- postCtls.push(postCtl);
- previousPostDay = currentPostDay;
- }
-
- return postCtls;
- }
-
- getPostList = () => {
- return this.refs.postlist;
- }
-
- render() {
- const posts = this.props.posts;
- const channel = this.props.channel;
-
- if (posts == null || channel == null) {
- return (
- <div id='post-list'>
- <LoadingScreen
- position='absolute'
- key='loading'
- />
- </div>
- );
- }
-
- let topRow;
- if (this.state.atEnd) {
- topRow = createChannelIntroMessage(channel, this.props.fullWidth);
- } else if (this.props.postVisibility >= Constants.MAX_POST_VISIBILITY) {
- topRow = (
- <div className='post-list__loading post-list__loading-search'>
- <FormattedMessage
- id='posts_view.maxLoaded'
- defaultMessage='Looking for a specific message? Try searching for it'
- />
- </div>
- );
- } else {
- topRow = (
- <a
- ref='loadmoretop'
- className='more-messages-text theme'
- href='#'
- onClick={this.loadMorePosts}
- >
- <FormattedMessage
- id='posts_view.loadMore'
- defaultMessage='Load more messages'
- />
- </a>
- );
- }
-
- const topPostCreateAt = this.state.topPost ? this.state.topPost.create_at : 0;
-
- let postVisibility = this.props.postVisibility;
-
- // In focus mode there's an extra (Constants.POST_CHUNK_SIZE / 2) posts to show
- if (this.props.focusedPostId) {
- postVisibility += Constants.POST_CHUNK_SIZE / 2;
- }
-
- return (
- <div id='post-list'>
- <FloatingTimestamp
- isScrolling={this.state.isScrolling}
- isMobile={Utils.isMobile()}
- createAt={topPostCreateAt}
- />
- <ScrollToBottomArrows
- isScrolling={this.state.isScrolling}
- atBottom={this.atBottom}
- onClick={this.scrollToBottom}
- />
- <NewMessageIndicator
- newMessages={this.state.unViewedCount}
- onClick={this.scrollToBottom}
- />
- <div
- ref='postlist'
- className='post-list-holder-by-time'
- key={'postlist-' + channel.id}
- onScroll={this.handleScroll}
- >
- <div className='post-list__table'>
- <div
- ref='postlistcontent'
- className='post-list__content'
- >
- {topRow}
- {this.createPosts(posts.slice(0, postVisibility))}
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_message_view/index.js b/webapp/components/post_view/post_message_view/index.js
deleted file mode 100644
index 84682eb89..000000000
--- a/webapp/components/post_view/post_message_view/index.js
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {getCustomEmojisByName} 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 = getCustomEmojisByName(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
deleted file mode 100644
index 348748450..000000000
--- a/webapp/components/post_view/post_message_view/post_message_view.jsx
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {Parser, ProcessNodeDefinitions} from 'html-to-react';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-import AtMention from 'components/at_mention';
-import MarkdownImage from 'components/markdown_image';
-
-import store from 'stores/redux_store.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 {getChannelsNameMapInCurrentTeam} from 'mattermost-redux/selectors/entities/channels';
-import {Posts} from 'mattermost-redux/constants';
-
-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),
-
- /*
- * 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,
-
- /**
- * Set to render post body compactly
- */
- compactDisplay: PropTypes.bool,
-
- /**
- * Flags if the post_message_view is for the RHS (Reply).
- */
- isRHS: PropTypes.bool,
-
- /**
- * Flags if the post_message_view is for the RHS (Reply).
- */
- hasMention: PropTypes.bool
- };
-
- static defaultProps = {
- options: {},
- mentionKeys: [],
- isRHS: false,
- hasMention: false
- };
-
- 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>
- );
- }
-
- postMessageHtmlToComponent(html) {
- const parser = new Parser();
- const attrib = 'data-mention';
- const processNodeDefinitions = new ProcessNodeDefinitions(React);
-
- function isValidNode() {
- return true;
- }
-
- const processingInstructions = [
- {
- replaceChildren: true,
- shouldProcessNode: (node) => node.attribs && node.attribs[attrib],
- processNode: (node) => {
- const mentionName = node.attribs[attrib];
-
- return (
- <AtMention
- mentionName={mentionName}
- isRHS={this.props.isRHS}
- hasMention={this.props.hasMention}
- />
- );
- }
- },
- {
- shouldProcessNode: (node) => node.type === 'tag' && node.name === 'img',
- processNode: (node) => {
- const {
- class: className,
- ...attribs
- } = node.attribs;
-
- return (
- <MarkdownImage
- className={className}
- {...attribs}
- />
- );
- }
- },
- {
- shouldProcessNode: () => true,
- processNode: processNodeDefinitions.processDefaultNode
- }
- ];
-
- return parser.parseWithInstructions(html, isValidNode, processingInstructions);
- }
-
- 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,
- atMentions: true,
- 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);
- }
-
- let message = this.props.post.message;
- const isEphemeral = Utils.isPostEphemeral(this.props.post);
- if (this.props.compactDisplay && isEphemeral) {
- const visibleMessage = Utils.localizeMessage('post_info.message.visible.compact', ' (Only visible to you)');
- message = message.concat(visibleMessage);
- }
- const htmlFormattedText = TextFormatting.formatText(message, options);
- const postMessageComponent = this.postMessageHtmlToComponent(htmlFormattedText);
-
- return (
- <div>
- <span
- id={postId}
- className='post-message__text'
- onClick={Utils.handleFormattedTextClick}
- >
- {postMessageComponent}
- </span>
- {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
deleted file mode 100644
index c134e1a7a..000000000
--- a/webapp/components/post_view/post_message_view/system_message_helpers.jsx
+++ /dev/null
@@ -1,232 +0,0 @@
-// 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);
-}
diff --git a/webapp/components/post_view/post_time.jsx b/webapp/components/post_view/post_time.jsx
deleted file mode 100644
index 2619c6807..000000000
--- a/webapp/components/post_view/post_time.jsx
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Constants from 'utils/constants.jsx';
-import {getDateForUnixTicks, isMobile, updateWindowDimensions} from 'utils/utils.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {Link} from 'react-router/es6';
-import TeamStore from 'stores/team_store.jsx';
-
-export default class PostTime extends React.PureComponent {
- static propTypes = {
-
- /*
- * If true, time will be rendered as a permalink to the post
- */
- isPermalink: PropTypes.bool.isRequired,
-
- /*
- * The time to display
- */
- eventTime: PropTypes.number.isRequired,
-
- /*
- * Set to display using 24 hour format
- */
- useMilitaryTime: PropTypes.bool,
-
- /*
- * The post id of posting being rendered
- */
- postId: PropTypes.string
- };
-
- static defaultProps = {
- eventTime: 0,
- useMilitaryTime: false
- };
-
- constructor(props) {
- super(props);
-
- this.state = {
- currentTeamDisplayName: TeamStore.getCurrent().name,
- width: '',
- height: ''
- };
- }
-
- componentDidMount() {
- this.intervalId = setInterval(() => {
- this.forceUpdate();
- }, Constants.TIME_SINCE_UPDATE_INTERVAL);
- window.addEventListener('resize', () => {
- updateWindowDimensions(this);
- });
- }
-
- componentWillUnmount() {
- clearInterval(this.intervalId);
- window.removeEventListener('resize', () => {
- updateWindowDimensions(this);
- });
- }
-
- renderTimeTag() {
- const date = getDateForUnixTicks(this.props.eventTime);
-
- return (
- <time
- className='post__time'
- dateTime={date.toISOString()}
- title={date}
- >
- {date.toLocaleString('en', {hour: '2-digit', minute: '2-digit', hour12: !this.props.useMilitaryTime})}
- </time>
- );
- }
-
- render() {
- if (isMobile() || !this.props.isPermalink) {
- return this.renderTimeTag();
- }
-
- return (
- <Link
- to={`/${this.state.currentTeamDisplayName}/pl/${this.props.postId}`}
- target='_blank'
- className='post__permalink'
- >
- {this.renderTimeTag()}
- </Link>
- );
- }
-}
diff --git a/webapp/components/post_view/reaction/index.js b/webapp/components/post_view/reaction/index.js
deleted file mode 100644
index 74edd1324..000000000
--- a/webapp/components/post_view/reaction/index.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getCurrentUserId, makeGetProfilesForReactions} from 'mattermost-redux/selectors/entities/users';
-import {getMissingProfilesByIds} from 'mattermost-redux/actions/users';
-import {addReaction, removeReaction} from 'mattermost-redux/actions/posts';
-import {getEmojiImageUrl} from 'mattermost-redux/utils/emoji_utils';
-import * as Emoji from 'utils/emoji.jsx';
-
-import Reaction from './reaction.jsx';
-
-function makeMapStateToProps() {
- const getProfilesForReactions = makeGetProfilesForReactions();
-
- return function mapStateToProps(state, ownProps) {
- const profiles = getProfilesForReactions(state, ownProps.reactions);
- let emoji;
- if (Emoji.EmojiIndicesByAlias.has(ownProps.emojiName)) {
- emoji = Emoji.Emojis[Emoji.EmojiIndicesByAlias.get(ownProps.emojiName)];
- } else {
- emoji = ownProps.emojis.get(ownProps.emojiName);
- }
-
- let emojiImageUrl = '';
- if (emoji) {
- emojiImageUrl = getEmojiImageUrl(emoji);
- }
-
- return {
- ...ownProps,
- profiles,
- otherUsersCount: ownProps.reactions.length - profiles.length,
- currentUserId: getCurrentUserId(state),
- reactionCount: ownProps.reactions.length,
- emojiImageUrl
- };
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- addReaction,
- removeReaction,
- getMissingProfilesByIds
- }, dispatch)
- };
-}
-
-export default connect(makeMapStateToProps, mapDispatchToProps)(Reaction);
diff --git a/webapp/components/post_view/reaction/reaction.jsx b/webapp/components/post_view/reaction/reaction.jsx
deleted file mode 100644
index 673f8fd7f..000000000
--- a/webapp/components/post_view/reaction/reaction.jsx
+++ /dev/null
@@ -1,254 +0,0 @@
-// 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 {OverlayTrigger, Tooltip} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-
-import * as Utils from 'utils/utils.jsx';
-
-export default class Reaction extends React.PureComponent {
- static propTypes = {
-
- /*
- * The post to render the reaction for
- */
- post: PropTypes.object.isRequired,
-
- /*
- * The user id of the logged in user
- */
- currentUserId: PropTypes.string.isRequired,
-
- /*
- * The name of the emoji for the reaction
- */
- emojiName: PropTypes.string.isRequired,
-
- /*
- * The number of reactions to this post for this emoji
- */
- reactionCount: PropTypes.number.isRequired,
-
- /*
- * Array of users who reacted to this post
- */
- profiles: PropTypes.array.isRequired,
-
- /*
- * The number of users not in the profile list who have reacted with this emoji
- */
- otherUsersCount: PropTypes.number.isRequired,
-
- /*
- * Array of reactions by user
- */
- reactions: PropTypes.arrayOf(PropTypes.object).isRequired,
-
- /*
- * The URL of the emoji image
- */
- emojiImageUrl: PropTypes.string.isRequired,
-
- actions: PropTypes.shape({
-
- /*
- * Function to add a reaction to a post
- */
- addReaction: PropTypes.func.isRequired,
-
- /*
- * Function to get non-loaded profiles by id
- */
- getMissingProfilesByIds: PropTypes.func.isRequired,
-
- /*
- * Function to remove a reaction from a post
- */
- removeReaction: PropTypes.func.isRequired
- })
- }
-
- constructor(props) {
- super(props);
-
- this.addReaction = this.addReaction.bind(this);
- this.removeReaction = this.removeReaction.bind(this);
- }
-
- addReaction(e) {
- e.preventDefault();
- this.props.actions.addReaction(this.props.post.id, this.props.emojiName);
- }
-
- removeReaction(e) {
- e.preventDefault();
- this.props.actions.removeReaction(this.props.post.id, this.props.emojiName);
- }
-
- loadMissingProfiles = () => {
- const ids = this.props.reactions.map((reaction) => reaction.user_id);
- this.props.actions.getMissingProfilesByIds(ids);
- }
-
- render() {
- if (!this.props.emojiImageUrl) {
- return null;
- }
-
- let currentUserReacted = false;
- const users = [];
- const otherUsersCount = this.props.otherUsersCount;
- for (const user of this.props.profiles) {
- if (user.id === this.props.currentUserId) {
- currentUserReacted = true;
- } else {
- users.push(Utils.displayUsernameForUser(user));
- }
- }
-
- // Sort users in alphabetical order with "you" being first if the current user reacted
- users.sort();
- if (currentUserReacted) {
- users.unshift(Utils.localizeMessage('reaction.you', 'You'));
- }
-
- let names;
- if (otherUsersCount > 0) {
- if (users.length > 0) {
- names = (
- <FormattedMessage
- id='reaction.usersAndOthersReacted'
- defaultMessage='{users} and {otherUsers, number} other {otherUsers, plural, one {user} other {users}}'
- values={{
- users: users.join(', '),
- otherUsers: otherUsersCount
- }}
- />
- );
- } else {
- names = (
- <FormattedMessage
- id='reaction.othersReacted'
- defaultMessage='{otherUsers, number} {otherUsers, plural, one {user} other {users}}'
- values={{
- otherUsers: otherUsersCount
- }}
- />
- );
- }
- } else if (users.length > 1) {
- names = (
- <FormattedMessage
- id='reaction.usersReacted'
- defaultMessage='{users} and {lastUser}'
- values={{
- users: users.slice(0, -1).join(', '),
- lastUser: users[users.length - 1]
- }}
- />
- );
- } else {
- names = users[0];
- }
-
- let reactionVerb;
- if (users.length + otherUsersCount > 1) {
- if (currentUserReacted) {
- reactionVerb = (
- <FormattedMessage
- id='reaction.reactionVerb.youAndUsers'
- defaultMessage='reacted'
- />
- );
- } else {
- reactionVerb = (
- <FormattedMessage
- id='reaction.reactionVerb.users'
- defaultMessage='reacted'
- />
- );
- }
- } else if (currentUserReacted) {
- reactionVerb = (
- <FormattedMessage
- id='reaction.reactionVerb.you'
- defaultMessage='reacted'
- />
- );
- } else {
- reactionVerb = (
- <FormattedMessage
- id='reaction.reactionVerb.user'
- defaultMessage='reacted'
- />
- );
- }
-
- const tooltip = (
- <FormattedMessage
- id='reaction.reacted'
- defaultMessage='{users} {reactionVerb} with {emoji}'
- values={{
- users: <b>{names}</b>,
- reactionVerb,
- emoji: <b>{':' + this.props.emojiName + ':'}</b>
- }}
- />
- );
-
- let handleClick;
- let clickTooltip;
- let className = 'post-reaction';
- if (currentUserReacted) {
- handleClick = this.removeReaction;
- clickTooltip = (
- <FormattedMessage
- id='reaction.clickToRemove'
- defaultMessage='(click to remove)'
- />
- );
-
- className += ' post-reaction--current-user';
- } else {
- handleClick = this.addReaction;
- clickTooltip = (
- <FormattedMessage
- id='reaction.clickToAdd'
- defaultMessage='(click to add)'
- />
- );
- }
-
- return (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={1000}
- placement='top'
- shouldUpdatePosition={true}
- overlay={
- <Tooltip id={`${this.props.post.id}-${this.props.emojiName}-reaction`}>
- {tooltip}
- <br/>
- {clickTooltip}
- </Tooltip>
- }
- onEnter={this.loadMissingProfiles}
- >
- <div
- className={className}
- onClick={handleClick}
- >
- <span
- className='post-reaction__emoji emoticon'
- style={{backgroundImage: 'url(' + this.props.emojiImageUrl + ')'}}
- />
- <span className='post-reaction__count'>
- {this.props.reactionCount}
- </span>
- </div>
- </OverlayTrigger>
- );
- }
-}
diff --git a/webapp/components/post_view/reaction_list/index.js b/webapp/components/post_view/reaction_list/index.js
deleted file mode 100644
index ee807ca88..000000000
--- a/webapp/components/post_view/reaction_list/index.js
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {makeGetReactionsForPost} from 'mattermost-redux/selectors/entities/posts';
-import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
-
-import * as Actions from 'mattermost-redux/actions/posts';
-
-import ReactionList from './reaction_list.jsx';
-
-function makeMapStateToProps() {
- const getReactionsForPost = makeGetReactionsForPost();
-
- return function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- reactions: getReactionsForPost(state, ownProps.post.id),
- emojis: getCustomEmojisByName(state)
- };
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getReactionsForPost: Actions.getReactionsForPost
- }, dispatch)
- };
-}
-
-export default connect(makeMapStateToProps, mapDispatchToProps)(ReactionList);
diff --git a/webapp/components/post_view/reaction_list/reaction_list.jsx b/webapp/components/post_view/reaction_list/reaction_list.jsx
deleted file mode 100644
index 4d2f3a5fc..000000000
--- a/webapp/components/post_view/reaction_list/reaction_list.jsx
+++ /dev/null
@@ -1,88 +0,0 @@
-// 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 {postListScrollChange} from 'actions/global_actions.jsx';
-
-import Reaction from 'components/post_view/reaction';
-
-export default class ReactionListView extends React.PureComponent {
- static propTypes = {
-
- /**
- * The post to render reactions for
- */
- post: PropTypes.object.isRequired,
-
- /**
- * The reactions to render
- */
- reactions: PropTypes.arrayOf(PropTypes.object),
-
- /**
- * The emojis for the different reactions
- */
- emojis: PropTypes.object.isRequired,
- actions: PropTypes.shape({
-
- /**
- * Function to get reactions for a post
- */
- getReactionsForPost: PropTypes.func.isRequired
- })
- }
-
- componentDidMount() {
- if (this.props.post.has_reactions) {
- this.props.actions.getReactionsForPost(this.props.post.id);
- }
- }
-
- componentDidUpdate(prevProps) {
- if (this.props.reactions !== prevProps.reactions) {
- postListScrollChange();
- }
- }
-
- render() {
- if (!this.props.post.has_reactions || (this.props.reactions && this.props.reactions.length === 0)) {
- return null;
- }
-
- const reactionsByName = new Map();
- const emojiNames = [];
-
- if (this.props.reactions) {
- for (const reaction of this.props.reactions) {
- const emojiName = reaction.emoji_name;
-
- if (reactionsByName.has(emojiName)) {
- reactionsByName.get(emojiName).push(reaction);
- } else {
- emojiNames.push(emojiName);
- reactionsByName.set(emojiName, [reaction]);
- }
- }
- }
-
- const children = emojiNames.map((emojiName) => {
- return (
- <Reaction
- key={emojiName}
- post={this.props.post}
- emojiName={emojiName}
- reactions={reactionsByName.get(emojiName) || []}
- emojis={this.props.emojis}
- />
- );
- });
-
- return (
- <div className='post-reaction-list'>
- {children}
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/scroll_to_bottom_arrows.jsx b/webapp/components/post_view/scroll_to_bottom_arrows.jsx
deleted file mode 100644
index 73f8e6527..000000000
--- a/webapp/components/post_view/scroll_to_bottom_arrows.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-
-import Constants from 'utils/constants.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default function ScrollToBottomArrows(props) {
- // only show on mobile
- if ($(window).width() > 768) {
- return <noscript/>;
- }
-
- let className = 'post-list__arrows';
- if (props.isScrolling && !props.atBottom) {
- className += ' scrolling';
- }
-
- return (
- <div
- className={className}
- onClick={props.onClick}
- >
- <span dangerouslySetInnerHTML={{__html: Constants.SCROLL_BOTTOM_ICON}}/>
- </div>
- );
-}
-
-ScrollToBottomArrows.propTypes = {
- isScrolling: PropTypes.bool.isRequired,
- atBottom: PropTypes.bool.isRequired,
- onClick: PropTypes.func.isRequired
-};