From 55f137c19cf45f4fd17249b5a23650c84b0d4f23 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Tue, 27 Oct 2015 15:48:51 -0400 Subject: Creating hierarchy --- web/react/components/center_panel.jsx | 54 +++++++++++++++++++ web/react/components/channel_view.jsx | 43 +++++++++++++++ web/react/components/sidebar.jsx | 17 ++---- web/react/components/sidebar_right.jsx | 10 ++-- web/react/pages/channel.jsx | 95 +++++++++------------------------ web/sass-files/sass/partials/_base.scss | 32 +++++------ web/templates/channel.html | 19 +------ 7 files changed, 148 insertions(+), 122 deletions(-) create mode 100644 web/react/components/center_panel.jsx create mode 100644 web/react/components/channel_view.jsx (limited to 'web') diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx new file mode 100644 index 000000000..7cb55898f --- /dev/null +++ b/web/react/components/center_panel.jsx @@ -0,0 +1,54 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +var CreatePost = require('../components/create_post.jsx'); +var PostListContainer = require('../components/post_list_container.jsx'); +var ChannelHeader = require('../components/channel_header.jsx'); +var Navbar = require('../components/navbar.jsx'); +var FileUploadOverlay = require('../components/file_upload_overlay.jsx'); + +export default class CenterPanel extends React.Component { + constructor(props) { + super(props); + } + render() { + return ( +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ ); + } +} + +CenterPanel.defaultProps = { +}; + +CenterPanel.propTypes = { +}; diff --git a/web/react/components/channel_view.jsx b/web/react/components/channel_view.jsx new file mode 100644 index 000000000..beafa7d63 --- /dev/null +++ b/web/react/components/channel_view.jsx @@ -0,0 +1,43 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +var CenterPanel = require('../components/center_panel.jsx'); +var Sidebar = require('../components/sidebar.jsx'); +var SidebarRight = require('../components/sidebar_right.jsx'); +var SidebarRightMenu = require('../components/sidebar_right_menu.jsx'); + +export default class ChannelView extends React.Component { + constructor(props) { + super(props); + } + render() { + return ( +
+ + + + +
+ ); + } +} +ChannelView.defaultProps = { +}; + +ChannelView.propTypes = { +}; diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 5cb6d168b..88753556c 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -178,10 +178,6 @@ export default class Sidebar extends React.Component { window.addEventListener('resize', this.handleResize); } shouldComponentUpdate(nextProps, nextState) { - if (!Utils.areStatesEqual(nextProps, this.props)) { - return true; - } - if (!Utils.areStatesEqual(nextState, this.state)) { return true; } @@ -235,7 +231,7 @@ export default class Sidebar extends React.Component { const unread = this.getUnreadCount(); const mentionTitle = unread.mentions > 0 ? '(' + unread.mentions + ') ' : ''; const unreadTitle = unread.msgs > 0 ? '* ' : ''; - document.title = mentionTitle + unreadTitle + currentChannelName + ' - ' + this.props.teamDisplayName + ' ' + currentSiteName; + document.title = mentionTitle + unreadTitle + currentChannelName + ' - ' + TeamStore.getCurrent().display_name + ' ' + currentSiteName; } } onScroll() { @@ -543,9 +539,9 @@ export default class Sidebar extends React.Component { /> @@ -631,11 +627,6 @@ export default class Sidebar extends React.Component { } Sidebar.defaultProps = { - teamType: '', - teamDisplayName: '' }; Sidebar.propTypes = { - teamType: React.PropTypes.string, - teamDisplayName: React.PropTypes.string, - teamName: React.PropTypes.string }; diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx index 51225cbbe..2ec2b5bbf 100644 --- a/web/react/components/sidebar_right.jsx +++ b/web/react/components/sidebar_right.jsx @@ -53,9 +53,13 @@ export default class SidebarRight extends React.Component { } render() { var postHolder = $('.post-list-holder-by-time').not('.inactive'); - const position = postHolder.scrollTop() + postHolder.height() + 14; - const bottom = postHolder[0].scrollHeight; - this.plScrolledToBottom = position >= bottom; + 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'); diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index 7a04c5979..464561742 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -2,13 +2,12 @@ // See License.txt for license information. var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); -var Navbar = require('../components/navbar.jsx'); -var Sidebar = require('../components/sidebar.jsx'); -var ChannelHeader = require('../components/channel_header.jsx'); -var PostListContainer = require('../components/post_list_container.jsx'); -var CreatePost = require('../components/create_post.jsx'); -var SidebarRight = require('../components/sidebar_right.jsx'); -var SidebarRightMenu = require('../components/sidebar_right_menu.jsx'); +var ChannelView = require('../components/channel_view.jsx'); +var ChannelLoader = require('../components/channel_loader.jsx'); +var ErrorBar = require('../components/error_bar.jsx'); +var ErrorStore = require('../stores/error_store.jsx'); + +var MentionList = require('../components/mention_list.jsx'); var GetLinkModal = require('../components/get_link_modal.jsx'); var MemberInviteModal = require('../components/invite_member_modal.jsx'); var EditChannelModal = require('../components/edit_channel_modal.jsx'); @@ -24,15 +23,10 @@ var TeamSettingsModal = require('../components/team_settings_modal.jsx'); var ChannelMembersModal = require('../components/channel_members.jsx'); var ChannelInviteModal = require('../components/channel_invite_modal.jsx'); var TeamMembersModal = require('../components/team_members.jsx'); -var ErrorBar = require('../components/error_bar.jsx'); -var ErrorStore = require('../stores/error_store.jsx'); -var ChannelLoader = require('../components/channel_loader.jsx'); -var MentionList = require('../components/mention_list.jsx'); var ChannelInfoModal = require('../components/channel_info_modal.jsx'); var AccessHistoryModal = require('../components/access_history_modal.jsx'); var ActivityLogModal = require('../components/activity_log_modal.jsx'); var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx'); -var FileUploadOverlay = require('../components/file_upload_overlay.jsx'); var RegisterAppModal = require('../components/register_app_modal.jsx'); var ImportThemeModal = require('../components/user_settings/import_theme_modal.jsx'); @@ -61,19 +55,30 @@ function setupChannelPage(props) { ); ReactDOM.render( - , - document.getElementById('navbar') + , + document.getElementById('channel_view') ); ReactDOM.render( - , - document.getElementById('sidebar-left') + , + document.getElementById('post_mention_tab') + ); + + ReactDOM.render( + , + document.getElementById('reply_mention_tab') ); + ReactDOM.render( + , + document.getElementById('edit_mention_tab') + ); + + // + // Modals + // ReactDOM.render( , document.getElementById('get_link_modal') @@ -104,11 +109,6 @@ function setupChannelPage(props) { document.getElementById('invite_member_modal') ); - ReactDOM.render( - , - document.getElementById('channel-header') - ); - ReactDOM.render( , document.getElementById('edit_channel_modal') @@ -149,11 +149,6 @@ function setupChannelPage(props) { document.getElementById('more_channels_modal') ); - ReactDOM.render( - , - document.getElementById('post-list') - ); - ReactDOM.render( , document.getElementById('edit_post_modal') @@ -169,39 +164,6 @@ function setupChannelPage(props) { document.getElementById('post_deleted_modal') ); - ReactDOM.render( - , - document.getElementById('post-create') - ); - - ReactDOM.render( - , - document.getElementById('sidebar-right') - ); - - ReactDOM.render( - , - document.getElementById('sidebar-menu') - ); - - ReactDOM.render( - , - document.getElementById('post_mention_tab') - ); - - ReactDOM.render( - , - document.getElementById('reply_mention_tab') - ); - - ReactDOM.render( - , - document.getElementById('edit_mention_tab') - ); - ReactDOM.render( , document.getElementById('access_history_modal') @@ -217,13 +179,6 @@ function setupChannelPage(props) { document.getElementById('removed_from_channel_modal') ); - ReactDOM.render( - , - document.getElementById('file_upload_overlay') - ); - ReactDOM.render( , document.getElementById('register_app_modal') diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss index 635928fe3..a9f46a815 100644 --- a/web/sass-files/sass/partials/_base.scss +++ b/web/sass-files/sass/partials/_base.scss @@ -8,30 +8,26 @@ body { background: $body-bg; position: relative; height: 100%; - &.white { - background: #fff; - > .container-fluid { - overflow: auto; - } - .inner__wrap { - > .row.content { - min-height: 100%; - margin-bottom: -89px; - } - } - } - .inner__wrap { +} + +.inner__wrap { height: 100%; > .row.main { - height: 100%; - position: relative; + height: 100%; + position: relative; } - } - > .container-fluid { +} + +.container-fluid { + @include clearfix; + height: 100%; + position: relative; +} + +.channel-view { @include clearfix; height: 100%; position: relative; - } } img { diff --git a/web/templates/channel.html b/web/templates/channel.html index 4b8318d43..63fe38587 100644 --- a/web/templates/channel.html +++ b/web/templates/channel.html @@ -5,24 +5,7 @@ {{template "head" . }}
-
- - - -
-
- -
-
-
-
-
-
-
-
-
-
-
+
-- cgit v1.2.3-1-g7c22 From ed68f2e9015f3ac94ef1d5f7bf2941611625c60d Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Thu, 29 Oct 2015 17:45:33 -0400 Subject: Refactoring center channel --- web/react/components/post_list.jsx | 764 --------------------------- web/react/components/post_list_container.jsx | 232 +++++++- web/react/components/posts_view.jsx | 249 +++++++++ web/react/utils/channel_intro_mssages.jsx | 218 ++++++++ web/sass-files/sass/partials/_post.scss | 3 + 5 files changed, 676 insertions(+), 790 deletions(-) delete mode 100644 web/react/components/post_list.jsx create mode 100644 web/react/components/posts_view.jsx create mode 100644 web/react/utils/channel_intro_mssages.jsx (limited to 'web') diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx deleted file mode 100644 index 444736db5..000000000 --- a/web/react/components/post_list.jsx +++ /dev/null @@ -1,764 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -const Post = require('./post.jsx'); -const UserProfile = require('./user_profile.jsx'); -const AsyncClient = require('../utils/async_client.jsx'); -const LoadingScreen = require('./loading_screen.jsx'); - -const PostStore = require('../stores/post_store.jsx'); -const ChannelStore = require('../stores/channel_store.jsx'); -const UserStore = require('../stores/user_store.jsx'); -const TeamStore = require('../stores/team_store.jsx'); -const SocketStore = require('../stores/socket_store.jsx'); -const PreferenceStore = require('../stores/preference_store.jsx'); - -const Utils = require('../utils/utils.jsx'); -const Client = require('../utils/client.jsx'); -const Constants = require('../utils/constants.jsx'); -const ActionTypes = Constants.ActionTypes; -const SocketEvents = Constants.SocketEvents; - -const AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); - -export default class PostList extends React.Component { - constructor(props) { - super(props); - - this.gotMorePosts = false; - this.scrolled = false; - this.prevScrollTop = 0; - this.seenNewMessages = false; - this.isUserScroll = true; - this.userHasSeenNew = false; - this.loadInProgress = false; - - this.onChange = this.onChange.bind(this); - this.onTimeChange = this.onTimeChange.bind(this); - this.onSocketChange = this.onSocketChange.bind(this); - this.createChannelIntroMessage = this.createChannelIntroMessage.bind(this); - this.loadMorePosts = this.loadMorePosts.bind(this); - this.loadFirstPosts = this.loadFirstPosts.bind(this); - this.activate = this.activate.bind(this); - this.deactivate = this.deactivate.bind(this); - this.handleResize = this.handleResize.bind(this); - this.resizePostList = this.resizePostList.bind(this); - this.updateScroll = this.updateScroll.bind(this); - - const state = this.getStateFromStores(props.channelId); - state.numToDisplay = Constants.POST_CHUNK_SIZE; - state.isFirstLoadComplete = false; - state.windowHeight = Utils.windowHeight(); - - this.state = state; - } - getStateFromStores(id) { - var postList = PostStore.getPosts(id); - - if (postList != null) { - var deletedPosts = PostStore.getUnseenDeletedPosts(id); - - if (deletedPosts && Object.keys(deletedPosts).length > 0) { - for (var pid in deletedPosts) { - if (deletedPosts.hasOwnProperty(pid)) { - postList.posts[pid] = deletedPosts[pid]; - postList.order.unshift(pid); - } - } - - postList.order.sort((a, b) => { - if (postList.posts[a].create_at > postList.posts[b].create_at) { - return -1; - } - if (postList.posts[a].create_at < postList.posts[b].create_at) { - return 1; - } - return 0; - }); - } - - var pendingPostList = PostStore.getPendingPosts(id); - - if (pendingPostList) { - postList.order = pendingPostList.order.concat(postList.order); - for (var ppid in pendingPostList.posts) { - if (pendingPostList.posts.hasOwnProperty(ppid)) { - postList.posts[ppid] = pendingPostList.posts[ppid]; - } - } - } - } - - return { - postList - }; - } - componentDidMount() { - window.onload = () => this.scrollToBottom(); - if (this.props.isActive) { - this.activate(); - this.loadFirstPosts(this.props.channelId); - } - } - componentWillUnmount() { - this.deactivate(); - } - activate() { - this.gotMorePosts = false; - this.scrolled = false; - this.prevScrollTop = 0; - this.seenNewMessages = false; - this.isUserScroll = true; - this.userHasSeenNew = false; - - PostStore.clearUnseenDeletedPosts(this.props.channelId); - PostStore.addChangeListener(this.onChange); - UserStore.addStatusesChangeListener(this.onTimeChange); - PreferenceStore.addChangeListener(this.onTimeChange); - SocketStore.addChangeListener(this.onSocketChange); - - const postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); - - window.addEventListener('resize', this.handleResize); - - postHolder.on('scroll', () => { - const position = postHolder.scrollTop() + postHolder.height() + 14; - const bottom = postHolder[0].scrollHeight; - - if (position >= bottom) { - this.scrolled = false; - } else { - this.scrolled = true; - } - - if (this.isUserScroll) { - this.userHasSeenNew = true; - } - this.isUserScroll = true; - - $('.top-visible-post').removeClass('top-visible-post'); - - $(ReactDOM.findDOMNode(this.refs.postlistcontent)).children().each(function select() { - if ($(this).position().top + $(this).height() / 2 > 0) { - $(this).addClass('top-visible-post'); - return false; - } - }); - }); - - $('.post-list__content div .post').removeClass('post--last'); - $('.post-list__content div:last-child .post').addClass('post--last'); - - if (!this.state.isFirstLoadComplete) { - this.loadFirstPosts(this.props.channelId); - } - - this.resizePostList(); - this.onChange(); - this.scrollToBottom(); - } - deactivate() { - PostStore.removeChangeListener(this.onChange); - UserStore.removeStatusesChangeListener(this.onTimeChange); - SocketStore.removeChangeListener(this.onSocketChange); - PreferenceStore.removeChangeListener(this.onTimeChange); - $('body').off('click.userpopover'); - - window.removeEventListener('resize', this.handleResize); - - var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); - postHolder.off('scroll'); - } - componentDidUpdate(prevProps, prevState) { - if (!this.props.isActive) { - return; - } - - if (prevState.windowHeight !== this.state.windowHeight) { - this.resizePostList(); - if (!this.scrolled) { - this.scrollToBottom(); - } - } - - $('.post-list__content div .post').removeClass('post--last'); - $('.post-list__content div:last-child .post').addClass('post--last'); - - if (this.state.postList == null || prevState.postList == null) { - this.scrollToBottom(); - return; - } - - var order = this.state.postList.order || []; - var posts = this.state.postList.posts || {}; - var oldOrder = prevState.postList.order || []; - var oldPosts = prevState.postList.posts || {}; - var userId = UserStore.getCurrentId(); - var firstPost = posts[order[0]] || {}; - var isNewPost = oldOrder.indexOf(order[0]) === -1; - - if (this.props.isActive && !prevProps.isActive) { - this.scrollToBottom(); - } else if (oldOrder.length === 0) { - this.scrollToBottom(); - - // the user is scrolled to the bottom - } else if (!this.scrolled) { - this.scrollToBottom(); - - // there's a new post and - // it's by the user (and not from their webhook) and not a comment - } else if (isNewPost && - userId === firstPost.user_id && - !firstPost.props.from_webhook && - !Utils.isComment(firstPost)) { - this.scrollToBottom(true); - - // the user clicked 'load more messages' - } else if (this.gotMorePosts && oldOrder.length > 0) { - let index; - if (prevState.numToDisplay >= oldOrder.length) { - index = oldOrder.length - 1; - } else { - index = prevState.numToDisplay; - } - const lastPost = oldPosts[oldOrder[index]]; - $('#post_' + lastPost.id)[0].scrollIntoView(); - this.gotMorePosts = false; - } else { - this.scrollTo(this.prevScrollTop); - } - } - componentWillUpdate() { - var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); - this.prevScrollTop = postHolder.scrollTop(); - } - componentWillReceiveProps(nextProps) { - if (nextProps.isActive === true && this.props.isActive === false) { - this.activate(); - } else if (nextProps.isActive === false && this.props.isActive === true) { - this.deactivate(); - } - } - updateScroll() { - if (!this.scrolled) { - this.scrollToBottom(); - } - } - handleResize() { - this.setState({ - windowHeight: Utils.windowHeight() - }); - } - resizePostList() { - const postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); - if ($('#create_post').length > 0) { - const height = this.state.windowHeight - $('#create_post').height() - $('#error_bar').outerHeight() - 50; - postHolder.css('height', height + 'px'); - } - } - scrollTo(val) { - this.isUserScroll = false; - var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); - postHolder[0].scrollTop = val; - } - scrollToBottom(force) { - this.isUserScroll = false; - var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist)); - if ($('#new_message_' + this.props.channelId)[0] && !this.userHasSeenNew && !force) { - $('#new_message_' + this.props.channelId)[0].scrollIntoView(); - } else { - postHolder.addClass('hide-scroll'); - postHolder[0].scrollTop = postHolder[0].scrollHeight; - postHolder.removeClass('hide-scroll'); - } - } - loadFirstPosts(id) { - if (this.loadInProgress) { - return; - } - - if (this.props.channelId == null) { - return; - } - - this.loadInProgress = true; - Client.getPosts( - id, - PostStore.getLatestUpdate(id), - () => { - this.loadInProgress = false; - this.setState({isFirstLoadComplete: true}); - }, - () => { - this.loadInProgress = false; - this.setState({isFirstLoadComplete: true}); - } - ); - } - onChange() { - var newState = this.getStateFromStores(this.props.channelId); - - if (!Utils.areStatesEqual(newState.postList, this.state.postList)) { - this.setState(newState); - } - } - onSocketChange(msg) { - if (msg.action === SocketEvents.POST_DELETED) { - var activeRoot = $(document.activeElement).closest('.comment-create-body')[0]; - var activeRootPostId = ''; - if (activeRoot && activeRoot.id.length > 0) { - activeRootPostId = activeRoot.id; - } - - if (activeRootPostId === msg.props.post_id && UserStore.getCurrentId() !== msg.user_id) { - $('#post_deleted').modal('show'); - } - } - } - onTimeChange() { - if (!this.state.postList) { - return; - } - - for (var id in this.state.postList.posts) { - if (!this.refs[id]) { - continue; - } - this.refs[id].forceUpdateInfo(); - } - } - createDMIntroMessage(channel) { - var teammate = Utils.getDirectTeammate(channel.id); - - if (teammate) { - var teammateName = teammate.username; - if (teammate.nickname.length > 0) { - teammateName = teammate.nickname; - } - - return ( -
-
- -
-
- -
-

- {'This is the start of your direct message history with ' + teammateName + '.'}
- {'Direct messages and files shared here are not shown to people outside this area.'} -

- - {'Set a header'} - -
- ); - } - - return ( -
-

{'This is the start of your direct message history with this teammate. Direct messages and files shared here are not shown to people outside this area.'}

-
- ); - } - createChannelIntroMessage(channel) { - if (channel.type === 'D') { - return this.createDMIntroMessage(channel); - } else if (ChannelStore.isDefault(channel)) { - return this.createDefaultIntroMessage(channel); - } else if (channel.name === Constants.OFFTOPIC_CHANNEL) { - return this.createOffTopicIntroMessage(channel); - } else if (channel.type === 'O' || channel.type === 'P') { - return this.createStandardIntroMessage(channel); - } - } - createDefaultIntroMessage(channel) { - const team = TeamStore.getCurrent(); - let inviteModalLink; - if (team.type === Constants.INVITE_TEAM) { - inviteModalLink = ( - - {'Invite others to this team'} - - ); - } else { - inviteModalLink = ( - {'Invite others to this team'} - - ); - } - - return ( -
-

{'Beginning of ' + channel.display_name}

-

- {'Welcome to ' + channel.display_name + '!'} -

- {'This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.'} -

- {inviteModalLink} - - {'Set a header'} - -
-
- ); - } - createOffTopicIntroMessage(channel) { - return ( -
-

{'Beginning of ' + channel.display_name}

-

- {'This is the start of ' + channel.display_name + ', a channel for non-work-related conversations.'} -
-

- - {'Set a header'} - - - {'Invite others to this channel'} - -
- ); - } - getChannelCreator(channel) { - if (channel.creator_id.length > 0) { - var creator = UserStore.getProfile(channel.creator_id); - if (creator) { - return creator.username; - } - } - - var members = ChannelStore.getExtraInfo(channel.id).members; - for (var i = 0; i < members.length; i++) { - if (Utils.isAdmin(members[i].roles)) { - return members[i].username; - } - } - } - createStandardIntroMessage(channel) { - var uiName = channel.display_name; - var creatorName = ''; - - var uiType; - var memberMessage; - if (channel.type === 'P') { - uiType = 'private group'; - memberMessage = ' Only invited members can see this private group.'; - } else { - uiType = 'channel'; - memberMessage = ' Any member can join and read this channel.'; - } - - var createMessage; - if (creatorName === '') { - createMessage = 'This is the start of the ' + uiName + ' ' + uiType + ', created on ' + Utils.displayDate(channel.create_at) + '.'; - } else { - createMessage = (This is the start of the {uiName} {uiType}, created by {creatorName} on {Utils.displayDate(channel.create_at)}); - } - - return ( -
-

{'Beginning of ' + uiName}

-

- {createMessage} - {memberMessage} -
-

- - {'Set a header'} - - - {'Invite others to this ' + uiType} - -
- ); - } - createPosts(posts, order) { - var postCtls = []; - var previousPostDay = new Date(0); - var userId = UserStore.getCurrentId(); - - var renderedLastViewed = false; - var lastViewed = Number.MAX_VALUE; - - if (ChannelStore.getMember(this.props.channelId) != null) { - lastViewed = ChannelStore.getMember(this.props.channelId).last_viewed_at; - } - - var numToDisplay = this.state.numToDisplay; - if (order.length - 1 < numToDisplay) { - numToDisplay = order.length - 1; - } - - for (var i = numToDisplay; i >= 0; i--) { - var post = posts[order[i]]; - var parentPost = posts[post.parent_id]; - - // If the post is a comment whose parent has been deleted, don't add it to the list. - if (parentPost && parentPost.state === Constants.POST_DELETED) { - continue; - } - - var sameUser = false; - var sameRoot = false; - var hideProfilePic = false; - var prevPost = posts[order[i + 1]]; - - if (prevPost) { - sameUser = prevPost.user_id === post.user_id && post.create_at - prevPost.create_at <= 1000 * 60 * 5; - - sameRoot = Utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id); - - // hide the profile pic if: - // the previous post was made by the same user as the current post, - // the previous post is not a comment, - // the current post is not a comment, - // the current post is not from a webhook - // and the previous post is not from a webhook - if ((prevPost.user_id === post.user_id) && - !Utils.isComment(prevPost) && - !Utils.isComment(post) && - (!post.props || !post.props.from_webhook) && - (!prevPost.props || !prevPost.props.from_webhook)) { - hideProfilePic = true; - } - } - - // check if it's the last comment in a consecutive string of comments on the same post - // it is the last comment if it is last post in the channel or the next post has a different root post - var isLastComment = Utils.isComment(post) && (i === 0 || posts[order[i - 1]].root_id !== post.root_id); - - var postCtl = ( - - ); - - const currentPostDay = Utils.getDateForUnixTicks(post.create_at); - if (currentPostDay.toDateString() !== previousPostDay.toDateString()) { - postCtls.push( -
-
-
{currentPostDay.toDateString()}
-
- ); - } - - if (post.user_id !== userId && post.create_at > lastViewed && !renderedLastViewed) { - renderedLastViewed = true; - - // Temporary fix to solve ie11 rendering issue - let newSeparatorId = ''; - if (!Utils.isBrowserIE()) { - newSeparatorId = 'new_message_' + this.props.channelId; - } - postCtls.push( -
-
-
{'New Messages'}
-
- ); - } - postCtls.push(postCtl); - previousPostDay = currentPostDay; - } - - return postCtls; - } - loadMorePosts() { - if (this.state.postList == null) { - return; - } - - var posts = this.state.postList.posts; - var order = this.state.postList.order; - var channelId = this.props.channelId; - - $(ReactDOM.findDOMNode(this.refs.loadmore)).text('Retrieving more messages...'); - - Client.getPostsPage( - channelId, - order.length, - Constants.POST_CHUNK_SIZE, - function success(data) { - $(ReactDOM.findDOMNode(this.refs.loadmore)).text('Load more messages'); - this.gotMorePosts = true; - this.setState({numToDisplay: this.state.numToDisplay + Constants.POST_CHUNK_SIZE}); - - if (!data) { - return; - } - - if (data.order.length === 0) { - return; - } - - var postList = {}; - postList.posts = $.extend(posts, data.posts); - postList.order = order.concat(data.order); - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POSTS, - id: channelId, - post_list: postList - }); - - Client.getProfiles(); - }.bind(this), - function fail(err) { - $(ReactDOM.findDOMNode(this.refs.loadmore)).text('Load more messages'); - AsyncClient.dispatchError(err, 'getPosts'); - }.bind(this) - ); - } - render() { - var order = []; - var posts; - var channel = ChannelStore.get(this.props.channelId); - - if (this.state.postList != null) { - posts = this.state.postList.posts; - order = this.state.postList.order; - } - - var moreMessages =

{'Beginning of Channel'}

; - if (channel != null) { - if (order.length >= this.state.numToDisplay) { - moreMessages = ( - - {'Load more messages'} - - ); - } else { - moreMessages = this.createChannelIntroMessage(channel); - } - } - - var postCtls = []; - if (posts && this.state.isFirstLoadComplete) { - postCtls = this.createPosts(posts, order); - } else { - postCtls.push( - ); - } - - var activeClass = ''; - if (!this.props.isActive) { - activeClass = 'inactive'; - } - - return ( -
-
-
- {moreMessages} - {postCtls} -
-
-
- ); - } -} - -PostList.defaultProps = { - isActive: false, - channelId: null -}; -PostList.propTypes = { - isActive: React.PropTypes.bool, - channelId: React.PropTypes.string -}; diff --git a/web/react/components/post_list_container.jsx b/web/react/components/post_list_container.jsx index 09cee6218..5e12f0e2b 100644 --- a/web/react/components/post_list_container.jsx +++ b/web/react/components/post_list_container.jsx @@ -1,59 +1,239 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -const PostList = require('./post_list.jsx'); +const PostsView = require('./posts_view.jsx'); const ChannelStore = require('../stores/channel_store.jsx'); +const PostStore = require('../stores/post_store.jsx'); +const Constants = require('../utils/constants.jsx'); +const ActionTypes = Constants.ActionTypes; +const Utils = require('../utils/utils.jsx'); +const Client = require('../utils/client.jsx'); +const AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); +const LoadingScreen = require('./loading_screen.jsx'); + +import {createChannelIntroMessage} from '../utils/channel_intro_mssages.jsx'; export default class PostListContainer extends React.Component { constructor() { super(); - this.onChange = this.onChange.bind(this); - this.onLeave = this.onLeave.bind(this); + this.onChannelChange = this.onChannelChange.bind(this); + this.onChannelLeave = this.onChannelLeave.bind(this); + this.onPostsChange = this.onPostsChange.bind(this); + this.handlePostListScroll = this.handlePostListScroll.bind(this); + this.loadMorePostsTop = this.loadMorePostsTop.bind(this); + this.postsLoaded = this.postsLoaded.bind(this); + this.postsLoadedFailure = this.postsLoadedFailure.bind(this); - let currentChannelId = ChannelStore.getCurrentId(); + const currentChannelId = ChannelStore.getCurrentId(); + const state = { + scrollType: PostsView.SCROLL_TYPE_BOTTOM, + numPostsToDisplay: Constants.POST_CHUNK_SIZE + }; if (currentChannelId) { - this.state = {currentChannelId: currentChannelId, postLists: [currentChannelId]}; + Object.assign(state, { + currentChannelIndex: 0, + channels: [currentChannelId], + postLists: [this.getChannelPosts(currentChannelId)] + }); } else { - this.state = {currentChannelId: null, postLists: []}; + Object.assign(state, { + currentChannelIndex: null, + channels: [], + postLists: [] + }); } + + this.state = state; } componentDidMount() { - ChannelStore.addChangeListener(this.onChange); - ChannelStore.addLeaveListener(this.onLeave); + ChannelStore.addChangeListener(this.onChannelChange); + ChannelStore.addLeaveListener(this.onChannelLeave); + PostStore.addChangeListener(this.onPostsChange); + } + componentWillUnmount() { + ChannelStore.removeChangeListener(this.onChannelChange); + ChannelStore.removeLeaveListener(this.onChannelLeave); + PostStore.removeChangeListener(this.onPostsChange); } - onChange() { - let channelId = ChannelStore.getCurrentId(); - if (channelId === this.state.currentChannelId) { + onChannelChange() { + const postLists = Object.assign({}, this.state.postLists); + const channels = this.state.channels.slice(); + const channelId = ChannelStore.getCurrentId(); + + // Has the channel really changed? + if (channelId === channels[this.state.currentChannelIndex]) { return; } - let postLists = this.state.postLists; - if (postLists.indexOf(channelId) === -1) { - postLists.push(channelId); + PostStore.clearUnseenDeletedPosts(channelId); + + let lastViewed = Number.MAX_VALUE; + let member = ChannelStore.getMember(channelId); + if (member != null) { + lastViewed = member.last_viewed_at; + } + + let newIndex = channels.indexOf(channelId); + if (newIndex === -1) { + newIndex = channels.length; + channels.push(channelId); + postLists[newIndex] = this.getChannelPosts(channelId); } - this.setState({currentChannelId: channelId, postLists: postLists}); + this.setState({ + currentChannelIndex: newIndex, + currentLastViewed: lastViewed, + scrollType: PostsView.SCROLL_TYPE_BOTTOM, + channels, + postLists}); } - onLeave(id) { - let postLists = this.state.postLists; - var index = postLists.indexOf(id); + onChannelLeave(id) { + const postLists = Object.assign({}, this.state.postLists); + const channels = this.state.channels.slice(); + const index = channels.indexOf(id); if (index !== -1) { postLists.splice(index, 1); + channels.splice(index, 1); + } + this.setState({channels, postLists}); + } + onPostsChange() { + const channels = this.state.channels; + const postLists = Object.assign({}, this.state.postLists); + const newPostList = this.getChannelPosts(channels[this.state.currentChannelIndex]); + + postLists[this.state.currentChannelIndex] = newPostList; + this.setState({postLists}); + } + getChannelPosts(id) { + const postList = PostStore.getPosts(id); + + if (postList != null) { + const deletedPosts = PostStore.getUnseenDeletedPosts(id); + + if (deletedPosts && Object.keys(deletedPosts).length > 0) { + for (const pid in deletedPosts) { + if (deletedPosts.hasOwnProperty(pid)) { + postList.posts[pid] = deletedPosts[pid]; + postList.order.unshift(pid); + } + } + + postList.order.sort((a, b) => { + if (postList.posts[a].create_at > postList.posts[b].create_at) { + return -1; + } + if (postList.posts[a].create_at < postList.posts[b].create_at) { + return 1; + } + return 0; + }); + } + + const pendingPostList = PostStore.getPendingPosts(id); + + if (pendingPostList) { + postList.order = pendingPostList.order.concat(postList.order); + for (const ppid in pendingPostList.posts) { + if (pendingPostList.posts.hasOwnProperty(ppid)) { + postList.posts[ppid] = pendingPostList.posts[ppid]; + } + } + } + } + + return postList; + } + loadMorePostsTop() { + const postLists = this.state.postLists; + const channels = this.state.channels; + const currentChannelId = channels[this.state.currentChannelIndex]; + const currentPostList = postLists[this.state.currentChannelIndex]; + + this.setState({numPostsToDisplay: this.state.numPostsToDisplay + Constants.POST_CHUNK_SIZE}); + + Client.getPostsPage( + currentChannelId, + currentPostList.order.length, + Constants.POST_CHUNK_SIZE, + this.postsLoaded, + this.postsLoadedFailure + ); + } + postsLoaded(data) { + if (!data) { + return; } + + if (data.order.length === 0) { + return; + } + + const postLists = this.state.postLists; + const currentPostList = postLists[this.state.currentChannelIndex]; + const channels = this.state.channels; + const currentChannelId = channels[this.state.currentChannelIndex]; + + var newPostList = {}; + newPostList.posts = Object.assign(currentPostList.posts, data.posts); + newPostList.order = currentPostList.order.concat(data.order); + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_POSTS, + id: currentChannelId, + post_list: newPostList + }); + + Client.getProfiles(); + } + postsLoadedFailure(err) { + AsyncClient.dispatchError(err, 'getPosts'); + } + handlePostListScroll(atBottom) { + if (atBottom) { + this.setState({scrollType: PostsView.SCROLL_TYPE_BOTTOM}); + } else { + this.setState({scrollType: PostsView.SCROLL_TYPE_FREE}); + } + } + shouldComponentUpdate(nextProps, nextState) { + if (Utils.areStatesEqual(this.state, nextState)) { + return false; + } + + return true; } render() { - let postLists = this.state.postLists; - let channelId = this.state.currentChannelId; + const postLists = this.state.postLists; + const channels = this.state.channels; + const currentChannelId = channels[this.state.currentChannelIndex]; + const channel = ChannelStore.get(currentChannelId); - let postListCtls = []; - for (let i = 0; i <= this.state.postLists.length - 1; i++) { + const postListCtls = []; + for (let i = 0; i < channels.length; i++) { + const isActive = (channels[i] === currentChannelId); postListCtls.push( - ); + if ((!postLists[i] || !channel) && isActive) { + postListCtls.push( + + ); + } } return ( diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx new file mode 100644 index 000000000..d9a95af8b --- /dev/null +++ b/web/react/components/posts_view.jsx @@ -0,0 +1,249 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +const UserStore = require('../stores/user_store.jsx'); +const Utils = require('../utils/utils.jsx'); +const Post = require('./post.jsx'); + +export default class PostsView extends React.Component { + constructor(props) { + super(props); + + this.handleScroll = this.handleScroll.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.handleResize = this.handleResize.bind(this); + } + static get SCROLL_TYPE_FREE() { + return 1; + } + static get SCROLL_TYPE_BOTTOM() { + return 2; + } + handleScroll() { + const atBottom = ((this.refs.postlist.scrollHeight - this.refs.postlist.scrollTop) === this.refs.postlist.clientHeight); + this.props.postListScrolled(atBottom); + } + loadMorePostsTop() { + this.props.loadMorePostsTopClicked(); + } + postsRerendered() { + } + createPosts(posts, order) { + const postCtls = []; + let previousPostDay = new Date(0); + const userId = UserStore.getCurrentId(); + + let renderedLastViewed = false; + + let numToDisplay = this.props.numPostsToDisplay; + if (order.length - 1 < numToDisplay) { + numToDisplay = order.length - 1; + } + + for (let i = numToDisplay; i >= 0; i--) { + const post = posts[order[i]]; + const parentPost = posts[post.parent_id]; + const prevPost = posts[order[i + 1]]; + + let sameUser = false; + let sameRoot = false; + let hideProfilePic = false; + + if (prevPost) { + sameUser = prevPost.user_id === post.user_id && post.create_at - prevPost.create_at <= 1000 * 60 * 5; + + sameRoot = Utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id); + + // hide the profile pic if: + // the previous post was made by the same user as the current post, + // the previous post is not a comment, + // the current post is not a comment, + // the current post is not from a webhook + // and the previous post is not from a webhook + if ((prevPost.user_id === post.user_id) && + !Utils.isComment(prevPost) && + !Utils.isComment(post) && + (!post.props || !post.props.from_webhook) && + (!prevPost.props || !prevPost.props.from_webhook)) { + hideProfilePic = true; + } + } + + // check if it's the last comment in a consecutive string of comments on the same post + // it is the last comment if it is last post in the channel or the next post has a different root post + var isLastComment = Utils.isComment(post) && (i === 0 || posts[order[i - 1]].root_id !== post.root_id); + + var postCtl = ( + + ); + + const currentPostDay = Utils.getDateForUnixTicks(post.create_at); + if (currentPostDay.toDateString() !== previousPostDay.toDateString()) { + postCtls.push( +
+
+
{currentPostDay.toDateString()}
+
+ ); + } + + if (post.user_id !== userId && + this.props.messageSeparatorTime !== 0 && + post.create_at > this.props.messageSeparatorTime && + !renderedLastViewed) { + renderedLastViewed = true; + + // Temporary fix to solve ie11 rendering issue + let newSeparatorId = ''; + if (!Utils.isBrowserIE()) { + newSeparatorId = 'new_message_' + post.id; + } + postCtls.push( +
+
+
{'New Messages'}
+
+ ); + } + postCtls.push(postCtl); + previousPostDay = currentPostDay; + } + + return postCtls; + } + scrollToBottom() { + if (this.props.scrollType === PostsView.SCROLL_TYPE_BOTTOM) { + window.requestAnimationFrame(() => { + this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight; + }); + } + } + handleResize() { + this.scrollToBottom(); + } + componentDidMount() { + this.scrollToBottom(); + window.addEventListener('resize', this.handleResize); + } + componentWillUnmount() { + window.removeEventListener('resize', this.handleResize); + } + componentDidUpdate() { + this.scrollToBottom(); + } + shouldComponentUpdate(nextProps) { + if (this.props.isActive !== nextProps.isActive) { + return true; + } + if (this.props.postList !== nextProps.postList) { + return true; + } + if (this.props.scrollPost !== nextProps.scrollPost) { + return true; + } + if (this.props.scrollType !== nextProps.scrollType && nextProps.scrollType !== PostsView.SCROLL_TYPE_FREE) { + return true; + } + if (this.props.numPostsToDisplay !== nextProps.numPostsToDisplay) { + return true; + } + if (this.props.messageSeparatorTime !== nextProps.messageSeparatorTime) { + return true; + } + if (!Utils.areStatesEqual(this.props.postList, nextProps.postList)) { + return true; + } + + return false; + } + render() { + let posts = []; + let order = []; + let moreMessages; + let postElements; + let activeClass = 'inactive'; + if (this.props.postList != null) { + posts = this.props.postList.posts; + order = this.props.postList.order; + + // Create intro message or top loadmore link + if (order.length >= this.props.numPostsToDisplay) { + moreMessages = ( + + {'Load more messages'} + + ); + } else { + moreMessages = this.props.introText; + } + + // Create post elements + postElements = this.createPosts(posts, order); + + // Show ourselves if we are marked active + if (this.props.isActive) { + activeClass = ''; + } + } + + return ( +
+
+
+ {moreMessages} + {postElements} +
+
+
+ ); + } +} +PostsView.defaultProps = { +}; + +PostsView.propTypes = { + isActive: React.PropTypes.bool, + postList: React.PropTypes.object.isRequired, + scrollPost: React.PropTypes.string, + scrollType: React.PropTypes.number, + postListScrolled: React.PropTypes.func.isRequired, + loadMorePostsTopClicked: React.PropTypes.func.isRequired, + numPostsToDisplay: React.PropTypes.number, + introText: React.PropTypes.element, + messageSeparatorTime: React.PropTypes.number +}; diff --git a/web/react/utils/channel_intro_mssages.jsx b/web/react/utils/channel_intro_mssages.jsx new file mode 100644 index 000000000..b3f868456 --- /dev/null +++ b/web/react/utils/channel_intro_mssages.jsx @@ -0,0 +1,218 @@ + +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +const Utils = require('./utils.jsx'); +const UserProfile = require('../components/user_profile.jsx'); +const ChannelStore = require('../stores/channel_store.jsx'); +const Constants = require('../utils/constants.jsx'); +const TeamStore = require('../stores/team_store.jsx'); + +export function createChannelIntroMessage(channel) { + if (channel.type === 'D') { + return createDMIntroMessage(channel); + } else if (ChannelStore.isDefault(channel)) { + return createDefaultIntroMessage(channel); + } else if (channel.name === Constants.OFFTOPIC_CHANNEL) { + return createOffTopicIntroMessage(channel); + } else if (channel.type === 'O' || channel.type === 'P') { + return createStandardIntroMessage(channel); + } +} + +export function createDMIntroMessage(channel) { + var teammate = Utils.getDirectTeammate(channel.id); + + if (teammate) { + var teammateName = teammate.username; + if (teammate.nickname.length > 0) { + teammateName = teammate.nickname; + } + + return ( +
+
+ +
+
+ + + +
+

+ {'This is the start of your direct message history with ' + teammateName + '.'}
+ {'Direct messages and files shared here are not shown to people outside this area.'} +

+ + {'Set a header'} + +
+ ); + } + + return ( +
+

{'This is the start of your direct message history with this teammate. Direct messages and files shared here are not shown to people outside this area.'}

+
+ ); +} + +export function createOffTopicIntroMessage(channel) { + return ( +
+

{'Beginning of ' + channel.display_name}

+

+ {'This is the start of ' + channel.display_name + ', a channel for non-work-related conversations.'} +
+

+ + {'Set a header'} + + + {'Invite others to this channel'} + +
+ ); +} + +export function createDefaultIntroMessage(channel) { + const team = TeamStore.getCurrent(); + let inviteModalLink; + if (team.type === Constants.INVITE_TEAM) { + inviteModalLink = ( + + {'Invite others to this team'} + + ); + } else { + inviteModalLink = ( + {'Invite others to this team'} + + ); + } + + return ( +
+

{'Beginning of ' + channel.display_name}

+

+ {'Welcome to ' + channel.display_name + '!'} +

+ {'This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.'} +

+ {inviteModalLink} + + {'Set a header'} + +
+
+ ); +} + +export function createStandardIntroMessage(channel) { + var uiName = channel.display_name; + var creatorName = ''; + + var uiType; + var memberMessage; + if (channel.type === 'P') { + uiType = 'private group'; + memberMessage = ' Only invited members can see this private group.'; + } else { + uiType = 'channel'; + memberMessage = ' Any member can join and read this channel.'; + } + + var createMessage; + if (creatorName === '') { + createMessage = 'This is the start of the ' + uiName + ' ' + uiType + ', created on ' + Utils.displayDate(channel.create_at) + '.'; + } else { + createMessage = ( + + {'This is the start of the '} + {uiName} + {' '} + {uiType}{', created by '} + {creatorName} + {' on '} + {Utils.displayDate(channel.create_at)} + + ); + } + + return ( +
+

{'Beginning of ' + uiName}

+

+ {createMessage} + {memberMessage} +
+

+ + {'Set a header'} + + + {'Invite others to this ' + uiType} + +
+ ); +} diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index 7709e17f3..e11f9b640 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -441,7 +441,10 @@ body.ios { &.post-profile-img__container { float: left; .post-profile-img { + width: 36px; + height: 36px; margin-right: 10px; + vertical-align: inherit; @include border-radius(50px); } } -- cgit v1.2.3-1-g7c22 From b9a3ff74dd2b299ae4980922a6dcc55002662517 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Fri, 30 Oct 2015 14:29:15 -0400 Subject: Hooking up external components --- web/react/components/create_post.jsx | 1 + web/react/components/post_info.jsx | 18 ++---- web/react/components/post_list_container.jsx | 25 +++++++- web/react/components/posts_view.jsx | 70 +++++++++++++++++++--- web/react/components/sidebar_right.jsx | 65 ++++++++++---------- .../components/updating_time_since_counter.jsx | 50 ++++++++++++++++ web/react/pages/channel.jsx | 4 +- web/react/stores/post_store.jsx | 29 +++++++++ web/react/utils/constants.jsx | 5 ++ web/sass-files/sass/partials/_base.scss | 12 ++++ 10 files changed, 217 insertions(+), 62 deletions(-) create mode 100644 web/react/components/updating_time_since_counter.jsx (limited to 'web') 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 = {`${utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}`}; - return (
  • - - - +
  • 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'} ); @@ -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(''); + + if (!(this.state.search_visible || this.state.post_right_visible)) { + $('.inner__wrap').removeClass('move--left').removeClass('move--right'); + $('.sidebar--right').removeClass('move--left'); + return ( +
    + ); } + + /*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 ( -
    - ); - } - - $('.inner__wrap').removeClass('.move--right').addClass('move--left'); - $('.sidebar--left').removeClass('move--right'); - $('.sidebar--right').addClass('move--left'); - $('.sidebar--right').prepend(''); - - 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 = ( + + {displayDate + ' at ' + displayTime} + + ); + + return ( + + + + ); + } +} +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( - , + , 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 } }; diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss index a9f46a815..c286927a2 100644 --- a/web/sass-files/sass/partials/_base.scss +++ b/web/sass-files/sass/partials/_base.scss @@ -8,6 +8,18 @@ body { background: $body-bg; position: relative; height: 100%; + &.white { + background: #fff; + > .container-fluid { + overflow: auto; + } + .inner__wrap { + > .row.content { + min-height: 100%; + margin-bottom: -89px; + } + } + } } .inner__wrap { -- cgit v1.2.3-1-g7c22 From 1e7ac66e3bcc796c705ed6a833161cab6bd5b65d Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Mon, 2 Nov 2015 08:37:17 -0500 Subject: Some renaming --- web/react/components/center_panel.jsx | 4 +- web/react/components/create_post.jsx | 2 +- web/react/components/edit_post_modal.jsx | 2 +- web/react/components/post_info.jsx | 4 +- web/react/components/post_list_container.jsx | 264 --------------------- web/react/components/posts_view.jsx | 4 +- web/react/components/posts_view_container.jsx | 264 +++++++++++++++++++++ web/react/components/rhs_thread.jsx | 10 +- web/react/components/sidebar_right.jsx | 2 +- web/react/components/time_since.jsx | 50 ++++ .../components/updating_time_since_counter.jsx | 50 ---- web/react/stores/post_store.jsx | 46 ++-- web/react/utils/constants.jsx | 2 +- 13 files changed, 352 insertions(+), 352 deletions(-) delete mode 100644 web/react/components/post_list_container.jsx create mode 100644 web/react/components/posts_view_container.jsx create mode 100644 web/react/components/time_since.jsx delete mode 100644 web/react/components/updating_time_since_counter.jsx (limited to 'web') diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx index 7cb55898f..b871fe81a 100644 --- a/web/react/components/center_panel.jsx +++ b/web/react/components/center_panel.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. var CreatePost = require('../components/create_post.jsx'); -var PostListContainer = require('../components/post_list_container.jsx'); +var PostsViewContainer = require('../components/posts_view_container.jsx'); var ChannelHeader = require('../components/channel_header.jsx'); var Navbar = require('../components/navbar.jsx'); var FileUploadOverlay = require('../components/file_upload_overlay.jsx'); @@ -32,7 +32,7 @@ export default class CenterPanel extends React.Component {
    - +
  • -
  • diff --git a/web/react/components/post_list_container.jsx b/web/react/components/post_list_container.jsx deleted file mode 100644 index 90468ab66..000000000 --- a/web/react/components/post_list_container.jsx +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -const PostsView = require('./posts_view.jsx'); -const ChannelStore = require('../stores/channel_store.jsx'); -const PostStore = require('../stores/post_store.jsx'); -const Constants = require('../utils/constants.jsx'); -const ActionTypes = Constants.ActionTypes; -const Utils = require('../utils/utils.jsx'); -const Client = require('../utils/client.jsx'); -const AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); -const AsyncClient = require('../utils/async_client.jsx'); -const LoadingScreen = require('./loading_screen.jsx'); - -import {createChannelIntroMessage} from '../utils/channel_intro_mssages.jsx'; - -export default class PostListContainer extends React.Component { - constructor() { - super(); - - this.onChannelChange = this.onChannelChange.bind(this); - this.onChannelLeave = this.onChannelLeave.bind(this); - this.onPostsChange = this.onPostsChange.bind(this); - this.handlePostListScroll = this.handlePostListScroll.bind(this); - 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) { - Object.assign(state, { - currentChannelIndex: 0, - channels: [currentChannelId], - postLists: [this.getChannelPosts(currentChannelId)] - }); - } else { - Object.assign(state, { - currentChannelIndex: null, - channels: [], - postLists: [] - }); - } - - this.state = state; - } - componentDidMount() { - 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); - const channels = this.state.channels.slice(); - const channelId = ChannelStore.getCurrentId(); - - // Has the channel really changed? - if (channelId === channels[this.state.currentChannelIndex]) { - return; - } - - PostStore.clearUnseenDeletedPosts(channelId); - - let lastViewed = Number.MAX_VALUE; - const member = ChannelStore.getMember(channelId); - if (member != null) { - lastViewed = member.last_viewed_at; - } - - let newIndex = channels.indexOf(channelId); - if (newIndex === -1) { - newIndex = channels.length; - channels.push(channelId); - postLists[newIndex] = this.getChannelPosts(channelId); - } - this.setState({ - currentChannelIndex: newIndex, - currentLastViewed: lastViewed, - scrollType: PostsView.SCROLL_TYPE_BOTTOM, - channels, - postLists}); - } - onChannelLeave(id) { - const postLists = Object.assign({}, this.state.postLists); - const channels = this.state.channels.slice(); - const index = channels.indexOf(id); - if (index !== -1) { - postLists.splice(index, 1); - channels.splice(index, 1); - } - this.setState({channels, postLists}); - } - onPostsChange() { - const channels = this.state.channels; - const postLists = Object.assign({}, this.state.postLists); - const newPostList = this.getChannelPosts(channels[this.state.currentChannelIndex]); - - postLists[this.state.currentChannelIndex] = newPostList; - this.setState({postLists}); - } - getChannelPosts(id) { - const postList = PostStore.getPosts(id); - - if (postList != null) { - const deletedPosts = PostStore.getUnseenDeletedPosts(id); - - if (deletedPosts && Object.keys(deletedPosts).length > 0) { - for (const pid in deletedPosts) { - if (deletedPosts.hasOwnProperty(pid)) { - postList.posts[pid] = deletedPosts[pid]; - postList.order.unshift(pid); - } - } - - postList.order.sort((a, b) => { - if (postList.posts[a].create_at > postList.posts[b].create_at) { - return -1; - } - if (postList.posts[a].create_at < postList.posts[b].create_at) { - return 1; - } - return 0; - }); - } - - const pendingPostList = PostStore.getPendingPosts(id); - - if (pendingPostList) { - postList.order = pendingPostList.order.concat(postList.order); - for (const ppid in pendingPostList.posts) { - if (pendingPostList.posts.hasOwnProperty(ppid)) { - postList.posts[ppid] = pendingPostList.posts[ppid]; - } - } - } - } - - return postList; - } - loadMorePostsTop() { - const postLists = this.state.postLists; - const channels = this.state.channels; - const currentChannelId = channels[this.state.currentChannelIndex]; - const currentPostList = postLists[this.state.currentChannelIndex]; - - this.setState({numPostsToDisplay: this.state.numPostsToDisplay + Constants.POST_CHUNK_SIZE}); - - Client.getPostsPage( - currentChannelId, - currentPostList.order.length, - Constants.POST_CHUNK_SIZE, - this.postsLoaded, - this.postsLoadedFailure - ); - } - postsLoaded(data) { - if (!data) { - return; - } - - if (data.order.length === 0) { - return; - } - - const postLists = this.state.postLists; - const currentPostList = postLists[this.state.currentChannelIndex]; - const channels = this.state.channels; - const currentChannelId = channels[this.state.currentChannelIndex]; - - var newPostList = {}; - newPostList.posts = Object.assign(currentPostList.posts, data.posts); - newPostList.order = currentPostList.order.concat(data.order); - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POSTS, - id: currentChannelId, - post_list: newPostList - }); - - Client.getProfiles(); - } - postsLoadedFailure(err) { - AsyncClient.dispatchError(err, 'getPosts'); - } - handlePostListScroll(atBottom) { - if (atBottom) { - this.setState({scrollType: PostsView.SCROLL_TYPE_BOTTOM}); - } else { - this.setState({scrollType: PostsView.SCROLL_TYPE_FREE}); - } - } - shouldComponentUpdate(nextProps, nextState) { - if (Utils.areStatesEqual(this.state, nextState)) { - return false; - } - - return true; - } - render() { - const postLists = this.state.postLists; - const channels = this.state.channels; - const currentChannelId = channels[this.state.currentChannelIndex]; - const channel = ChannelStore.get(currentChannelId); - - const postListCtls = []; - for (let i = 0; i < channels.length; i++) { - const isActive = (channels[i] === currentChannelId); - postListCtls.push( - - ); - if ((!postLists[i] || !channel) && isActive) { - postListCtls.push( - - ); - } - } - - return ( -
    {postListCtls}
    - ); - } -} diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx index 57e7abd35..5cefb4885 100644 --- a/web/react/components/posts_view.jsx +++ b/web/react/components/posts_view.jsx @@ -47,7 +47,7 @@ export default class PostsView extends React.Component { // --- -------- - this.props.postListScrolled(this.isAtBottom()); + this.props.postViewScrolled(this.isAtBottom()); this.prevScrollHeight = this.refs.postlist.scrollHeight; } loadMorePostsTop() { @@ -293,7 +293,7 @@ PostsView.propTypes = { postList: React.PropTypes.object, scrollPost: React.PropTypes.string, scrollType: React.PropTypes.number, - postListScrolled: React.PropTypes.func.isRequired, + postViewScrolled: React.PropTypes.func.isRequired, loadMorePostsTopClicked: React.PropTypes.func.isRequired, numPostsToDisplay: React.PropTypes.number, introText: React.PropTypes.element, diff --git a/web/react/components/posts_view_container.jsx b/web/react/components/posts_view_container.jsx new file mode 100644 index 000000000..9eda2a158 --- /dev/null +++ b/web/react/components/posts_view_container.jsx @@ -0,0 +1,264 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +const PostsView = require('./posts_view.jsx'); +const ChannelStore = require('../stores/channel_store.jsx'); +const PostStore = require('../stores/post_store.jsx'); +const Constants = require('../utils/constants.jsx'); +const ActionTypes = Constants.ActionTypes; +const Utils = require('../utils/utils.jsx'); +const Client = require('../utils/client.jsx'); +const AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); +const LoadingScreen = require('./loading_screen.jsx'); + +import {createChannelIntroMessage} from '../utils/channel_intro_mssages.jsx'; + +export default class PostsViewContainer extends React.Component { + constructor() { + super(); + + this.onChannelChange = this.onChannelChange.bind(this); + this.onChannelLeave = this.onChannelLeave.bind(this); + this.onPostsChange = this.onPostsChange.bind(this); + this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this); + this.loadMorePostsTop = this.loadMorePostsTop.bind(this); + this.postsLoaded = this.postsLoaded.bind(this); + this.postsLoadedFailure = this.postsLoadedFailure.bind(this); + this.handlePostsViewJumpRequest = this.handlePostsViewJumpRequest.bind(this); + + const currentChannelId = ChannelStore.getCurrentId(); + const state = { + scrollType: PostsView.SCROLL_TYPE_BOTTOM, + scrollPost: null, + numPostsToDisplay: Constants.POST_CHUNK_SIZE + }; + if (currentChannelId) { + Object.assign(state, { + currentChannelIndex: 0, + channels: [currentChannelId], + postLists: [this.getChannelPosts(currentChannelId)] + }); + } else { + Object.assign(state, { + currentChannelIndex: null, + channels: [], + postLists: [] + }); + } + + this.state = state; + } + componentDidMount() { + ChannelStore.addChangeListener(this.onChannelChange); + ChannelStore.addLeaveListener(this.onChannelLeave); + PostStore.addChangeListener(this.onPostsChange); + PostStore.addPostsViewJumpListener(this.handlePostsViewJumpRequest); + } + componentWillUnmount() { + ChannelStore.removeChangeListener(this.onChannelChange); + ChannelStore.removeLeaveListener(this.onChannelLeave); + PostStore.removeChangeListener(this.onPostsChange); + PostStore.removePostsViewJumpListener(this.handlePostsViewJumpRequest); + } + handlePostsViewJumpRequest(type, post) { + switch (type) { + case Constants.PostsViewJumpTypes.BOTTOM: + this.setState({scrollType: PostsView.SCROLL_TYPE_BOTTOM}); + break; + case Constants.PostsViewJumpTypes.POST: + this.setState({ + scrollType: PostsView.SCROLL_TYPE_POST, + scrollPost: post + }); + break; + case Constants.PostsViewJumpTypes.SIDEBAR_OPEN: + this.setState({scrollType: PostsView.SIDEBAR_OPEN}); + break; + } + } + onChannelChange() { + const postLists = Object.assign({}, this.state.postLists); + const channels = this.state.channels.slice(); + const channelId = ChannelStore.getCurrentId(); + + // Has the channel really changed? + if (channelId === channels[this.state.currentChannelIndex]) { + return; + } + + PostStore.clearUnseenDeletedPosts(channelId); + + let lastViewed = Number.MAX_VALUE; + const member = ChannelStore.getMember(channelId); + if (member != null) { + lastViewed = member.last_viewed_at; + } + + let newIndex = channels.indexOf(channelId); + if (newIndex === -1) { + newIndex = channels.length; + channels.push(channelId); + postLists[newIndex] = this.getChannelPosts(channelId); + } + this.setState({ + currentChannelIndex: newIndex, + currentLastViewed: lastViewed, + scrollType: PostsView.SCROLL_TYPE_BOTTOM, + channels, + postLists}); + } + onChannelLeave(id) { + const postLists = Object.assign({}, this.state.postLists); + const channels = this.state.channels.slice(); + const index = channels.indexOf(id); + if (index !== -1) { + postLists.splice(index, 1); + channels.splice(index, 1); + } + this.setState({channels, postLists}); + } + onPostsChange() { + const channels = this.state.channels; + const postLists = Object.assign({}, this.state.postLists); + const newPostsView = this.getChannelPosts(channels[this.state.currentChannelIndex]); + + postLists[this.state.currentChannelIndex] = newPostsView; + this.setState({postLists}); + } + getChannelPosts(id) { + const postList = PostStore.getPosts(id); + + if (postList != null) { + const deletedPosts = PostStore.getUnseenDeletedPosts(id); + + if (deletedPosts && Object.keys(deletedPosts).length > 0) { + for (const pid in deletedPosts) { + if (deletedPosts.hasOwnProperty(pid)) { + postList.posts[pid] = deletedPosts[pid]; + postList.order.unshift(pid); + } + } + + postList.order.sort((a, b) => { + if (postList.posts[a].create_at > postList.posts[b].create_at) { + return -1; + } + if (postList.posts[a].create_at < postList.posts[b].create_at) { + return 1; + } + return 0; + }); + } + + const pendingPostList = PostStore.getPendingPosts(id); + + if (pendingPostList) { + postList.order = pendingPostList.order.concat(postList.order); + for (const ppid in pendingPostList.posts) { + if (pendingPostList.posts.hasOwnProperty(ppid)) { + postList.posts[ppid] = pendingPostList.posts[ppid]; + } + } + } + } + + return postList; + } + loadMorePostsTop() { + const postLists = this.state.postLists; + const channels = this.state.channels; + const currentChannelId = channels[this.state.currentChannelIndex]; + const currentPostList = postLists[this.state.currentChannelIndex]; + + this.setState({numPostsToDisplay: this.state.numPostsToDisplay + Constants.POST_CHUNK_SIZE}); + + Client.getPostsPage( + currentChannelId, + currentPostList.order.length, + Constants.POST_CHUNK_SIZE, + this.postsLoaded, + this.postsLoadedFailure + ); + } + postsLoaded(data) { + if (!data) { + return; + } + + if (data.order.length === 0) { + return; + } + + const postLists = this.state.postLists; + const currentPostList = postLists[this.state.currentChannelIndex]; + const channels = this.state.channels; + const currentChannelId = channels[this.state.currentChannelIndex]; + + var newPostList = {}; + newPostList.posts = Object.assign(currentPostList.posts, data.posts); + newPostList.order = currentPostList.order.concat(data.order); + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_POSTS, + id: currentChannelId, + post_list: newPostList + }); + + Client.getProfiles(); + } + postsLoadedFailure(err) { + AsyncClient.dispatchError(err, 'getPosts'); + } + handlePostsViewScroll(atBottom) { + if (atBottom) { + this.setState({scrollType: PostsView.SCROLL_TYPE_BOTTOM}); + } else { + this.setState({scrollType: PostsView.SCROLL_TYPE_FREE}); + } + } + shouldComponentUpdate(nextProps, nextState) { + if (Utils.areStatesEqual(this.state, nextState)) { + return false; + } + + return true; + } + render() { + const postLists = this.state.postLists; + const channels = this.state.channels; + const currentChannelId = channels[this.state.currentChannelIndex]; + const channel = ChannelStore.get(currentChannelId); + + const postListCtls = []; + for (let i = 0; i < channels.length; i++) { + const isActive = (channels[i] === currentChannelId); + postListCtls.push( + + ); + if ((!postLists[i] || !channel) && isActive) { + postListCtls.push( + + ); + } + } + + return ( +
    {postListCtls}
    + ); + } +} diff --git a/web/react/components/rhs_thread.jsx b/web/react/components/rhs_thread.jsx index bcdec2870..fe57bed28 100644 --- a/web/react/components/rhs_thread.jsx +++ b/web/react/components/rhs_thread.jsx @@ -34,12 +34,12 @@ export default class RhsThread extends React.Component { } var channelId = postList.posts[postList.order[0]].channel_id; - var pendingPostList = PostStore.getPendingPosts(channelId); + var pendingPostsList = PostStore.getPendingPosts(channelId); - if (pendingPostList) { - for (var pid in pendingPostList.posts) { - if (pendingPostList.posts.hasOwnProperty(pid)) { - postList.posts[pid] = pendingPostList.posts[pid]; + if (pendingPostsList) { + for (var pid in pendingPostsList.posts) { + if (pendingPostsList.posts.hasOwnProperty(pid)) { + postList.posts[pid] = pendingPostsList.posts[pid]; } } } diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx index 020db6d88..e2ef60959 100644 --- a/web/react/components/sidebar_right.jsx +++ b/web/react/components/sidebar_right.jsx @@ -34,7 +34,7 @@ export default class SidebarRight extends React.Component { PostStore.removeSelectedPostChangeListener(this.onSelectedChange); } componentWillUpdate() { - PostStore.jumpPostListSidebarOpen(); + PostStore.jumpPostsViewSidebarOpen(); } doStrangeThings() { // We should have a better way to do this stuff diff --git a/web/react/components/time_since.jsx b/web/react/components/time_since.jsx new file mode 100644 index 000000000..c37739b9c --- /dev/null +++ b/web/react/components/time_since.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 TimeSince 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 = ( + + {displayDate + ' at ' + displayTime} + + ); + + return ( + + + + ); + } +} +TimeSince.defaultProps = { + eventTime: 0 +}; + +TimeSince.propTypes = { + eventTime: React.PropTypes.number.isRequired +}; diff --git a/web/react/components/updating_time_since_counter.jsx b/web/react/components/updating_time_since_counter.jsx deleted file mode 100644 index d06ffb842..000000000 --- a/web/react/components/updating_time_since_counter.jsx +++ /dev/null @@ -1,50 +0,0 @@ -// 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 = ( - - {displayDate + ' at ' + displayTime} - - ); - - return ( - - - - ); - } -} -UpdatingTimeSinceCounter.defaultProps = { - eventTime: 0 -}; - -UpdatingTimeSinceCounter.propTypes = { - eventTime: React.PropTypes.number.isRequired -}; diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx index 19b200ac8..0fe253310 100644 --- a/web/react/stores/post_store.jsx +++ b/web/react/stores/post_store.jsx @@ -14,7 +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'; +var POSTS_VIEW_JUMP_EVENT = 'post_list_jump'; class PostStoreClass extends EventEmitter { constructor() { @@ -30,11 +30,11 @@ class PostStoreClass extends EventEmitter { this.emitEditPost = this.emitEditPost.bind(this); this.addEditPostListener = this.addEditPostListener.bind(this); - this.removeEditPostListener = this.removeEditPostListener.bind(this); + this.removeEditPostListener = this.removeEditPostListner.bind(this); - this.emitPostListJump = this.emitPostListJump.bind(this); - this.addPostListJumpListener = this.addPostListJumpListener.bind(this); - this.removePostListJumpListener = this.removePostListJumpListener.bind(this); + this.emitPostsViewJump = this.emitPostsViewJump.bind(this); + this.addPostsViewJumpListener = this.addPostsViewJumpListener.bind(this); + this.removePostsViewJumpListener = this.removePostsViewJumpListener.bind(this); this.getCurrentPosts = this.getCurrentPosts.bind(this); this.storePosts = this.storePosts.bind(this); @@ -101,32 +101,32 @@ class PostStoreClass extends EventEmitter { this.on(EDIT_POST_EVENT, callback); } - removeEditPostListener(callback) { + removeEditPostListner(callback) { this.removeListener(EDIT_POST_EVENT, callback); } - emitPostListJump(type, post) { - this.emit(POST_LIST_JUMP_EVENT, type, post); + emitPostsViewJump(type, post) { + this.emit(POSTS_VIEW_JUMP_EVENT, type, post); } - addPostListJumpListener(callback) { - this.on(POST_LIST_JUMP_EVENT, callback); + addPostsViewJumpListener(callback) { + this.on(POSTS_VIEW_JUMP_EVENT, callback); } - removePostListJumpListener(callback) { - this.removeListener(POST_LIST_JUMP_EVENT, callback); + removePostsViewJumpListener(callback) { + this.removeListener(POSTS_VIEW_JUMP_EVENT, callback); } - jumpPostListBottom() { - this.emitPostListJump(Constants.PostListJumpTypes.BOTTOM, null); + jumpPostsViewToBottom() { + this.emitPostsViewJump(Constants.PostsViewJumpTypes.BOTTOM, null); } - jumpPostListToPost(post) { - this.emitPostListJump(Constants.PostListJumpTypes.POST, post); + jumpPostsViewToPost(post) { + this.emitPostsViewJump(Constants.PostsViewJumpTypes.POST, post); } - jumpPostListSidebarOpen() { - this.emitPostListJump(Constants.PostListJumpTypes.SIDEBAR_OPEN, null); + jumpPostsViewSidebarOpen() { + this.emitPostsViewJump(Constants.PostsViewJumpTypes.SIDEBAR_OPEN, null); } getCurrentPosts() { @@ -137,16 +137,16 @@ class PostStoreClass extends EventEmitter { } return null; } - storePosts(channelId, newPostList) { - if (isPostListNull(newPostList)) { + storePosts(channelId, newPostsView) { + if (isPostListNull(newPostsView)) { return; } var postList = makePostListNonNull(this.getPosts(channelId)); - for (const pid in newPostList.posts) { - if (newPostList.posts.hasOwnProperty(pid)) { - const np = newPostList.posts[pid]; + for (const pid in newPostsView.posts) { + if (newPostsView.posts.hasOwnProperty(pid)) { + const np = newPostsView.posts[pid]; if (np.delete_at === 0) { postList.posts[pid] = np; if (postList.order.indexOf(pid) === -1) { diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index c97e4d982..8884d1d10 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -351,7 +351,7 @@ module.exports = { java: 'Java', ini: 'ini' }, - PostListJumpTypes: { + PostsViewJumpTypes: { BOTTOM: 1, POST: 2, SIDEBAR_OPEN: 3 -- cgit v1.2.3-1-g7c22 From d4ec6d3bf42d257851304fe9588f2db8bbbefa13 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Mon, 2 Nov 2015 10:32:00 -0500 Subject: Removing useless resize alert function in PostBody --- web/react/components/post.jsx | 4 +--- web/react/components/post_body.jsx | 4 +--- web/react/components/posts_view.jsx | 4 ---- 3 files changed, 2 insertions(+), 10 deletions(-) (limited to 'web') diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index dedac8951..c3c5b3e0b 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -204,7 +204,6 @@ export default class Post extends React.Component { posts={posts} handleCommentClick={this.handleCommentClick} retryPost={this.retryPost} - resize={this.props.resize} /> ); -- cgit v1.2.3-1-g7c22