diff options
Diffstat (limited to 'webapp/components')
-rw-r--r-- | webapp/components/create_comment.jsx | 2 | ||||
-rw-r--r-- | webapp/components/create_post.jsx | 2 | ||||
-rw-r--r-- | webapp/components/emoji_picker/emoji_picker.jsx | 18 | ||||
-rw-r--r-- | webapp/components/post_view/components/post_info.jsx | 63 | ||||
-rw-r--r-- | webapp/components/rhs_comment.jsx | 92 | ||||
-rw-r--r-- | webapp/components/rhs_root_post.jsx | 73 | ||||
-rw-r--r-- | webapp/components/rhs_thread.jsx | 5 |
7 files changed, 237 insertions, 18 deletions
diff --git a/webapp/components/create_comment.jsx b/webapp/components/create_comment.jsx index 899609ed0..8aa26882b 100644 --- a/webapp/components/create_comment.jsx +++ b/webapp/components/create_comment.jsx @@ -580,7 +580,7 @@ export default class CreateComment extends React.Component { emojiPicker = ( <EmojiPicker onEmojiClick={this.handleEmojiClick} - topOrBottom='bottom' + pickerLocation='bottom' emojiOffset={this.state.emojiOffset} outsideClick={this.closeEmoji} /> diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx index 93a299b89..b33e5ea01 100644 --- a/webapp/components/create_post.jsx +++ b/webapp/components/create_post.jsx @@ -618,7 +618,7 @@ export default class CreatePost extends React.Component { emojiPicker = ( <EmojiPicker onEmojiClick={this.handleEmojiClick} - topOrBottom='top' + pickerLocation='top' outsideClick={this.closeEmoji} /> diff --git a/webapp/components/emoji_picker/emoji_picker.jsx b/webapp/components/emoji_picker/emoji_picker.jsx index e12974054..0f45b7297 100644 --- a/webapp/components/emoji_picker/emoji_picker.jsx +++ b/webapp/components/emoji_picker/emoji_picker.jsx @@ -34,7 +34,7 @@ class EmojiPicker extends React.Component { static propTypes = { customEmojis: React.PropTypes.object, onEmojiClick: React.PropTypes.func.isRequired, - topOrBottom: React.PropTypes.string.isRequired, + pickerLocation: React.PropTypes.string.isRequired, emojiOffset: React.PropTypes.number, outsideClick: React.PropTypes.func } @@ -68,7 +68,9 @@ class EmojiPicker extends React.Component { onOutsideEvent = (event) => { // Handle the event. - this.props.outsideClick(event); + if (this.props.outsideClick) { + this.props.outsideClick(event); + } } handleCategoryClick(category) { @@ -287,7 +289,17 @@ class EmojiPicker extends React.Component { items.push(this.renderCategory(category, this.state.filter)); } } - const cssclass = this.props.topOrBottom === 'top' ? 'emoji-picker' : 'emoji-picker-bottom'; + let cssclass = 'emoji-picker '; + if (this.props.pickerLocation === 'top') { + cssclass += 'emoji-picker-top'; + } else if (this.props.pickerLocation === 'bottom') { + cssclass += 'emoji-picker-bottom'; + } else if (this.props.pickerLocation === 'react') { + cssclass = 'emoji-picker-react'; + } else if (this.props.pickerLocation === 'react-rhs-comment') { + cssclass = 'emoji-picker-react-rhs-comment'; + } + const pickerStyle = this.props.emojiOffset ? {top: this.props.emojiOffset} : {}; return ( <div diff --git a/webapp/components/post_view/components/post_info.jsx b/webapp/components/post_view/components/post_info.jsx index 5318ec272..1da3ecd24 100644 --- a/webapp/components/post_view/components/post_info.jsx +++ b/webapp/components/post_view/components/post_info.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. import $ from 'jquery'; +import ReactDOM from 'react-dom'; import PostTime from './post_time.jsx'; @@ -12,7 +13,8 @@ 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 {Tooltip, OverlayTrigger, Overlay} from 'react-bootstrap'; +import EmojiPicker from 'components/emoji_picker/emoji_picker.jsx'; import React from 'react'; import {FormattedMessage} from 'react-intl'; @@ -28,10 +30,17 @@ export default class PostInfo extends React.Component { this.unflagPost = this.unflagPost.bind(this); this.pinPost = this.pinPost.bind(this); this.unpinPost = this.unpinPost.bind(this); + this.reactEmojiClick = this.reactEmojiClick.bind(this); + this.emojiPickerClick = this.emojiPickerClick.bind(this); this.canEdit = false; this.canDelete = false; this.editDisableAction = new DelayedAction(this.handleEditDisable); + + this.state = { + showEmojiPicker: false, + reactionPickerOffset: 21 + }; } handleDropdownOpened() { @@ -271,6 +280,10 @@ export default class PostInfo extends React.Component { GlobalActions.showGetPostLinkModal(this.props.post); } + emojiPickerClick() { + this.setState({showEmojiPicker: !this.state.showEmojiPicker}); + } + removePost() { GlobalActions.emitRemovePost(this.props.post); } @@ -308,6 +321,14 @@ export default class PostInfo extends React.Component { PostActions.unflagPost(this.props.post.id); } + reactEmojiClick(emoji) { + const pickerOffset = 21; + + const emojiName = emoji.name || emoji.aliases[0]; + PostActions.addReaction(this.props.post.channel_id, this.props.post.id, emojiName); + this.setState({showEmojiPicker: false, reactionPickerOffset: pickerOffset}); + } + render() { var post = this.props.post; var comments = ''; @@ -335,11 +356,47 @@ export default class PostInfo extends React.Component { className='comment-icon' dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}} /> - {commentCountText} + <span className='comment-count'> + {commentCountText} + </span> </a> ); } + let react; + if (post.state !== Constants.POST_FAILED && + post.state !== Constants.POST_LOADING && + !Utils.isPostEphemeral(post) && + Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMOJI_PICKER_PREVIEW)) { + react = ( + <span> + <Overlay + show={this.state.showEmojiPicker} + placement='top' + rootClose={true} + container={this} + onHide={() => this.setState({showEmojiPicker: false})} + target={() => ReactDOM.findDOMNode(this.refs['reactIcon_' + post.id])} + + > + <EmojiPicker + onEmojiClick={this.reactEmojiClick} + pickerLocation='top' + + /> + </Overlay> + <a + href='#' + className='reacticon__container' + onClick={this.emojiPickerClick} + ref={'reactIcon_' + post.id} + ><i className='fa fa-smile-o'/> + </a> + </span> + + ); + } + let options; if (Utils.isPostEphemeral(post)) { options = ( @@ -358,6 +415,7 @@ export default class PostInfo extends React.Component { > {dropdown} </div> + {react} {comments} </li> ); @@ -445,6 +503,7 @@ export default class PostInfo extends React.Component { postId={post.id} /> {pinnedBadge} + {this.state.showEmojiPicker} {flagTrigger} </li> {options} diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx index 278163bf7..fe0f139fa 100644 --- a/webapp/components/rhs_comment.jsx +++ b/webapp/components/rhs_comment.jsx @@ -10,7 +10,7 @@ import ReactionListContainer from 'components/post_view/components/reaction_list import RhsDropdown from 'components/rhs_dropdown.jsx'; import * as GlobalActions from 'actions/global_actions.jsx'; -import {flagPost, unflagPost, pinPost, unpinPost} from 'actions/post_actions.jsx'; +import {flagPost, unflagPost, pinPost, unpinPost, addReaction} from 'actions/post_actions.jsx'; import TeamStore from 'stores/team_store.jsx'; @@ -19,10 +19,13 @@ 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 {Tooltip, OverlayTrigger, Overlay} from 'react-bootstrap'; import {FormattedMessage} from 'react-intl'; +import EmojiPicker from 'components/emoji_picker/emoji_picker.jsx'; +import ReactDOM from 'react-dom'; + import loadingGif from 'images/load.gif'; import React from 'react'; @@ -38,6 +41,8 @@ export default class RhsComment extends React.Component { this.unflagPost = this.unflagPost.bind(this); this.pinPost = this.pinPost.bind(this); this.unpinPost = this.unpinPost.bind(this); + this.reactEmojiClick = this.reactEmojiClick.bind(this); + this.emojiPickerClick = this.emojiPickerClick.bind(this); this.canEdit = false; this.canDelete = false; @@ -46,7 +51,9 @@ export default class RhsComment extends React.Component { this.state = { currentTeamDisplayName: TeamStore.getCurrent().name, width: '', - height: '' + height: '', + showReactEmojiPicker: false, + reactPickerOffset: 15 }; } @@ -88,7 +95,7 @@ export default class RhsComment extends React.Component { ); } - shouldComponentUpdate(nextProps) { + shouldComponentUpdate(nextProps, nextState) { if (nextProps.status !== this.props.status) { return true; } @@ -117,6 +124,10 @@ export default class RhsComment extends React.Component { return true; } + if (this.state.showReactEmojiPicker !== nextState.showReactEmojiPicker) { + return true; + } + return false; } @@ -327,11 +338,39 @@ export default class RhsComment extends React.Component { ); } + emojiPickerClick() { + // set default offset + let reactOffset = 15; + const reactSelectorHeight = 360; + const reactionIconY = ReactDOM.findDOMNode(this).getBoundingClientRect().top; + const rhsMinHeight = 700; + + const spaceAvail = rhsMinHeight - reactionIconY; + if (spaceAvail < reactSelectorHeight) { + reactOffset = (spaceAvail - reactSelectorHeight); + } + this.setState({showReactEmojiPicker: !this.state.showReactEmojiPicker, reactPickerOffset: reactOffset}); + } + + reactEmojiClick(emoji) { + this.setState({showReactEmojiPicker: false}); + const emojiName = emoji.name || emoji.aliases[0]; + addReaction(this.props.post.channel_id, this.props.post.id, emojiName); + } + render() { const post = this.props.post; const flagIcon = Constants.FLAG_ICON_SVG; const mattermostLogo = Constants.MATTERMOST_ICON_SVG; const isSystemMessage = PostUtils.isSystemMessage(post); + let canReact = false; + + if (post.state !== Constants.POST_FAILED && + post.state !== Constants.POST_LOADING && + !Utils.isPostEphemeral(post) && + Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMOJI_PICKER_PREVIEW)) { + canReact = true; + } var currentUserCss = ''; if (this.props.currentUser.id === post.user_id) { @@ -536,6 +575,42 @@ export default class RhsComment extends React.Component { ); } + let react; + let reactOverlay; + + if (canReact) { + react = ( + <span> + <a + href='#' + className='reacticon__container reaction' + onClick={this.emojiPickerClick} + ref={'rhs_reacticon_' + post.id} + ><i className='fa fa-smile-o'/> + </a> + </span> + + ); + reactOverlay = ( + <Overlay + id={'rhs_react_overlay_' + post.id} + show={this.state.showReactEmojiPicker} + placement='top' + rootClose={true} + container={this.refs['post_body_' + post.id]} + onHide={() => this.setState({showReactEmojiPicker: false})} + target={() => ReactDOM.findDOMNode(this.refs['rhs_reacticon_' + post.id])} + + > + <EmojiPicker + onEmojiClick={this.reactEmojiClick} + pickerLocation='react-rhs-comment' + emojiOffset={this.state.reactPickerOffset} + /> + </Overlay> + ); + } + let options; if (Utils.isPostEphemeral(post)) { options = ( @@ -546,7 +621,9 @@ export default class RhsComment extends React.Component { } else if (!PostUtils.isSystemMessage(post)) { options = ( <li className='col col__reply'> + {reactOverlay} {this.createDropdown()} + {react} </li> ); } @@ -570,7 +647,10 @@ export default class RhsComment extends React.Component { }; return ( - <div className={'post post--thread ' + currentUserCss + ' ' + compactClass + ' ' + systemMessageClass}> + <div + ref={'post_body_' + post.id} + className={'post post--thread ' + currentUserCss + ' ' + compactClass + ' ' + systemMessageClass} + > <div className='post__content'> {profilePicContainer} <div> @@ -586,7 +666,7 @@ export default class RhsComment extends React.Component { </li> {options} </ul> - <div className='post__body'> + <div className='post__body' > <div className={postClass}> {loading} <PostMessageContainer post={post}/> diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx index f07826d63..b298853d8 100644 --- a/webapp/components/rhs_root_post.jsx +++ b/webapp/components/rhs_root_post.jsx @@ -14,14 +14,17 @@ 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 {flagPost, unflagPost, pinPost, unpinPost, addReaction} from 'actions/post_actions.jsx'; import * as Utils from 'utils/utils.jsx'; import * as PostUtils from 'utils/post_utils.jsx'; +import EmojiPicker from 'components/emoji_picker/emoji_picker.jsx'; +import ReactDOM from 'react-dom'; + import Constants from 'utils/constants.jsx'; import DelayedAction from 'utils/delayed_action.jsx'; -import {Tooltip, OverlayTrigger} from 'react-bootstrap'; +import {Tooltip, OverlayTrigger, Overlay} from 'react-bootstrap'; import {FormattedMessage} from 'react-intl'; @@ -37,6 +40,8 @@ export default class RhsRootPost extends React.Component { this.unflagPost = this.unflagPost.bind(this); this.pinPost = this.pinPost.bind(this); this.unpinPost = this.unpinPost.bind(this); + this.reactEmojiClick = this.reactEmojiClick.bind(this); + this.emojiPickerClick = this.emojiPickerClick.bind(this); this.canEdit = false; this.canDelete = false; @@ -45,7 +50,9 @@ export default class RhsRootPost extends React.Component { this.state = { currentTeamDisplayName: TeamStore.getCurrent().name, width: '', - height: '' + height: '', + showRHSEmojiPicker: false, + testStateObj: true }; } @@ -70,7 +77,7 @@ export default class RhsRootPost extends React.Component { this.canEdit = false; } - shouldComponentUpdate(nextProps) { + shouldComponentUpdate(nextProps, nextState) { if (nextProps.status !== this.props.status) { return true; } @@ -107,6 +114,10 @@ export default class RhsRootPost extends React.Component { return true; } + if (this.state.showRHSEmojiPicker !== nextState.showRHSEmojiPicker) { + return true; + } + return false; } @@ -155,13 +166,30 @@ export default class RhsRootPost extends React.Component { unpinPost(this.props.post.channel_id, this.props.post.id); } + emojiPickerClick() { + this.setState({showRHSEmojiPicker: !this.state.showRHSEmojiPicker}); + } + + reactEmojiClick(emoji) { + const emojiName = emoji.name || emoji.aliases[0]; + addReaction(this.props.post.channel_id, this.props.post.id, emojiName); + this.setState({showRHSEmojiPicker: false}); + } + 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); + let canReact = false; const flagIcon = Constants.FLAG_ICON_SVG; + if (post.state !== Constants.POST_FAILED && + post.state !== Constants.POST_LOADING && + !Utils.isPostEphemeral(post) && + Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMOJI_PICKER_PREVIEW)) { + canReact = true; + } this.canDelete = PostUtils.canDeletePost(post); this.canEdit = PostUtils.canEditPost(post, this.editDisableAction); @@ -195,6 +223,41 @@ export default class RhsRootPost extends React.Component { } } + let react; + let reactOverlay; + + if (canReact) { + react = ( + <span> + <a + href='#' + className='reacticon__container reaction' + onClick={this.emojiPickerClick} + ref='rhs_root_reacticon' + ><i className='fa fa-smile-o'/> + </a> + </span> + + ); + reactOverlay = ( + <Overlay + id='rhs_react_overlay' + show={this.state.showRHSEmojiPicker} + placement='bottom' + rootClose={true} + container={this} + onHide={() => this.setState({showRHSEmojiPicker: false})} + target={() => ReactDOM.findDOMNode(this.refs.rhs_root_reacticon)} + + > + <EmojiPicker + onEmojiClick={this.reactEmojiClick} + pickerLocation='react' + /> + </Overlay> + ); + } + var dropdownContents = []; if (Utils.isMobile()) { @@ -547,7 +610,9 @@ export default class RhsRootPost extends React.Component { </OverlayTrigger> </li> <li className='col col__reply'> + {reactOverlay} {rootOptions} + {react} </li> </ul> <div className='post__body'> diff --git a/webapp/components/rhs_thread.jsx b/webapp/components/rhs_thread.jsx index 79879973b..4ac36717a 100644 --- a/webapp/components/rhs_thread.jsx +++ b/webapp/components/rhs_thread.jsx @@ -424,7 +424,10 @@ export default class RhsThread extends React.Component { renderView={renderView} onScroll={this.handleScroll} > - <div className='post-right__scroll'> + <div + ref='post-right__scroll' + className='post-right__scroll' + > <DateSeparator date={rootPostDay.toDateString()} /> |