diff options
author | Harrison Healey <harrisonmhealey@gmail.com> | 2017-06-13 14:35:45 -0400 |
---|---|---|
committer | Christopher Speller <crspeller@gmail.com> | 2017-06-13 11:35:45 -0700 |
commit | 40efd8367a85e3333e9b7cc45c390259d412088c (patch) | |
tree | f40d4fb6f5e7b1bdb8cefa95e25a626837a07a9c | |
parent | 56883d6f95b268477a1f0ec956bc1891ce30c2a1 (diff) | |
download | chat-40efd8367a85e3333e9b7cc45c390259d412088c.tar.gz chat-40efd8367a85e3333e9b7cc45c390259d412088c.tar.bz2 chat-40efd8367a85e3333e9b7cc45c390259d412088c.zip |
PLT-6127/PLT-6135/PLT-6137 Added EmojiPickerOverlay component to better position emoji picker (#6352)
* Cleaned up emoji picker CSS
* Fixed border of emoji picker when reacting to comments in the RHS
* PLT-6135 Made EmojiPicker automatically position itself above/below the [...] menu
* PLT-6135 Changed post textbox emoji picker to use a RootCloseWrapper
* PLT-6135 Changed comment textbox emoji picker to use a RootCloseWrapper
* PLT-6135 Changed RHS post components to use EmojiPickerOverlay
* Removed react-outside-event package
* Fixed merge conflict
* Fixed emoji picker position on posts in RHS
* Removed unused CSS classes
* Fixed not being able to react to posts with emoji picker
-rw-r--r-- | webapp/components/create_comment.jsx | 60 | ||||
-rw-r--r-- | webapp/components/create_post.jsx | 44 | ||||
-rw-r--r-- | webapp/components/emoji_picker/emoji_picker.jsx | 58 | ||||
-rw-r--r-- | webapp/components/emoji_picker/emoji_picker_overlay.jsx | 62 | ||||
-rw-r--r-- | webapp/components/post_view/components/post.jsx | 4 | ||||
-rw-r--r-- | webapp/components/post_view/components/post_header.jsx | 4 | ||||
-rw-r--r-- | webapp/components/post_view/components/post_info.jsx | 56 | ||||
-rw-r--r-- | webapp/components/post_view/components/post_list.jsx | 5 | ||||
-rw-r--r-- | webapp/components/rhs_comment.jsx | 67 | ||||
-rw-r--r-- | webapp/components/rhs_root_post.jsx | 57 | ||||
-rw-r--r-- | webapp/components/rhs_thread.jsx | 8 | ||||
-rw-r--r-- | webapp/package.json | 1 | ||||
-rw-r--r-- | webapp/sass/components/_emoticons.scss | 168 | ||||
-rw-r--r-- | webapp/sass/layout/_post-right.scss | 6 | ||||
-rw-r--r-- | webapp/sass/layout/_post.scss | 6 | ||||
-rw-r--r-- | webapp/utils/utils.jsx | 11 |
16 files changed, 265 insertions, 352 deletions
diff --git a/webapp/components/create_comment.jsx b/webapp/components/create_comment.jsx index 0389af0a2..e2ea84c46 100644 --- a/webapp/components/create_comment.jsx +++ b/webapp/components/create_comment.jsx @@ -24,6 +24,7 @@ import * as PostActions from 'actions/post_actions.jsx'; import Constants from 'utils/constants.jsx'; import {FormattedMessage} from 'react-intl'; +import {RootCloseWrapper} from 'react-overlays'; import {browserHistory} from 'react-router/es6'; const ActionTypes = Constants.ActionTypes; @@ -58,10 +59,7 @@ export default class CreateComment extends React.Component { this.showPostDeletedModal = this.showPostDeletedModal.bind(this); this.hidePostDeletedModal = this.hidePostDeletedModal.bind(this); this.handlePostError = this.handlePostError.bind(this); - this.handleEmojiPickerClick = this.handleEmojiPickerClick.bind(this); this.handleEmojiClick = this.handleEmojiClick.bind(this); - this.onKeyPress = this.onKeyPress.bind(this); - this.closeEmoji = this.closeEmoji.bind(this); PostStore.clearCommentDraftUploads(); MessageHistoryStore.resetHistoryIndex('comment'); @@ -77,38 +75,14 @@ export default class CreateComment extends React.Component { showPostDeletedModal: false, enableAddButton, showEmojiPicker: false, - emojiOffset: 0, emojiPickerEnabled: Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMOJI_PICKER_PREVIEW) }; this.lastBlurAt = 0; } - closeEmoji(clickEvent) { - /* - if the user clicked something outside the component, except the RHS emojipicker icon - and the picker is open, then close it - */ - if (clickEvent && clickEvent.srcElement && - clickEvent.srcElement.className !== '' && - clickEvent.srcElement.className.indexOf('emoji-rhs') === -1 && - this.state.showEmojiPicker) { - this.setState({showEmojiPicker: !this.state.showEmojiPicker}); - } - } - - handleEmojiPickerClick() { - const threadHeight = document.getElementById('thread--root') ? document.getElementById('thread--root').offsetHeight : 0; - const messagesHeight = document.querySelector('div.post-right-comments-container') ? document.querySelector('div.post-right-comments-container').offsetHeight : 0; - - const totalHeight = threadHeight + messagesHeight; - let pickerOffset = 0; - if (totalHeight > 361) { - pickerOffset = -361; - } else { - pickerOffset = -1 * totalHeight; - } - this.setState({showEmojiPicker: !this.state.showEmojiPicker, emojiOffset: pickerOffset}); + toggleEmojiPicker = () => { + this.setState({showEmojiPicker: !this.state.showEmojiPicker}); } handleEmojiClick(emoji) { @@ -120,34 +94,28 @@ export default class CreateComment extends React.Component { } if (this.state.message === '') { - this.setState({message: ':' + emojiAlias + ': ', showEmojiPicker: false}); + this.setState({message: ':' + emojiAlias + ': '}); } else { //check whether there is already a blank at the end of the current message const newMessage = (/\s+$/.test(this.state.message)) ? this.state.message + ':' + emojiAlias + ': ' : this.state.message + ' :' + emojiAlias + ': '; - this.setState({message: newMessage, showEmojiPicker: false}); + this.setState({message: newMessage}); } + this.setState({showEmojiPicker: false}); + this.focusTextbox(); } componentDidMount() { PreferenceStore.addChangeListener(this.onPreferenceChange); - document.addEventListener('keydown', this.onKeyPress); this.focusTextbox(); } componentWillUnmount() { PreferenceStore.removeChangeListener(this.onPreferenceChange); - document.removeEventListener('keydown', this.onKeyPress); - } - - onKeyPress(e) { - if (e.which === Constants.KeyCodes.ESCAPE && this.state.showEmojiPicker === true) { - this.setState({showEmojiPicker: !this.state.showEmojiPicker}); - } } onPreferenceChange() { @@ -584,12 +552,12 @@ export default class CreateComment extends React.Component { let emojiPicker = null; if (this.state.showEmojiPicker) { emojiPicker = ( - <EmojiPicker - onEmojiClick={this.handleEmojiClick} - pickerLocation='bottom' - emojiOffset={this.state.emojiOffset} - outsideClick={this.closeEmoji} - /> + <RootCloseWrapper onRootClose={this.toggleEmojiPicker}> + <EmojiPicker + onEmojiClick={this.handleEmojiClick} + onHide={this.toggleEmojiPicker} + /> + </RootCloseWrapper> ); } @@ -625,7 +593,7 @@ export default class CreateComment extends React.Component { onUploadError={this.handleUploadError} postType='comment' channelId={this.props.channelId} - onEmojiClick={this.handleEmojiPickerClick} + onEmojiClick={this.toggleEmojiPicker} emojiEnabled={this.state.emojiPickerEnabled} navBarName='rhs' /> diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx index 31c19bd5c..09741706c 100644 --- a/webapp/components/create_post.jsx +++ b/webapp/components/create_post.jsx @@ -28,6 +28,7 @@ import ConfirmModal from './confirm_modal.jsx'; import Constants from 'utils/constants.jsx'; import {FormattedHTMLMessage, FormattedMessage} from 'react-intl'; +import {RootCloseWrapper} from 'react-overlays'; import {browserHistory} from 'react-router/es6'; const Preferences = Constants.Preferences; @@ -67,9 +68,7 @@ export default class CreatePost extends React.Component { this.hidePostDeletedModal = this.hidePostDeletedModal.bind(this); this.showShortcuts = this.showShortcuts.bind(this); this.handleEmojiClick = this.handleEmojiClick.bind(this); - this.handleEmojiPickerClick = this.handleEmojiPickerClick.bind(this); this.handlePostError = this.handlePostError.bind(this); - this.closeEmoji = this.closeEmoji.bind(this); this.hideNotifyAllModal = this.hideNotifyAllModal.bind(this); this.showNotifyAllModal = this.showNotifyAllModal.bind(this); this.handleNotifyModalCancel = this.handleNotifyModalCancel.bind(this); @@ -107,16 +106,8 @@ export default class CreatePost extends React.Component { this.setState({postError}); } - closeEmoji(clickEvent) { - /* - if the user clicked something outside the component, except the main emojipicker icon - and the picker is open, then close it - */ - if (clickEvent && clickEvent.srcElement && - clickEvent.srcElement.className.indexOf('emoji-main') === -1 && - this.state.showEmojiPicker) { - this.setState({showEmojiPicker: !this.state.showEmojiPicker}); - } + toggleEmojiPicker = () => { + this.setState({showEmojiPicker: !this.state.showEmojiPicker}); } doSubmit(e) { @@ -446,10 +437,6 @@ export default class CreatePost extends React.Component { } showShortcuts(e) { - if (e.which === Constants.KeyCodes.ESCAPE && this.state.showEmojiPicker === true) { - this.setState({showEmojiPicker: !this.state.showEmojiPicker}); - } - if ((e.ctrlKey || e.metaKey) && e.keyCode === Constants.KeyCodes.FORWARD_SLASH) { e.preventDefault(); const args = {}; @@ -580,20 +567,18 @@ export default class CreatePost extends React.Component { } if (this.state.message === '') { - this.setState({message: ':' + emojiAlias + ': ', showEmojiPicker: false}); + this.setState({message: ':' + emojiAlias + ': '}); } else { //check whether there is already a blank at the end of the current message const newMessage = (/\s+$/.test(this.state.message)) ? this.state.message + ':' + emojiAlias + ': ' : this.state.message + ' :' + emojiAlias + ': '; - this.setState({message: newMessage, showEmojiPicker: false}); + this.setState({message: newMessage}); } - this.focusTextbox(); - } + this.setState({showEmojiPicker: false}); - handleEmojiPickerClick() { - this.setState({showEmojiPicker: !this.state.showEmojiPicker}); + this.focusTextbox(); } createTutorialTip() { @@ -692,15 +677,16 @@ export default class CreatePost extends React.Component { if (!this.state.enableSendButton) { sendButtonClass += ' disabled'; } + let emojiPicker = null; if (this.state.showEmojiPicker) { emojiPicker = ( - <EmojiPicker - onEmojiClick={this.handleEmojiClick} - pickerLocation='top' - outsideClick={this.closeEmoji} - - /> + <RootCloseWrapper onRootClose={this.toggleEmojiPicker}> + <EmojiPicker + onHide={this.toggleEmojiPicker} + onEmojiClick={this.handleEmojiClick} + /> + </RootCloseWrapper> ); } @@ -743,7 +729,7 @@ export default class CreatePost extends React.Component { onUploadError={this.handleUploadError} postType='post' channelId='' - onEmojiClick={this.handleEmojiPickerClick} + onEmojiClick={this.toggleEmojiPicker} emojiEnabled={this.state.emojiPickerEnabled} navBarName='main' /> diff --git a/webapp/components/emoji_picker/emoji_picker.jsx b/webapp/components/emoji_picker/emoji_picker.jsx index e9cddce34..cbb388a10 100644 --- a/webapp/components/emoji_picker/emoji_picker.jsx +++ b/webapp/components/emoji_picker/emoji_picker.jsx @@ -1,15 +1,13 @@ -import PropTypes from 'prop-types'; - // 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 * as Emoji from 'utils/emoji.jsx'; import EmojiStore from 'stores/emoji_store.jsx'; import PureRenderMixin from 'react-addons-pure-render-mixin'; import * as Utils from 'utils/utils.jsx'; -import ReactOutsideEvent from 'react-outside-event'; import {FormattedMessage} from 'react-intl'; import EmojiPickerCategory from './components/emoji_picker_category.jsx'; @@ -30,13 +28,12 @@ const CATEGORIES = [ 'custom' ]; -class EmojiPicker extends React.Component { +export default class EmojiPicker extends React.Component { static propTypes = { + style: PropTypes.object, + placement: PropTypes.oneOf(['top', 'bottom', 'left']), customEmojis: PropTypes.object, - onEmojiClick: PropTypes.func.isRequired, - pickerLocation: PropTypes.string.isRequired, - emojiOffset: PropTypes.number, - outsideClick: PropTypes.func + onEmojiClick: PropTypes.func.isRequired } constructor(props) { @@ -53,7 +50,6 @@ class EmojiPicker extends React.Component { this.handleScroll = this.handleScroll.bind(this); this.handleItemUnmount = this.handleItemUnmount.bind(this); this.renderCategory = this.renderCategory.bind(this); - this.onOutsideEvent = this.onOutsideEvent.bind(this); this.state = { category: 'recent', @@ -63,14 +59,11 @@ class EmojiPicker extends React.Component { } componentDidMount() { - this.searchInput.focus(); - } - - onOutsideEvent = (event) => { - // Handle the event. - if (this.props.outsideClick) { - this.props.outsideClick(event); - } + // Delay taking focus because this briefly renders offscreen when using an Overlay + // so focusing it immediately on mount can cause weird scrolling + requestAnimationFrame(() => { + this.searchInput.focus(); + }); } handleCategoryClick(category) { @@ -99,7 +92,7 @@ class EmojiPicker extends React.Component { } handleItemUnmount(emoji) { - //Prevent emoji preview from showing emoji which is not present anymore (due to filter) + // Prevent emoji preview from showing emoji which is not present anymore (due to filter) if (this.state.selected === emoji) { this.setState({selected: null}); } @@ -292,22 +285,25 @@ class EmojiPicker extends React.Component { items.push(this.renderCategory(category, this.state.filter)); } } - 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'; + + let pickerStyle; + if (this.props.style && !(this.props.style.left === 0 || this.props.style.top === 0)) { + if (this.props.placement === 'top' || this.props.placement === 'bottom') { + // Only take the top/bottom position passed by React Bootstrap since we want to be right-aligned + pickerStyle = { + top: this.props.style.top, + bottom: this.props.style.bottom, + right: 1 + }; + } else { + pickerStyle = this.props.style; + } } - const pickerStyle = this.props.emojiOffset ? {top: this.props.emojiOffset} : {}; return ( <div + className='emoji-picker' style={pickerStyle} - className={cssclass} > <div className='emoji-picker__categories'> <EmojiPickerCategory @@ -426,7 +422,3 @@ class EmojiPicker extends React.Component { ); } } - -// disabling eslint check for outslide click handler -// eslint-disable-next-line new-cap -export default ReactOutsideEvent(EmojiPicker, ['click']); diff --git a/webapp/components/emoji_picker/emoji_picker_overlay.jsx b/webapp/components/emoji_picker/emoji_picker_overlay.jsx new file mode 100644 index 000000000..09cc0a36c --- /dev/null +++ b/webapp/components/emoji_picker/emoji_picker_overlay.jsx @@ -0,0 +1,62 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import PropTypes from 'prop-types'; +import {Overlay} from 'react-bootstrap'; + +import EmojiPicker from './emoji_picker.jsx'; + +export default class EmojiPickerOverlay extends React.PureComponent { + static propTypes = { + show: PropTypes.bool.isRequired, + container: PropTypes.func, + target: PropTypes.func.isRequired, + onEmojiClick: PropTypes.func.isRequired, + onHide: PropTypes.func.isRequired + } + + constructor(props) { + super(props); + + this.state = { + placement: 'top' + }; + } + + componentWillUpdate(nextProps) { + if (nextProps.show && !this.props.show) { + const spaceRequiredAbove = 422; + const spaceRequiredBelow = 436; + + const targetBounds = nextProps.target().getBoundingClientRect(); + + let placement; + if (targetBounds.top > spaceRequiredAbove) { + placement = 'top'; + } else if (window.innerHeight - targetBounds.bottom > spaceRequiredBelow) { + placement = 'bottom'; + } else { + placement = 'left'; + } + + this.setState({placement}); + } + } + + render() { + return ( + <Overlay + show={this.props.show} + placement={this.state.placement} + rootClose={true} + container={this.props.container} + onHide={this.props.onHide} + target={this.props.target} + animation={false} + > + <EmojiPicker onEmojiClick={this.props.onEmojiClick}/> + </Overlay> + ); + } +} diff --git a/webapp/components/post_view/components/post.jsx b/webapp/components/post_view/components/post.jsx index c1b932774..b9823396d 100644 --- a/webapp/components/post_view/components/post.jsx +++ b/webapp/components/post_view/components/post.jsx @@ -38,7 +38,8 @@ export default class Post extends Component { isFlagged: PropTypes.bool, status: PropTypes.string, isBusy: PropTypes.bool, - childComponentDidUpdateFunction: PropTypes.func + childComponentDidUpdateFunction: PropTypes.func, + getPostList: PropTypes.func.isRequired }; constructor(props) { @@ -308,6 +309,7 @@ export default class Post extends Component { isFlagged={this.props.isFlagged} status={this.props.status} isBusy={this.props.isBusy} + getPostList={this.props.getPostList} /> <PostBody post={post} diff --git a/webapp/components/post_view/components/post_header.jsx b/webapp/components/post_view/components/post_header.jsx index 570a04f8f..eab2d4629 100644 --- a/webapp/components/post_view/components/post_header.jsx +++ b/webapp/components/post_view/components/post_header.jsx @@ -91,6 +91,7 @@ export default class PostHeader extends React.Component { compactDisplay={this.props.compactDisplay} useMilitaryTime={this.props.useMilitaryTime} isFlagged={this.props.isFlagged} + getPostList={this.props.getPostList} /> </div> </div> @@ -119,5 +120,6 @@ PostHeader.propTypes = { useMilitaryTime: PropTypes.bool.isRequired, isFlagged: PropTypes.bool.isRequired, status: PropTypes.string, - isBusy: PropTypes.bool + isBusy: PropTypes.bool, + getPostList: PropTypes.func.isRequired }; diff --git a/webapp/components/post_view/components/post_info.jsx b/webapp/components/post_view/components/post_info.jsx index 39a0b1fec..45c716ad0 100644 --- a/webapp/components/post_view/components/post_info.jsx +++ b/webapp/components/post_view/components/post_info.jsx @@ -2,7 +2,6 @@ // See License.txt for license information. import $ from 'jquery'; -import ReactDOM from 'react-dom'; import PostTime from './post_time.jsx'; import PostFlagIcon from 'components/common/post_flag_icon.jsx'; @@ -15,8 +14,7 @@ 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 {Overlay} from 'react-bootstrap'; -import EmojiPicker from 'components/emoji_picker/emoji_picker.jsx'; +import EmojiPickerOverlay from 'components/emoji_picker/emoji_picker_overlay.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import PropTypes from 'prop-types'; @@ -36,7 +34,6 @@ export default class PostInfo extends React.Component { 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; @@ -280,8 +277,16 @@ export default class PostInfo extends React.Component { GlobalActions.showGetPostLinkModal(this.props.post); } - emojiPickerClick() { - this.setState({showEmojiPicker: !this.state.showEmojiPicker}); + toggleEmojiPicker = () => { + const showEmojiPicker = !this.state.showEmojiPicker; + + this.setState({showEmojiPicker}); + this.props.handleDropdownOpened(showEmojiPicker); + } + + hideEmojiPicker = () => { + this.setState({showEmojiPicker: false}); + this.props.handleDropdownOpened(false); } removePost() { @@ -328,6 +333,10 @@ export default class PostInfo extends React.Component { PostActions.addReaction(this.props.post.channel_id, this.props.post.id, emojiName); } + getDotMenu = () => { + return this.refs.dotMenu; + } + render() { var post = this.props.post; @@ -359,30 +368,21 @@ export default class PostInfo extends React.Component { if (Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMOJI_PICKER_PREVIEW)) { react = ( <span> - <Overlay + <EmojiPickerOverlay show={this.state.showEmojiPicker} - placement='top' - rootClose={true} - container={this} - onHide={() => this.setState({showEmojiPicker: false})} - target={() => ReactDOM.findDOMNode(this.refs['reactIcon_' + post.id])} - animation={false} - > - <EmojiPicker - onEmojiClick={this.reactEmojiClick} - pickerLocation='top' - - /> - </Overlay> + container={this.props.getPostList} + target={this.getDotMenu} + onHide={this.hideEmojiPicker} + onEmojiClick={this.reactEmojiClick} + /> <a href='#' className='reacticon__container' - onClick={this.emojiPickerClick} - ref={'reactIcon_' + post.id} - ><i className='fa fa-smile-o'/> + onClick={this.toggleEmojiPicker} + > + <i className='fa fa-smile-o'/> </a> </span> - ); } } @@ -399,7 +399,10 @@ export default class PostInfo extends React.Component { if (dropdown) { options = ( - <div className='col col__reply'> + <div + ref='dotMenu' + className='col col__reply' + > <div className='dropdown' ref='dotMenu' @@ -468,5 +471,6 @@ PostInfo.propTypes = { currentUser: PropTypes.object.isRequired, compactDisplay: PropTypes.bool, useMilitaryTime: PropTypes.bool.isRequired, - isFlagged: PropTypes.bool + isFlagged: PropTypes.bool, + getPostList: PropTypes.func.isRequired }; diff --git a/webapp/components/post_view/components/post_list.jsx b/webapp/components/post_view/components/post_list.jsx index a0b88cd0f..403e26f84 100644 --- a/webapp/components/post_view/components/post_list.jsx +++ b/webapp/components/post_view/components/post_list.jsx @@ -368,6 +368,7 @@ export default class PostList extends React.Component { status={status} isBusy={this.props.isBusy} childComponentDidUpdateFunction={this.childComponentDidUpdate} + getPostList={this.getPostList} /> ); @@ -562,6 +563,10 @@ export default class PostList extends React.Component { this.checkAndUpdateScrolling(); } + getPostList = () => { + return this.refs.postlist; + } + render() { // Create intro message or top loadmore link let moreMessagesTop; diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx index b7e82d903..20734c4f4 100644 --- a/webapp/components/rhs_comment.jsx +++ b/webapp/components/rhs_comment.jsx @@ -20,12 +20,10 @@ import * as PostUtils from 'utils/post_utils.jsx'; import Constants from 'utils/constants.jsx'; import DelayedAction from 'utils/delayed_action.jsx'; -import {Overlay} from 'react-bootstrap'; import {FormattedMessage} from 'react-intl'; -import EmojiPicker from 'components/emoji_picker/emoji_picker.jsx'; -import ReactDOM from 'react-dom'; +import EmojiPickerOverlay from 'components/emoji_picker/emoji_picker_overlay.jsx'; import loadingGif from 'images/load.gif'; @@ -45,7 +43,6 @@ export default class RhsComment extends React.Component { 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.handleDropdownOpened = this.handleDropdownOpened.bind(this); this.canEdit = false; @@ -56,8 +53,7 @@ export default class RhsComment extends React.Component { currentTeamDisplayName: TeamStore.getCurrent().name, width: '', height: '', - showReactEmojiPicker: false, - reactPickerOffset: 15, + showEmojiPicker: false, dropdownOpened: false }; } @@ -129,7 +125,7 @@ export default class RhsComment extends React.Component { return true; } - if (this.state.showReactEmojiPicker !== nextState.showReactEmojiPicker) { + if (this.state.showEmojiPicker !== nextState.showEmojiPicker) { return true; } @@ -356,22 +352,17 @@ 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; + toggleEmojiPicker = () => { + const showEmojiPicker = !this.state.showEmojiPicker; - const spaceAvail = rhsMinHeight - reactionIconY; - if (spaceAvail < reactSelectorHeight) { - reactOffset = (spaceAvail - reactSelectorHeight); - } - this.setState({showReactEmojiPicker: !this.state.showReactEmojiPicker, reactPickerOffset: reactOffset}); + this.setState({ + showEmojiPicker, + dropdownOpened: showEmojiPicker + }); } reactEmojiClick(emoji) { - this.setState({showReactEmojiPicker: false}); + this.setState({showEmojiPicker: false}); const emojiName = emoji.name || emoji.aliases[0]; addReaction(this.props.post.channel_id, this.props.post.id, emojiName); } @@ -554,39 +545,26 @@ export default class RhsComment extends React.Component { } let react; - let reactOverlay; - if (!isEphemeral && !isPending && !isSystemMessage && Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMOJI_PICKER_PREVIEW)) { react = ( <span> + <EmojiPickerOverlay + show={this.state.showEmojiPicker} + onHide={this.toggleEmojiPicker} + target={() => this.refs.dotMenu} + container={this.props.getPostList} + onEmojiClick={this.reactEmojiClick} + /> <a href='#' className='reacticon__container reaction' - onClick={this.emojiPickerClick} + onClick={this.toggleEmojiPicker} 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])} - animation={false} - > - <EmojiPicker - onEmojiClick={this.reactEmojiClick} - pickerLocation='react-rhs-comment' - emojiOffset={this.state.reactPickerOffset} - /> - </Overlay> - ); } let options; @@ -598,8 +576,10 @@ export default class RhsComment extends React.Component { ); } else if (!isSystemMessage) { options = ( - <div className='col col__reply'> - {reactOverlay} + <div + ref='dotMenu' + className='col col__reply' + > {this.createDropdown(isSystemMessage)} {react} </div> @@ -674,5 +654,6 @@ RhsComment.propTypes = { useMilitaryTime: PropTypes.bool.isRequired, isFlagged: PropTypes.bool, status: PropTypes.string, - isBusy: PropTypes.bool + isBusy: PropTypes.bool, + getPostList: PropTypes.func.isRequired }; diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx index c00022e9e..e77fb7992 100644 --- a/webapp/components/rhs_root_post.jsx +++ b/webapp/components/rhs_root_post.jsx @@ -20,12 +20,10 @@ import {flagPost, unflagPost, pinPost, unpinPost, addReaction} from 'actions/pos 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 EmojiPickerOverlay from 'components/emoji_picker/emoji_picker_overlay.jsx'; import Constants from 'utils/constants.jsx'; import DelayedAction from 'utils/delayed_action.jsx'; -import {Overlay} from 'react-bootstrap'; import {FormattedMessage} from 'react-intl'; @@ -44,7 +42,6 @@ export default class RhsRootPost extends React.Component { 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.handleDropdownOpened = this.handleDropdownOpened.bind(this); this.canEdit = false; @@ -55,7 +52,7 @@ export default class RhsRootPost extends React.Component { currentTeamDisplayName: TeamStore.getCurrent().name, width: '', height: '', - showRHSEmojiPicker: false, + showEmojiPicker: false, testStateObj: true, dropdownOpened: false }; @@ -119,7 +116,7 @@ export default class RhsRootPost extends React.Component { return true; } - if (this.state.showRHSEmojiPicker !== nextState.showRHSEmojiPicker) { + if (this.state.showEmojiPicker !== nextState.showEmojiPicker) { return true; } @@ -175,12 +172,17 @@ export default class RhsRootPost extends React.Component { unpinPost(this.props.post.channel_id, this.props.post.id); } - emojiPickerClick() { - this.setState({showRHSEmojiPicker: !this.state.showRHSEmojiPicker}); + toggleEmojiPicker = () => { + const showEmojiPicker = !this.state.showEmojiPicker; + + this.setState({ + showEmojiPicker, + dropdownOpened: showEmojiPicker + }); } reactEmojiClick(emoji) { - this.setState({showRHSEmojiPicker: false}); + this.setState({showEmojiPicker: false}); const emojiName = emoji.name || emoji.aliases[0]; addReaction(this.props.post.channel_id, this.props.post.id, emojiName); } @@ -250,38 +252,26 @@ export default class RhsRootPost extends React.Component { } let react; - let reactOverlay; - if (!isEphemeral && !isPending && !isSystemMessage && Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMOJI_PICKER_PREVIEW)) { react = ( <span> + <EmojiPickerOverlay + show={this.state.showEmojiPicker} + onHide={this.toggleEmojiPicker} + target={() => this.refs.dotMenu} + container={this.props.getPostList} + onEmojiClick={this.reactEmojiClick} + /> <a href='#' className='reacticon__container reaction' - onClick={this.emojiPickerClick} + onClick={this.toggleEmojiPicker} 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)} - animation={false} - > - <EmojiPicker - onEmojiClick={this.reactEmojiClick} - pickerLocation='react' - /> - </Overlay> - ); } var dropdownContents = []; @@ -590,8 +580,10 @@ export default class RhsRootPost extends React.Component { isFlagged={this.props.isFlagged} /> </div> - <div className='col col__reply'> - {reactOverlay} + <div + ref='dotMenu' + className='col col__reply' + > {rootOptions} {react} </div> @@ -628,5 +620,6 @@ RhsRootPost.propTypes = { isFlagged: PropTypes.bool, status: PropTypes.string, previewCollapsed: PropTypes.string, - isBusy: PropTypes.bool + isBusy: PropTypes.bool, + getPostList: PropTypes.func.isRequired }; diff --git a/webapp/components/rhs_thread.jsx b/webapp/components/rhs_thread.jsx index bcb8a715f..a532119b8 100644 --- a/webapp/components/rhs_thread.jsx +++ b/webapp/components/rhs_thread.jsx @@ -338,6 +338,10 @@ export default class RhsThread extends React.Component { }); } + getPostListContainer = () => { + return this.refs.postListContainer; + } + render() { if (this.state.postsArray == null || this.state.selected == null) { return ( @@ -414,6 +418,7 @@ export default class RhsThread extends React.Component { isFlagged={isFlagged} status={status} isBusy={this.state.isBusy} + getPostList={this.getPostListContainer} /> </div> ); @@ -446,7 +451,7 @@ export default class RhsThread extends React.Component { onScroll={this.handleScroll} > <div - ref='post-right__scroll' + ref='postListContainer' className='post-right__scroll' > <DateSeparator @@ -464,6 +469,7 @@ export default class RhsThread extends React.Component { status={rootStatus} previewCollapsed={this.state.previewsCollapsed} isBusy={this.state.isBusy} + getPostList={this.getPostListContainer} /> <div ref='rhspostlist' diff --git a/webapp/package.json b/webapp/package.json index 6ebb33c20..eba2b8b00 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -83,7 +83,6 @@ "node-sass": "4.5.3", "raw-loader": "0.5.1", "react-addons-test-utils": "15.5.1", - "react-outside-event": "1.2.5", "remote-redux-devtools": "0.5.10", "remote-redux-devtools-on-debugger": "0.7.1", "sass-loader": "6.0.5", diff --git a/webapp/sass/components/_emoticons.scss b/webapp/sass/components/_emoticons.scss index 210c8620b..24d53eef3 100644 --- a/webapp/sass/components/_emoticons.scss +++ b/webapp/sass/components/_emoticons.scss @@ -8,7 +8,6 @@ top: 2px; vertical-align: middle; visibility: hidden; - } .emoticon { @@ -48,100 +47,6 @@ vertical-align: middle; } -.emoji-picker__popover { - padding: 0px; - - .popover-content { - padding: 0px; - } -} - -.emoji-picker-react-rhs-comment { - position: absolute; - width: 278px; - border: 1px solid; - min-height: 298px; - border-radius: 3px; - top: 21px; - right:59px; - z-index: 100; - .emoji-picker__search-container { - position: relative; - - .emoji-picker__search-icon { - padding-left: 6px; - padding-top: 6px; - position: absolute; - font-size: 13px; - } - - - } -} - -.emoji-picker-react { - position: absolute; - width: 278px; - border: 1px solid; - min-height: 298px; - border-radius: 3px; - top: 42px; - right:59px; - z-index: 100; - .emoji-picker__search-container { - position: relative; - - .emoji-picker__search-icon { - padding-left: 6px; - padding-top: 6px; - position: absolute; - font-size: 13px; - } - - - } -} - - -.emoji-picker-bottom { - display: flex; - flex-direction: column; - @include user-select(none); - position: absolute; - z-index: 40; - right: 0%; - width: 278px; - border: 1px solid; - min-height: 298px; - border-radius: 3px; - - .emoji-picker__search-container { - position: relative; - - .emoji-picker__search-icon { - padding-left: 6px; - padding-top: 6px; - position: absolute; - font-size: 13px; - } - - - } -} - -.emoji-picker__search { - border-width: 1px 0px 1px 0px; - border-style: solid; - padding: 2px 0 2px 25px; - width: 100%; - height: 25px; - font-size: 12px; - - &:focus{ - outline: none; - } -} - .emoji-picker { @include user-select(none); border-radius: 3px; @@ -149,23 +54,9 @@ display: flex; flex-direction: column; position: absolute; - right: 0; - top: -361px; + height: 362px; width: 278px; z-index: 8; - - .emoji-picker__search-container { - position: relative; - - .emoji-picker__search-icon { - padding-left: 6px; - padding-top: 6px; - position: absolute; - font-size: 13px; - } - - - } } .emoji-picker__categories { @@ -187,6 +78,30 @@ } } +.emoji-picker__search-container { + position: relative; + + .emoji-picker__search-icon { + padding-left: 6px; + padding-top: 6px; + position: absolute; + font-size: 13px; + } + + .emoji-picker__search { + border-width: 1px 0px 1px 0px; + border-style: solid; + padding: 2px 0 2px 25px; + width: 100%; + height: 25px; + font-size: 12px; + + &:focus{ + outline: none; + } + } +} + .emoji-picker__items { max-height: 262px; overflow-x: hidden; @@ -194,15 +109,12 @@ padding: 0px 8px 8px 8px; position: relative; - - .emoji-picker__category-header { font-size: 12px; font-weight: bold; margin-bottom: 6px; margin-top: 3px; padding-top: 3px; - // padding-bottom: 10px; } .emoji-picker__preview_sprite { @@ -238,25 +150,21 @@ } } - .emoji-picker__item { - font-size: 18px; - margin: 3px; - } - .emoticon { - max-height: 21px; - max-width: 21px; - min-height: 0; - min-width: 0; - width: auto; - height: auto; - margin: 0 auto; + .emoji-picker__item-wrapper { + .emoji-picker__item { + font-size: 18px; + margin: 3px; + max-height: 21px; + max-width: 21px; + min-height: 0; + min-width: 0; + width: auto; + height: auto; + margin: 0 auto; + } } } -.emojisprite-wrapper { - cursor: pointer; -} - .emoji-picker__preview { border-top: 1px solid; display: flex; @@ -294,6 +202,7 @@ font-weight: bold; } } + .emoji-picker__preview-image-box { display: flex; align-items: center; @@ -318,5 +227,4 @@ max-height: 36px; max-width: 42px; } - } diff --git a/webapp/sass/layout/_post-right.scss b/webapp/sass/layout/_post-right.scss index 531638ec7..85dd20f53 100644 --- a/webapp/sass/layout/_post-right.scss +++ b/webapp/sass/layout/_post-right.scss @@ -92,6 +92,12 @@ margin-right: 10px; padding: 6px 0; } + + .emoji-picker { + position: absolute; + top: -361px; + right: 0px; + } } } diff --git a/webapp/sass/layout/_post.scss b/webapp/sass/layout/_post.scss index 85a4bb2cd..73253f4be 100644 --- a/webapp/sass/layout/_post.scss +++ b/webapp/sass/layout/_post.scss @@ -393,6 +393,12 @@ } } + .emoji-picker { + position: absolute; + top: -361px; + right: 0px; + } + .post-create.scroll { .btn-file { right: 10px; diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx index de0976c14..1f3716039 100644 --- a/webapp/utils/utils.jsx +++ b/webapp/utils/utils.jsx @@ -596,10 +596,7 @@ export function applyTheme(theme) { changeCss('body.app__body', 'scrollbar-track-color:' + theme.centerChannelBg); changeCss('.app__body .post-list__new-messages-below', 'color:' + theme.centerChannelBg); changeCss('.app__body .emoji-picker, .app__body .emoji-picker__search', 'background:' + theme.centerChannelBg); - changeCss('.app__body .emoji-picker-react, .app__body .emoji-picker__search', 'background:' + theme.centerChannelBg); - changeCss('.app__body .emoji-picker-react-rhs-comment, .app__body .emoji-picker__search', 'background:' + theme.centerChannelBg); - - changeCss('.app__body .nav-tabs, .app__body .nav-tabs > li.active > a, .app__body .emoji-picker-bottom, .app__body .emoji-picker__search', 'background:' + theme.centerChannelBg); + changeCss('.app__body .nav-tabs, .app__body .nav-tabs > li.active > a', 'background:' + theme.centerChannelBg); } if (theme.centerChannelColor) { @@ -678,12 +675,8 @@ export function applyTheme(theme) { changeCss('.app__body .post-reaction:not(.post-reaction--current-user)', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.25)); changeCss('.app__body .post-reaction:not(.post-reaction--current-user)', 'color:' + changeOpacity(theme.centerChannelColor, 0.7)); changeCss('.app__body .emoji-picker', 'color:' + theme.centerChannelColor); - changeCss('.app__body .emoji-picker-react', 'color:' + theme.centerChannelColor); - changeCss('.app__body .emoji-picker-bottom', 'color:' + theme.centerChannelColor); - changeCss('.app__body .emoji-picker, .app__body .emoji-picker-react-rhs-comment, .app__body .emoji-picker-react, .app__body .emoji-picker__search-container .emoji-picker__search', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2)); - changeCss('.app__body .emoji-picker-bottom, .app__body .emoji-picker__search-container .emoji-picker__search', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2)); + changeCss('.app__body .emoji-picker, .app__body .emoji-picker__search-container .emoji-picker__search', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2)); changeCss('.app__body .emoji-picker, .app__body .emoji-picker__items .emoji-picker__search-container .emoji-picker__search', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2)); - changeCss('.app__body .emoji-picker-bottom, .app__body .emoji-picker__items .emoji-picker__search-container .emoji-picker__search', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2)); changeCss('.app__body .emoji-picker__items', 'background-color:' + changeOpacity(theme.centerChannelColor, 0.05)); changeCss('.app__body .emoji-picker__categories', 'border-bottom-color:' + changeOpacity(theme.centerChannelColor, 0.2)); changeCss('.emoji-picker__category .fa:hover', 'color:' + changeOpacity(theme.centerChannelColor, 0.8)); |