mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
* #4665 Added EmojiPicker Work primarily by @broernr-de and @harrison on pre-release.mattermost.com * Final fixes to handle custom emojis from internal review and single merge error * ESLint fixes * CSS changes and other code to support emoji picker in reply window * Fix for file upload and emoji picker icon positions on post and comment. RHS emoji picker appearing see-through at this time. * Fix for two ESLint issues. * covered most of feedback: RHS emoji picker looks correct color-wise RHS emoji picker dynamically positions against height of thread size (post + reply messages) escape closes emoji window search box focused on open ESLint fixes against other files oversized emoji preview fixes * Adding in 'outside click' eventing to dismiss the emoji window * Changing some formatting to fix mismatch between my local eslant rules and jenkins. * adding alternative import method due to downstream testing errors * yet another attempt to retain functionality and pass tests - skipping import of browser store * fix for feedback items 5 and 7: * move search to float on top with stylistic changes * whitespace in the header (+1 squashed commit) Squashed commits: [6a26d32] changes that address items 1, 2, 6, 8, and 9 of latest feedback * Fix for attachment preview location on mobile * Fix for latest rounds of feedback * fixing eslint issue * making emojipicker sprite based, fixing alignments * Fix for emoji quality, fixing some behavior (hover background and cursor settings) undoing config changes * Preview feature for emojis * Adjustments to config file, and changing layout/design of attachment and emoji icon. * manual revert from master branch for config.json * reverting paperclip and fixing alignments. Additionally fixing inadvertent display of picker on mobile. * CSS changes to try to fix the hover behavior - currently working for emoji picker (when enabled), but hover for attachment isn't working * Made suggested changes by jwilander except for jQuery removal * Adding hover for both icons * removal of some usages of jQuery * Fix for two layout issues on IE11/Edge * UI improvements for emoji picker * Fix for many minor display issues * fix for additional appearance items * fix to two minor UI items * A little extra padding for IE11 * fix for IE11 scroll issue, and removing align attribute on img tag which was throwing js error * fixes some display issues on firefox * fix for uneven sides of emojis * fix for eslint issues that I didn't introduce * fix for missing bottom edge of RHS emojipicker. also fixing text overlapping icons on text area (including RHS) * Update "emoji selector" to "emoji picker" * changes for code review - removal of ..getDOMNode - use sprite imagery for emoji preview - remove lastBlurAt from state as it wasn't used * fixes for: - fake custom emoji preview in picker - RHS scrollbar on preview * fix for minor alignment of preview emoji
589 lines
18 KiB
JavaScript
589 lines
18 KiB
JavaScript
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
import UserProfile from './user_profile.jsx';
|
|
import PostBodyAdditionalContent from 'components/post_view/components/post_body_additional_content.jsx';
|
|
import PostMessageContainer from 'components/post_view/components/post_message_container.jsx';
|
|
import FileAttachmentListContainer from './file_attachment_list_container.jsx';
|
|
import ProfilePicture from 'components/profile_picture.jsx';
|
|
import ReactionListContainer from 'components/post_view/components/reaction_list_container.jsx';
|
|
import RhsDropdown from 'components/rhs_dropdown.jsx';
|
|
|
|
import ChannelStore from 'stores/channel_store.jsx';
|
|
import UserStore from 'stores/user_store.jsx';
|
|
import TeamStore from 'stores/team_store.jsx';
|
|
|
|
import * as GlobalActions from 'actions/global_actions.jsx';
|
|
import {flagPost, unflagPost, pinPost, unpinPost} from 'actions/post_actions.jsx';
|
|
|
|
import * as Utils from 'utils/utils.jsx';
|
|
import * as PostUtils from 'utils/post_utils.jsx';
|
|
|
|
import Constants from 'utils/constants.jsx';
|
|
import DelayedAction from 'utils/delayed_action.jsx';
|
|
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
|
|
|
|
import {FormattedMessage} from 'react-intl';
|
|
|
|
import React from 'react';
|
|
import {Link} from 'react-router/es6';
|
|
|
|
export default class RhsRootPost extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.handlePermalink = this.handlePermalink.bind(this);
|
|
this.flagPost = this.flagPost.bind(this);
|
|
this.unflagPost = this.unflagPost.bind(this);
|
|
this.pinPost = this.pinPost.bind(this);
|
|
this.unpinPost = this.unpinPost.bind(this);
|
|
|
|
this.canEdit = false;
|
|
this.canDelete = false;
|
|
this.editDisableAction = new DelayedAction(this.handleEditDisable);
|
|
|
|
this.state = {
|
|
currentTeamDisplayName: TeamStore.getCurrent().name,
|
|
width: '',
|
|
height: ''
|
|
};
|
|
}
|
|
|
|
componentDidMount() {
|
|
window.addEventListener('resize', () => {
|
|
Utils.updateWindowDimensions(this);
|
|
});
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
window.removeEventListener('resize', () => {
|
|
Utils.updateWindowDimensions(this);
|
|
});
|
|
}
|
|
|
|
handlePermalink(e) {
|
|
e.preventDefault();
|
|
GlobalActions.showGetPostLinkModal(this.props.post);
|
|
}
|
|
|
|
handleEditDisable() {
|
|
this.canEdit = false;
|
|
}
|
|
|
|
shouldComponentUpdate(nextProps) {
|
|
if (nextProps.status !== this.props.status) {
|
|
return true;
|
|
}
|
|
|
|
if (nextProps.isBusy !== this.props.isBusy) {
|
|
return true;
|
|
}
|
|
|
|
if (nextProps.compactDisplay !== this.props.compactDisplay) {
|
|
return true;
|
|
}
|
|
|
|
if (nextProps.useMilitaryTime !== this.props.useMilitaryTime) {
|
|
return true;
|
|
}
|
|
|
|
if (nextProps.isFlagged !== this.props.isFlagged) {
|
|
return true;
|
|
}
|
|
|
|
if (nextProps.previewCollapsed !== this.props.previewCollapsed) {
|
|
return true;
|
|
}
|
|
|
|
if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
|
|
return true;
|
|
}
|
|
|
|
if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
|
|
return true;
|
|
}
|
|
|
|
if (!Utils.areObjectsEqual(nextProps.currentUser, this.props.currentUser)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
flagPost(e) {
|
|
e.preventDefault();
|
|
flagPost(this.props.post.id);
|
|
}
|
|
|
|
unflagPost(e) {
|
|
e.preventDefault();
|
|
unflagPost(this.props.post.id);
|
|
}
|
|
|
|
timeTag(post, timeOptions) {
|
|
return (
|
|
<time
|
|
className='post__time'
|
|
dateTime={Utils.getDateForUnixTicks(post.create_at).toISOString()}
|
|
>
|
|
{Utils.getDateForUnixTicks(post.create_at).toLocaleString('en', timeOptions)}
|
|
</time>
|
|
);
|
|
}
|
|
|
|
renderTimeTag(post, timeOptions) {
|
|
return Utils.isMobile() ?
|
|
this.timeTag(post, timeOptions) :
|
|
(
|
|
<Link
|
|
to={`/${this.state.currentTeamDisplayName}/pl/${post.id}`}
|
|
target='_blank'
|
|
className='post__permalink'
|
|
>
|
|
{this.timeTag(post, timeOptions)}
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
pinPost(e) {
|
|
e.preventDefault();
|
|
pinPost(this.props.post.channel_id, this.props.post.id);
|
|
}
|
|
|
|
unpinPost(e) {
|
|
e.preventDefault();
|
|
unpinPost(this.props.post.channel_id, this.props.post.id);
|
|
}
|
|
|
|
render() {
|
|
const post = this.props.post;
|
|
const user = this.props.user;
|
|
const mattermostLogo = Constants.MATTERMOST_ICON_SVG;
|
|
var timestamp = user ? user.last_picture_update : 0;
|
|
var channel = ChannelStore.get(post.channel_id);
|
|
const flagIcon = Constants.FLAG_ICON_SVG;
|
|
|
|
this.canDelete = PostUtils.canDeletePost(post);
|
|
this.canEdit = PostUtils.canEditPost(post, this.editDisableAction);
|
|
|
|
var type = 'Post';
|
|
if (post.root_id.length > 0) {
|
|
type = 'Comment';
|
|
}
|
|
|
|
var userCss = '';
|
|
if (UserStore.getCurrentId() === post.user_id) {
|
|
userCss = 'current--user';
|
|
}
|
|
|
|
var systemMessageClass = '';
|
|
if (PostUtils.isSystemMessage(post)) {
|
|
systemMessageClass = 'post--system';
|
|
}
|
|
|
|
var channelName;
|
|
if (channel) {
|
|
if (channel.type === 'D') {
|
|
channelName = (
|
|
<FormattedMessage
|
|
id='rhs_root.direct'
|
|
defaultMessage='Direct Message'
|
|
/>
|
|
);
|
|
} else {
|
|
channelName = channel.display_name;
|
|
}
|
|
}
|
|
|
|
var dropdownContents = [];
|
|
|
|
if (Utils.isMobile()) {
|
|
if (this.props.isFlagged) {
|
|
dropdownContents.push(
|
|
<li
|
|
key='mobileFlag'
|
|
role='presentation'
|
|
>
|
|
<a
|
|
href='#'
|
|
onClick={this.unflagPost}
|
|
>
|
|
<FormattedMessage
|
|
id='rhs_root.mobile.unflag'
|
|
defaultMessage='Unflag'
|
|
/>
|
|
</a>
|
|
</li>
|
|
);
|
|
} else {
|
|
dropdownContents.push(
|
|
<li
|
|
key='mobileFlag'
|
|
role='presentation'
|
|
>
|
|
<a
|
|
href='#'
|
|
onClick={this.flagPost}
|
|
>
|
|
<FormattedMessage
|
|
id='rhs_root.mobile.flag'
|
|
defaultMessage='Flag'
|
|
/>
|
|
</a>
|
|
</li>
|
|
);
|
|
}
|
|
}
|
|
|
|
dropdownContents.push(
|
|
<li
|
|
key='rhs-root-permalink'
|
|
role='presentation'
|
|
>
|
|
<a
|
|
href='#'
|
|
onClick={this.handlePermalink}
|
|
>
|
|
<FormattedMessage
|
|
id='rhs_root.permalink'
|
|
defaultMessage='Permalink'
|
|
/>
|
|
</a>
|
|
</li>
|
|
);
|
|
|
|
if (post.is_pinned) {
|
|
dropdownContents.push(
|
|
<li
|
|
key='rhs-root-unpin'
|
|
role='presentation'
|
|
>
|
|
<a
|
|
href='#'
|
|
onClick={this.unpinPost}
|
|
>
|
|
<FormattedMessage
|
|
id='rhs_root.unpin'
|
|
defaultMessage='Un-pin from channel'
|
|
/>
|
|
</a>
|
|
</li>
|
|
);
|
|
} else {
|
|
dropdownContents.push(
|
|
<li
|
|
key='rhs-root-pin'
|
|
role='presentation'
|
|
>
|
|
<a
|
|
href='#'
|
|
onClick={this.pinPost}
|
|
>
|
|
<FormattedMessage
|
|
id='rhs_root.pin'
|
|
defaultMessage='Pin to channel'
|
|
/>
|
|
</a>
|
|
</li>
|
|
);
|
|
}
|
|
|
|
if (this.canDelete) {
|
|
dropdownContents.push(
|
|
<li
|
|
key='rhs-root-delete'
|
|
role='presentation'
|
|
>
|
|
<a
|
|
href='#'
|
|
role='menuitem'
|
|
onClick={() => GlobalActions.showDeletePostModal(post, this.props.commentCount)}
|
|
>
|
|
<FormattedMessage
|
|
id='rhs_root.del'
|
|
defaultMessage='Delete'
|
|
/>
|
|
</a>
|
|
</li>
|
|
);
|
|
}
|
|
|
|
if (this.canEdit) {
|
|
dropdownContents.push(
|
|
<li
|
|
key='rhs-root-edit'
|
|
role='presentation'
|
|
className={this.canEdit ? '' : 'hide'}
|
|
>
|
|
<a
|
|
href='#'
|
|
role='menuitem'
|
|
data-toggle='modal'
|
|
data-target='#edit_post'
|
|
data-refocusid='#reply_textbox'
|
|
data-title={type}
|
|
data-message={post.message}
|
|
data-postid={post.id}
|
|
data-channelid={post.channel_id}
|
|
>
|
|
<FormattedMessage
|
|
id='rhs_root.edit'
|
|
defaultMessage='Edit'
|
|
/>
|
|
</a>
|
|
</li>
|
|
);
|
|
}
|
|
|
|
var rootOptions = '';
|
|
if (dropdownContents.length > 0) {
|
|
rootOptions = (
|
|
<RhsDropdown dropdownContents={dropdownContents}/>
|
|
);
|
|
}
|
|
|
|
let fileAttachment = null;
|
|
if (post.file_ids && post.file_ids.length > 0) {
|
|
fileAttachment = (
|
|
<FileAttachmentListContainer
|
|
post={post}
|
|
compactDisplay={this.props.compactDisplay}
|
|
/>
|
|
);
|
|
}
|
|
|
|
let userProfile = (
|
|
<UserProfile
|
|
user={user}
|
|
status={this.props.status}
|
|
isBusy={this.props.isBusy}
|
|
/>
|
|
);
|
|
let botIndicator;
|
|
|
|
if (post.props && post.props.from_webhook) {
|
|
if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') {
|
|
userProfile = (
|
|
<UserProfile
|
|
user={user}
|
|
overwriteName={post.props.override_username}
|
|
disablePopover={true}
|
|
/>
|
|
);
|
|
} else {
|
|
userProfile = (
|
|
<UserProfile
|
|
user={user}
|
|
disablePopover={true}
|
|
/>
|
|
);
|
|
}
|
|
|
|
botIndicator = <li className='col col__name bot-indicator'>{'BOT'}</li>;
|
|
} else if (PostUtils.isSystemMessage(post)) {
|
|
userProfile = (
|
|
<UserProfile
|
|
user={{}}
|
|
overwriteName={
|
|
<FormattedMessage
|
|
id='post_info.system'
|
|
defaultMessage='System'
|
|
/>
|
|
}
|
|
overwriteImage={Constants.SYSTEM_MESSAGE_PROFILE_IMAGE}
|
|
disablePopover={true}
|
|
/>
|
|
);
|
|
}
|
|
|
|
let status = this.props.status;
|
|
if (post.props && post.props.from_webhook === 'true') {
|
|
status = null;
|
|
}
|
|
|
|
let profilePic = (
|
|
<ProfilePicture
|
|
src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
|
|
status={status}
|
|
width='36'
|
|
height='36'
|
|
user={this.props.user}
|
|
isBusy={this.props.isBusy}
|
|
/>
|
|
);
|
|
|
|
if (post.props && post.props.from_webhook) {
|
|
profilePic = (
|
|
<ProfilePicture
|
|
src={PostUtils.getProfilePicSrcForPost(post, timestamp)}
|
|
width='36'
|
|
height='36'
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (PostUtils.isSystemMessage(post)) {
|
|
profilePic = (
|
|
<span
|
|
className='icon'
|
|
dangerouslySetInnerHTML={{__html: mattermostLogo}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
let compactClass = '';
|
|
let postClass = '';
|
|
if (this.props.compactDisplay) {
|
|
compactClass = 'post--compact';
|
|
|
|
if (post.props && post.props.from_webhook) {
|
|
profilePic = (
|
|
<ProfilePicture
|
|
src=''
|
|
/>
|
|
);
|
|
} else {
|
|
profilePic = (
|
|
<ProfilePicture
|
|
src=''
|
|
status={status}
|
|
user={this.props.user}
|
|
isBusy={this.props.isBusy}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
if (PostUtils.isEdited(this.props.post)) {
|
|
postClass += ' post--edited';
|
|
}
|
|
|
|
const profilePicContainer = (<div className='post__img'>{profilePic}</div>);
|
|
|
|
let flag;
|
|
let flagFunc;
|
|
let flagVisible = '';
|
|
let flagTooltip = (
|
|
<Tooltip id='flagTooltip'>
|
|
<FormattedMessage
|
|
id='flag_post.flag'
|
|
defaultMessage='Flag for follow up'
|
|
/>
|
|
</Tooltip>
|
|
);
|
|
if (this.props.isFlagged) {
|
|
flagVisible = 'visible';
|
|
flag = (
|
|
<span
|
|
className='icon'
|
|
dangerouslySetInnerHTML={{__html: flagIcon}}
|
|
/>
|
|
);
|
|
flagFunc = this.unflagPost;
|
|
flagTooltip = (
|
|
<Tooltip id='flagTooltip'>
|
|
<FormattedMessage
|
|
id='flag_post.unflag'
|
|
defaultMessage='Unflag'
|
|
/>
|
|
</Tooltip>
|
|
);
|
|
} else {
|
|
flag = (
|
|
<span
|
|
className='icon'
|
|
dangerouslySetInnerHTML={{__html: flagIcon}}
|
|
/>
|
|
);
|
|
flagFunc = this.flagPost;
|
|
}
|
|
|
|
let pinnedBadge;
|
|
if (post.is_pinned) {
|
|
pinnedBadge = (
|
|
<span className='post__pinned-badge'>
|
|
<FormattedMessage
|
|
id='post_info.pinned'
|
|
defaultMessage='Pinned'
|
|
/>
|
|
</span>
|
|
);
|
|
}
|
|
|
|
const timeOptions = {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
hour12: !this.props.useMilitaryTime
|
|
};
|
|
|
|
return (
|
|
<div
|
|
id='thread--root'
|
|
className={'post post--root post--thread ' + userCss + ' ' + systemMessageClass + ' ' + compactClass}
|
|
>
|
|
<div className='post-right-channel__name'>{channelName}</div>
|
|
<div className='post__content'>
|
|
{profilePicContainer}
|
|
<div>
|
|
<ul className='post__header'>
|
|
<li className='col__name'>{userProfile}</li>
|
|
{botIndicator}
|
|
<li className='col'>
|
|
{this.renderTimeTag(post, timeOptions)}
|
|
{pinnedBadge}
|
|
<OverlayTrigger
|
|
key={'rootpostflagtooltipkey' + flagVisible}
|
|
delayShow={Constants.OVERLAY_TIME_DELAY}
|
|
placement='top'
|
|
overlay={flagTooltip}
|
|
>
|
|
<a
|
|
href='#'
|
|
className={'flag-icon__container ' + flagVisible}
|
|
onClick={flagFunc}
|
|
>
|
|
{flag}
|
|
</a>
|
|
</OverlayTrigger>
|
|
</li>
|
|
<li className='col col__reply'>
|
|
{rootOptions}
|
|
</li>
|
|
</ul>
|
|
<div className='post__body'>
|
|
<div className={postClass}>
|
|
<PostBodyAdditionalContent
|
|
post={post}
|
|
message={<PostMessageContainer post={post}/>}
|
|
previewCollapsed={this.props.previewCollapsed}
|
|
/>
|
|
</div>
|
|
{fileAttachment}
|
|
<ReactionListContainer
|
|
post={post}
|
|
currentUserId={this.props.currentUser.id}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
RhsRootPost.defaultProps = {
|
|
commentCount: 0
|
|
};
|
|
RhsRootPost.propTypes = {
|
|
post: React.PropTypes.object.isRequired,
|
|
user: React.PropTypes.object.isRequired,
|
|
currentUser: React.PropTypes.object.isRequired,
|
|
commentCount: React.PropTypes.number,
|
|
compactDisplay: React.PropTypes.bool,
|
|
useMilitaryTime: React.PropTypes.bool.isRequired,
|
|
isFlagged: React.PropTypes.bool,
|
|
status: React.PropTypes.string,
|
|
previewCollapsed: React.PropTypes.string,
|
|
isBusy: React.PropTypes.bool
|
|
};
|