diff options
Diffstat (limited to 'web/react')
-rw-r--r-- | web/react/components/create_post.jsx | 1 | ||||
-rw-r--r-- | web/react/components/post_info.jsx | 18 | ||||
-rw-r--r-- | web/react/components/post_list_container.jsx | 25 | ||||
-rw-r--r-- | web/react/components/posts_view.jsx | 70 | ||||
-rw-r--r-- | web/react/components/sidebar_right.jsx | 65 | ||||
-rw-r--r-- | web/react/components/updating_time_since_counter.jsx | 50 | ||||
-rw-r--r-- | web/react/pages/channel.jsx | 4 | ||||
-rw-r--r-- | web/react/stores/post_store.jsx | 29 | ||||
-rw-r--r-- | web/react/utils/constants.jsx | 5 |
9 files changed, 205 insertions, 62 deletions
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index cdbc3bc6d..eb702bf7c 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -176,6 +176,7 @@ export default class CreatePost extends React.Component { PostStore.storePendingPost(post); PostStore.storeDraft(channel.id, null); + PostStore.jumpPostListBottom(); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); Client.createPost(post, channel, diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx index ddda48e06..202b043ce 100644 --- a/web/react/components/post_info.jsx +++ b/web/react/components/post_info.jsx @@ -3,10 +3,9 @@ var UserStore = require('../stores/user_store.jsx'); var utils = require('../utils/utils.jsx'); +var UpdatingTimeSinceCounter = require('./updating_time_since_counter.jsx'); var Constants = require('../utils/constants.jsx'); -var Tooltip = ReactBootstrap.Tooltip; -var OverlayTrigger = ReactBootstrap.OverlayTrigger; export default class PostInfo extends React.Component { constructor(props) { @@ -144,21 +143,12 @@ export default class PostInfo extends React.Component { var dropdown = this.createDropdown(); - let tooltip = <Tooltip id={post.id + 'tooltip'}>{`${utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}`}</Tooltip>; - return ( <ul className='post-header post-info'> <li className='post-header-col'> - <OverlayTrigger - delayShow={500} - container={this} - placement='top' - overlay={tooltip} - > - <time className='post-profile-time'> - {utils.displayDateTime(post.create_at)} - </time> - </OverlayTrigger> + <UpdatingTimeSinceCounter + eventTime={post.create_at} + /> </li> <li className='post-header-col post-header__reply'> <div className='dropdown'> diff --git a/web/react/components/post_list_container.jsx b/web/react/components/post_list_container.jsx index 5e12f0e2b..90468ab66 100644 --- a/web/react/components/post_list_container.jsx +++ b/web/react/components/post_list_container.jsx @@ -25,10 +25,12 @@ export default class PostListContainer extends React.Component { this.loadMorePostsTop = this.loadMorePostsTop.bind(this); this.postsLoaded = this.postsLoaded.bind(this); this.postsLoadedFailure = this.postsLoadedFailure.bind(this); + this.handlePostListJumpRequest = this.handlePostListJumpRequest.bind(this); const currentChannelId = ChannelStore.getCurrentId(); const state = { scrollType: PostsView.SCROLL_TYPE_BOTTOM, + scrollPost: null, numPostsToDisplay: Constants.POST_CHUNK_SIZE }; if (currentChannelId) { @@ -51,11 +53,29 @@ export default class PostListContainer extends React.Component { ChannelStore.addChangeListener(this.onChannelChange); ChannelStore.addLeaveListener(this.onChannelLeave); PostStore.addChangeListener(this.onPostsChange); + PostStore.addPostListJumpListener(this.handlePostListJumpRequest); } componentWillUnmount() { ChannelStore.removeChangeListener(this.onChannelChange); ChannelStore.removeLeaveListener(this.onChannelLeave); PostStore.removeChangeListener(this.onPostsChange); + PostStore.removePostListJumpListener(this.handlePostListJumpRequest); + } + handlePostListJumpRequest(type, post) { + switch (type) { + case Constants.PostListJumpTypes.BOTTOM: + this.setState({scrollType: PostsView.SCROLL_TYPE_BOTTOM}); + break; + case Constants.PostListJumpTypes.POST: + this.setState({ + scrollType: PostsView.SCROLL_TYPE_POST, + scrollPost: post + }); + break; + case Constants.PostListJumpTypes.SIDEBAR_OPEN: + this.setState({scrollType: PostsView.SIDEBAR_OPEN}); + break; + } } onChannelChange() { const postLists = Object.assign({}, this.state.postLists); @@ -70,7 +90,7 @@ export default class PostListContainer extends React.Component { PostStore.clearUnseenDeletedPosts(channelId); let lastViewed = Number.MAX_VALUE; - let member = ChannelStore.getMember(channelId); + const member = ChannelStore.getMember(channelId); if (member != null) { lastViewed = member.last_viewed_at; } @@ -219,10 +239,11 @@ export default class PostListContainer extends React.Component { isActive={isActive} postList={postLists[i]} scrollType={this.state.scrollType} + scrollPost={this.state.scrollPost} postListScrolled={this.handlePostListScroll} loadMorePostsTopClicked={this.loadMorePostsTop} numPostsToDisplay={this.state.numPostsToDisplay} - introText={channel ? createChannelIntroMessage(channel) : []} + introText={channel ? createChannelIntroMessage(channel) : null} messageSeparatorTime={this.state.currentLastViewed} /> ); diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx index d9a95af8b..57e7abd35 100644 --- a/web/react/components/posts_view.jsx +++ b/web/react/components/posts_view.jsx @@ -10,11 +10,16 @@ export default class PostsView extends React.Component { super(props); this.handleScroll = this.handleScroll.bind(this); + this.isAtBottom = this.isAtBottom.bind(this); this.loadMorePostsTop = this.loadMorePostsTop.bind(this); this.postsRerendered = this.postsRerendered.bind(this); this.createPosts = this.createPosts.bind(this); - this.scrollToBottom = this.scrollToBottom.bind(this); + this.updateScrolling = this.updateScrolling.bind(this); this.handleResize = this.handleResize.bind(this); + + this.jumpToPostNode = null; + this.wasAtBottom = true; + this.scrollHeight = 0; } static get SCROLL_TYPE_FREE() { return 1; @@ -22,9 +27,28 @@ export default class PostsView extends React.Component { static get SCROLL_TYPE_BOTTOM() { return 2; } + static get SIDEBAR_OPEN() { + return 3; + } + isAtBottom() { + return ((this.refs.postlist.scrollHeight - this.refs.postlist.scrollTop) === this.refs.postlist.clientHeight); + } handleScroll() { - const atBottom = ((this.refs.postlist.scrollHeight - this.refs.postlist.scrollTop) === this.refs.postlist.clientHeight); - this.props.postListScrolled(atBottom); + // HACK FOR RHS -- REMOVE WHEN RHS DIES + const childNodes = this.refs.postlistcontent.childNodes; + for (let i = 0; i < childNodes.length; i++) { + // If the node is 1/3 down the page + if (childNodes[i].offsetTop > (this.refs.postlist.scrollTop + (this.refs.postlist.offsetHeight / 3))) { + this.jumpToPostNode = childNodes[i]; + break; + } + } + this.wasAtBottom = this.isAtBottom(); + + // --- -------- + + this.props.postListScrolled(this.isAtBottom()); + this.prevScrollHeight = this.refs.postlist.scrollHeight; } loadMorePostsTop() { this.props.loadMorePostsTopClicked(); @@ -134,25 +158,53 @@ export default class PostsView extends React.Component { return postCtls; } - scrollToBottom() { + updateScrolling() { if (this.props.scrollType === PostsView.SCROLL_TYPE_BOTTOM) { window.requestAnimationFrame(() => { this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight; }); + } else if (this.props.scrollType === PostsView.SCROLL_TYPE_POST && this.props.scrollPost) { + window.requestAnimationFrame(() => { + const postNode = ReactDOM.findDOMNode(this.refs[this.props.scrollPost]); + postNode.scrollIntoView(); + if (this.refs.postlist.scrollTop === postNode.offsetTop) { + this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / 3); + } else { + this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / 3) + (this.refs.postlist.scrollTop - postNode.offsetTop); + } + }); + } else if (this.props.scrollType === PostsView.SIDEBAR_OPEN) { + // If we are at the bottom then stay there + if (this.wasAtBottom) { + this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight; + } else { + window.requestAnimationFrame(() => { + this.jumpToPostNode.scrollIntoView(); + if (this.refs.postlist.scrollTop === this.jumpToPostNode.offsetTop) { + this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / 3); + } else { + this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / 3) + (this.refs.postlist.scrollTop - this.jumpToPostNode.offsetTop); + } + }); + } + } else if (this.refs.postlist.scrollHeight !== this.prevScrollHeight) { + window.requestAnimationFrame(() => { + this.refs.postlist.scrollTop += (this.refs.postlist.scrollHeight - this.prevScrollHeight); + }); } } handleResize() { - this.scrollToBottom(); + this.updateScrolling(); } componentDidMount() { - this.scrollToBottom(); + this.updateScrolling(); window.addEventListener('resize', this.handleResize); } componentWillUnmount() { window.removeEventListener('resize', this.handleResize); } componentDidUpdate() { - this.scrollToBottom(); + this.updateScrolling(); } shouldComponentUpdate(nextProps) { if (this.props.isActive !== nextProps.isActive) { @@ -197,7 +249,7 @@ export default class PostsView extends React.Component { className='more-messages-text theme' href='#' onClick={this.loadMorePostsTop} - > + > {'Load more messages'} </a> ); @@ -238,7 +290,7 @@ PostsView.defaultProps = { PostsView.propTypes = { isActive: React.PropTypes.bool, - postList: React.PropTypes.object.isRequired, + postList: React.PropTypes.object, scrollPost: React.PropTypes.string, scrollType: React.PropTypes.number, postListScrolled: React.PropTypes.func.isRequired, diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx index 2ec2b5bbf..020db6d88 100644 --- a/web/react/components/sidebar_right.jsx +++ b/web/react/components/sidebar_right.jsx @@ -20,23 +20,48 @@ export default class SidebarRight extends React.Component { this.onSelectedChange = this.onSelectedChange.bind(this); this.onSearchChange = this.onSearchChange.bind(this); + this.doStrangeThings = this.doStrangeThings.bind(this); + this.state = getStateFromStores(); } componentDidMount() { SearchStore.addSearchChangeListener(this.onSearchChange); PostStore.addSelectedPostChangeListener(this.onSelectedChange); + this.doStrangeThings(); } componentWillUnmount() { SearchStore.removeSearchChangeListener(this.onSearchChange); PostStore.removeSelectedPostChangeListener(this.onSelectedChange); } - componentDidUpdate() { - if (this.plScrolledToBottom) { - var postHolder = $('.post-list-holder-by-time').not('.inactive'); - postHolder.scrollTop(postHolder[0].scrollHeight); - } else { - $('.top-visible-post')[0].scrollIntoView(); + componentWillUpdate() { + PostStore.jumpPostListSidebarOpen(); + } + doStrangeThings() { + // We should have a better way to do this stuff + // Hence the function name. + $('.inner__wrap').removeClass('.move--right'); + $('.inner__wrap').addClass('move--left'); + $('.sidebar--left').removeClass('move--right'); + $('.sidebar--right').addClass('move--left'); + + //$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>'); + + if (!(this.state.search_visible || this.state.post_right_visible)) { + $('.inner__wrap').removeClass('move--left').removeClass('move--right'); + $('.sidebar--right').removeClass('move--left'); + return ( + <div></div> + ); } + + /*setTimeout(() => { + $('.sidebar__overlay').fadeOut('200', () => { + $('.sidebar__overlay').remove(); + }); + }, 500);*/ + } + componentDidUpdate() { + this.doStrangeThings(); } onSelectedChange(fromSearch) { var newState = getStateFromStores(fromSearch); @@ -52,34 +77,6 @@ export default class SidebarRight extends React.Component { } } render() { - var postHolder = $('.post-list-holder-by-time').not('.inactive'); - if (postHolder[0]) { - const position = postHolder.scrollTop() + postHolder.height() + 14; - const bottom = postHolder[0].scrollHeight; - this.plScrolledToBottom = position >= bottom; - } else { - this.plScrolledToBottom = true; - } - - if (!(this.state.search_visible || this.state.post_right_visible)) { - $('.inner__wrap').removeClass('move--left').removeClass('move--right'); - $('.sidebar--right').removeClass('move--left'); - return ( - <div></div> - ); - } - - $('.inner__wrap').removeClass('.move--right').addClass('move--left'); - $('.sidebar--left').removeClass('move--right'); - $('.sidebar--right').addClass('move--left'); - $('.sidebar--right').prepend('<div class="sidebar__overlay"></div>'); - - setTimeout(() => { - $('.sidebar__overlay').fadeOut('200', function fadeOverlay() { - $(this).remove(); - }); - }, 500); - var content = ''; if (this.state.search_visible) { diff --git a/web/react/components/updating_time_since_counter.jsx b/web/react/components/updating_time_since_counter.jsx new file mode 100644 index 000000000..d06ffb842 --- /dev/null +++ b/web/react/components/updating_time_since_counter.jsx @@ -0,0 +1,50 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +var Utils = require('../utils/utils.jsx'); + +var Tooltip = ReactBootstrap.Tooltip; +var OverlayTrigger = ReactBootstrap.OverlayTrigger; + +export default class UpdatingTimeSinceCounter extends React.Component { + constructor(props) { + super(props); + } + componentDidMount() { + this.intervalId = setInterval(() => { + this.forceUpdate(); + }, 30000); + } + componentWillUnmount() { + clearInterval(this.intervalId); + } + render() { + const displayDate = Utils.displayDate(this.props.eventTime); + const displayTime = Utils.displayTime(this.props.eventTime); + + const tooltip = ( + <Tooltip id={'time-since-tooltip-' + this.props.eventTime}> + {displayDate + ' at ' + displayTime} + </Tooltip> + ); + + return ( + <OverlayTrigger + delayShow={400} + placement='top' + overlay={tooltip} + > + <time className='post-profile-time'> + {Utils.displayDateTime(this.props.eventTime)} + </time> + </OverlayTrigger> + ); + } +} +UpdatingTimeSinceCounter.defaultProps = { + eventTime: 0 +}; + +UpdatingTimeSinceCounter.propTypes = { + eventTime: React.PropTypes.number.isRequired +}; diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index 464561742..067dcde50 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -55,9 +55,7 @@ function setupChannelPage(props) { ); ReactDOM.render( - <ChannelView - - />, + <ChannelView/>, document.getElementById('channel_view') ); diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx index 8f4e30e7c..19b200ac8 100644 --- a/web/react/stores/post_store.jsx +++ b/web/react/stores/post_store.jsx @@ -14,6 +14,7 @@ var ActionTypes = Constants.ActionTypes; var CHANGE_EVENT = 'change'; var SELECTED_POST_CHANGE_EVENT = 'selected_post_change'; var EDIT_POST_EVENT = 'edit_post'; +var POST_LIST_JUMP_EVENT = 'post_list_jump'; class PostStoreClass extends EventEmitter { constructor() { @@ -31,6 +32,10 @@ class PostStoreClass extends EventEmitter { this.addEditPostListener = this.addEditPostListener.bind(this); this.removeEditPostListener = this.removeEditPostListener.bind(this); + this.emitPostListJump = this.emitPostListJump.bind(this); + this.addPostListJumpListener = this.addPostListJumpListener.bind(this); + this.removePostListJumpListener = this.removePostListJumpListener.bind(this); + this.getCurrentPosts = this.getCurrentPosts.bind(this); this.storePosts = this.storePosts.bind(this); this.pStorePosts = this.pStorePosts.bind(this); @@ -100,6 +105,30 @@ class PostStoreClass extends EventEmitter { this.removeListener(EDIT_POST_EVENT, callback); } + emitPostListJump(type, post) { + this.emit(POST_LIST_JUMP_EVENT, type, post); + } + + addPostListJumpListener(callback) { + this.on(POST_LIST_JUMP_EVENT, callback); + } + + removePostListJumpListener(callback) { + this.removeListener(POST_LIST_JUMP_EVENT, callback); + } + + jumpPostListBottom() { + this.emitPostListJump(Constants.PostListJumpTypes.BOTTOM, null); + } + + jumpPostListToPost(post) { + this.emitPostListJump(Constants.PostListJumpTypes.POST, post); + } + + jumpPostListSidebarOpen() { + this.emitPostListJump(Constants.PostListJumpTypes.SIDEBAR_OPEN, null); + } + getCurrentPosts() { var currentId = ChannelStore.getCurrentId(); diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 1593f6706..c97e4d982 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -350,5 +350,10 @@ module.exports = { ruby: 'Ruby', java: 'Java', ini: 'ini' + }, + PostListJumpTypes: { + BOTTOM: 1, + POST: 2, + SIDEBAR_OPEN: 3 } }; |