summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/install/requirements.md8
-rw-r--r--web/react/components/channel_header.jsx7
-rw-r--r--web/react/components/channel_loader.jsx60
-rw-r--r--web/react/components/create_post.jsx46
-rw-r--r--web/react/components/post_list.jsx202
-rw-r--r--web/react/components/post_list_container.jsx62
-rw-r--r--web/react/pages/channel.jsx4
-rw-r--r--web/react/stores/channel_store.jsx14
-rw-r--r--web/react/utils/constants.jsx1
-rw-r--r--web/sass-files/sass/partials/_post.scss25
-rw-r--r--web/sass-files/sass/partials/_responsive.scss15
11 files changed, 314 insertions, 130 deletions
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index fa54e81ef..1e0a12fb9 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -49,7 +49,7 @@ The below guidelines offer estimates based on real world usage of Mattermost acr
### Storage
-To estimate initial storage requirements begin with a Mattermost server approximately 600 MB to 800 MB in size including operating system and database, then add the multiplied product of:
+To estimate initial storage requirements, begin with a Mattermost server approximately 600 MB to 800 MB in size including operating system and database, then add the multiplied product of:
- Estimated storage per user per month (see below), multipled by 12 months in a year
- Estimated mean average number of users in a year
@@ -61,10 +61,10 @@ File usage per user varies significantly across industries. The below benchmarks
- **Low usage teams** (1-5 MB/user/month) - Primarily use text-messages and links to communicate. Examples would include software development teams that heavily use web-based document creation and management tools, and therefore rarely upload files to the server.
-- **Medium usage teams** (5-25 MB/user/month) - Use a mix of text-messages and shared documents and images to communicate. Examples might include business teams that may commonly drag and drop screenshots, PDFs and Microsoft Office documents into Mattermost for sharing and review.
+- **Medium usage teams** (5-25 MB/user/month) - Use a mix of text-messages as well as shared documents and images to communicate. Examples might include business teams that may commonly drag and drop screenshots, PDFs and Microsoft Office documents into Mattermost for sharing and review.
-- **High usage teams** - (25-100 MB/user/month) - Heaviest utlization comes from teams uploading a high number of large files into Mattermost on a regular basis. Examples include creative teams sharing and storing artwork and media with tags and commentary in a pipeline production process.
+- **High usage teams** - (25-100 MB/user/month) - Heaviest utlization comes from teams uploading a high number of large files into Mattermost on a regular basis. Examples include creative teams who share and store artwork and media with tags and commentary in a pipeline production process.
-*Example:* A 30 person team with medium usage (5-25 MB/user/month) with a safety factor of 2x would require between 300 MB (30 users * 5 MB * 2x safety factor) and 1500 MB (30 users * 25 MB * 2x safety factor) of free space in the next year.
+*Example:* A 30-person team with medium usage (5-25 MB/user/month) with a safety factor of 2x would require between 300 MB (30 users * 5 MB * 2x safety factor) and 1500 MB (30 users * 25 MB * 2x safety factor) of free space in the next year.
It's recommended to review storage utilization at least quarterly to ensure adequate free space is available.
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 87b9cab04..db23a5831 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -64,9 +64,14 @@ export default class ChannelHeader extends React.Component {
handleLeave() {
Client.leaveChannel(this.state.channel.id,
function handleLeaveSuccess() {
+ AppDispatcher.handleViewAction({
+ type: ActionTypes.LEAVE_CHANNEL,
+ id: this.state.channel.id
+ });
+
const townsquare = ChannelStore.getByName('town-square');
Utils.switchChannel(townsquare);
- },
+ }.bind(this),
function handleLeaveError(err) {
AsyncClient.dispatchError(err, 'handleLeave');
}
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx
index 8e8ed3f73..ce6f60f87 100644
--- a/web/react/components/channel_loader.jsx
+++ b/web/react/components/channel_loader.jsx
@@ -17,6 +17,8 @@ export default class ChannelLoader extends React.Component {
constructor(props) {
super(props);
+ this.intervalId = null;
+
this.onSocketChange = this.onSocketChange.bind(this);
this.state = {};
@@ -35,10 +37,12 @@ export default class ChannelLoader extends React.Component {
PostStore.clearPendingPosts();
/* Set up interval functions */
- setInterval(
+ this.intervalId = setInterval(
function pollStatuses() {
AsyncClient.getStatuses();
- }, 30000);
+ },
+ 30000
+ );
/* Device tracking setup */
var iOS = (/(iPad|iPhone|iPod)/g).test(navigator.userAgent);
@@ -49,12 +53,12 @@ export default class ChannelLoader extends React.Component {
/* Set up tracking for whether the window is active */
window.isActive = true;
- $(window).focus(function windowFocus() {
+ $(window).on('focus', function windowFocus() {
AsyncClient.updateLastViewedAt();
window.isActive = true;
});
- $(window).blur(function windowBlur() {
+ $(window).on('blur', function windowBlur() {
window.isActive = false;
});
@@ -84,6 +88,54 @@ export default class ChannelLoader extends React.Component {
Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +10) + ';');
$('.team__header').addClass('theme--gray');
}
+
+ /* Setup global mouse events */
+ $('body').on('click.userpopover', function popOver(e) {
+ if ($(e.target).attr('data-toggle') !== 'popover' &&
+ $(e.target).parents('.popover.in').length === 0) {
+ $('.user-popover').popover('hide');
+ }
+ });
+
+ $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) {
+ if (ev.type === 'mouseenter') {
+ $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after');
+ $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before');
+ } else {
+ $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--after');
+ $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--before');
+ }
+ });
+
+ $('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) {
+ if (ev.type === 'mouseenter') {
+ $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment');
+ $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment');
+ } else {
+ $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment');
+ $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment');
+ }
+ });
+
+ /* Setup modal events */
+ $('.modal').on('show.bs.modal', function onShow() {
+ $('.modal-body').css('overflow-y', 'auto');
+ $('.modal-body').css('max-height', $(window).height() * 0.7);
+ });
+ }
+ componentWillUnmount() {
+ clearInterval(this.intervalId);
+
+ $(window).off('focus');
+ $(window).off('blur');
+
+ SocketStore.removeChangeListener(this.onSocketChange);
+
+ $('body').off('click.userpopover');
+ $('body').off('mouseenter mouseleave', '.post');
+ $('body').off('mouseenter mouseleave', '.post.post--comment.same--root');
+
+ $('.modal').off('show.bs.modal');
}
onSocketChange(msg) {
if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) {
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 50aa0850d..d9e67836d 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -315,25 +315,33 @@ export default class CreatePost extends React.Component {
>
<div className='post-create'>
<div className='post-create-body'>
- <Textbox
- onUserInput={this.handleUserInput}
- onKeyPress={this.postMsgKeyPress}
- onHeightChange={this.resizePostHolder}
- messageText={this.state.messageText}
- createMessage='Write a message...'
- channelId={this.state.channelId}
- id='post_textbox'
- ref='textbox'
- />
- <FileUpload
- ref='fileUpload'
- getFileCount={this.getFileCount}
- onUploadStart={this.handleUploadStart}
- onFileUpload={this.handleFileUploadComplete}
- onUploadError={this.handleUploadError}
- postType='post'
- channelId=''
- />
+ <div className='post-body__cell'>
+ <Textbox
+ onUserInput={this.handleUserInput}
+ onKeyPress={this.postMsgKeyPress}
+ onHeightChange={this.resizePostHolder}
+ messageText={this.state.messageText}
+ createMessage='Write a message...'
+ channelId={this.state.channelId}
+ id='post_textbox'
+ ref='textbox'
+ />
+ <FileUpload
+ ref='fileUpload'
+ getFileCount={this.getFileCount}
+ onUploadStart={this.handleUploadStart}
+ onFileUpload={this.handleFileUploadComplete}
+ onUploadError={this.handleUploadError}
+ postType='post'
+ channelId=''
+ />
+ </div>
+ <a
+ className='send-button theme'
+ onClick={this.handleSubmit}
+ >
+ <i className='fa fa-paper-plane' />
+ </a>
</div>
<div className={postFooterClassName}>
{postError}
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 6fa87ca4a..d7c0d4862 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -18,8 +18,8 @@ var ActionTypes = Constants.ActionTypes;
import {strings} from '../utils/config.js';
export default class PostList extends React.Component {
- constructor() {
- super();
+ constructor(props) {
+ super(props);
this.gotMorePosts = false;
this.scrolled = false;
@@ -27,6 +27,7 @@ export default class PostList extends React.Component {
this.seenNewMessages = false;
this.isUserScroll = true;
this.userHasSeenNew = false;
+ this.loadInProgress = false;
this.onChange = this.onChange.bind(this);
this.onTimeChange = this.onTimeChange.bind(this);
@@ -34,22 +35,19 @@ export default class PostList extends React.Component {
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.resize = this.resize.bind(this);
- this.state = this.getStateFromStores();
+ this.state = this.getStateFromStores(props.channelId);
this.state.numToDisplay = Constants.POST_CHUNK_SIZE;
this.state.isFirstLoadComplete = false;
}
- getStateFromStores() {
- var channel = ChannelStore.getCurrent();
-
- if (channel == null) {
- channel = {};
- }
-
- var postList = PostStore.getCurrentPosts();
+ getStateFromStores(id) {
+ var postList = PostStore.getPosts(id);
if (postList != null) {
- var deletedPosts = PostStore.getUnseenDeletedPosts(channel.id);
+ var deletedPosts = PostStore.getUnseenDeletedPosts(id);
if (deletedPosts && Object.keys(deletedPosts).length > 0) {
for (var pid in deletedPosts) {
@@ -70,7 +68,7 @@ export default class PostList extends React.Component {
});
}
- var pendingPostList = PostStore.getPendingPosts(channel.id);
+ var pendingPostList = PostStore.getPendingPosts(id);
if (pendingPostList) {
postList.order = pendingPostList.order.concat(postList.order);
@@ -82,43 +80,42 @@ export default class PostList extends React.Component {
}
}
- var lastViewed = Number.MAX_VALUE;
-
- if (ChannelStore.getCurrentMember() != null) {
- lastViewed = ChannelStore.getCurrentMember().last_viewed_at;
- }
-
return {
- postList: postList,
- channel: channel,
- lastViewed: lastViewed
+ postList: postList
};
}
componentDidMount() {
+ 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);
- ChannelStore.addChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onTimeChange);
SocketStore.addChangeListener(this.onSocketChange);
- var postHolder = $('.post-list-holder-by-time');
-
- $('.modal').on('show.bs.modal', function onShow() {
- $('.modal-body').css('overflow-y', 'auto');
- $('.modal-body').css('max-height', $(window).height() * 0.7);
- });
-
- $(window).resize(function resize() {
- if ($('#create_post').length > 0) {
- var height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50;
- postHolder.css('height', height + 'px');
- }
+ var postHolder = $(React.findDOMNode(this.refs.postlist));
+ $(window).on('resize.' + this.props.channelId, function resize() {
+ this.resize();
if (!this.scrolled) {
this.scrollToBottom();
}
}.bind(this));
- postHolder.scroll(function scroll() {
+ postHolder.on('scroll', function scroll() {
var position = postHolder.scrollTop() + postHolder.height() + 14;
var bottom = postHolder[0].scrollHeight;
@@ -134,41 +131,25 @@ export default class PostList extends React.Component {
this.isUserScroll = true;
}.bind(this));
- $('body').on('click.userpopover', function popOver(e) {
- if ($(e.target).attr('data-toggle') !== 'popover' &&
- $(e.target).parents('.popover.in').length === 0) {
- $('.user-popover').popover('hide');
- }
- });
-
$('.post-list__content div .post').removeClass('post--last');
$('.post-list__content div:last-child .post').addClass('post--last');
- $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) {
- if (ev.type === 'mouseenter') {
- $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after');
- $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before');
- } else {
- $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--after');
- $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--before');
- }
- });
-
- $('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) {
- if (ev.type === 'mouseenter') {
- $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment');
- $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment');
- } else {
- $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment');
- $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment');
- }
- });
+ if (!this.state.isFirstLoadComplete) {
+ this.loadFirstPosts(this.props.channelId);
+ }
+ this.resize();
+ this.onChange();
this.scrollToBottom();
-
- if (this.state.channel.id != null) {
- this.loadFirstPosts(this.state.channel.id);
- }
+ }
+ deactivate() {
+ PostStore.removeChangeListener(this.onChange);
+ UserStore.removeStatusesChangeListener(this.onTimeChange);
+ SocketStore.removeChangeListener(this.onSocketChange);
+ $('body').off('click.userpopover');
+ $(window).off('resize.' + this.props.channelId);
+ var postHolder = $(React.findDOMNode(this.refs.postlist));
+ postHolder.off('scroll');
}
componentDidUpdate(prevProps, prevState) {
$('.post-list__content div .post').removeClass('post--last');
@@ -187,7 +168,7 @@ export default class PostList extends React.Component {
var firstPost = posts[order[0]] || {};
var isNewPost = oldOrder.indexOf(order[0]) === -1;
- if (this.state.channel.id !== prevState.channel.id) {
+ if (this.props.isActive && !prevProps.isActive) {
this.scrollToBottom();
} else if (oldOrder.length === 0) {
this.scrollToBottom();
@@ -201,37 +182,43 @@ export default class PostList extends React.Component {
} else if (isNewPost &&
userId === firstPost.user_id &&
!utils.isComment(firstPost)) {
- this.state.lastViewed = utils.getTimestamp();
this.scrollToBottom(true);
// the user clicked 'load more messages'
} else if (this.gotMorePosts) {
var lastPost = oldPosts[oldOrder[prevState.numToDisplay]];
$('#' + lastPost.id)[0].scrollIntoView();
+ this.gotMorePosts = false;
} else {
this.scrollTo(this.prevScrollTop);
}
}
componentWillUpdate() {
- var postHolder = $('.post-list-holder-by-time');
+ var postHolder = $(React.findDOMNode(this.refs.postlist));
this.prevScrollTop = postHolder.scrollTop();
}
- componentWillUnmount() {
- PostStore.removeChangeListener(this.onChange);
- ChannelStore.removeChangeListener(this.onChange);
- UserStore.removeStatusesChangeListener(this.onTimeChange);
- SocketStore.removeChangeListener(this.onSocketChange);
- $('body').off('click.userpopover');
- $('.modal').off('show.bs.modal');
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.isActive === true && this.props.isActive === false) {
+ this.activate();
+ } else if (nextProps.isActive === false && this.props.isActive === true) {
+ this.deactivate();
+ }
+ }
+ resize() {
+ const postHolder = $(React.findDOMNode(this.refs.postlist));
+ if ($('#create_post').length > 0) {
+ const height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50;
+ postHolder.css('height', height + 'px');
+ }
}
scrollTo(val) {
this.isUserScroll = false;
- var postHolder = $('.post-list-holder-by-time');
+ var postHolder = $(React.findDOMNode(this.refs.postlist));
postHolder[0].scrollTop = val;
}
scrollToBottom(force) {
this.isUserScroll = false;
- var postHolder = $('.post-list-holder-by-time');
+ var postHolder = $(React.findDOMNode(this.refs.postlist));
if ($('#new_message')[0] && !this.userHasSeenNew && !force) {
$('#new_message')[0].scrollIntoView();
} else {
@@ -241,34 +228,32 @@ export default class PostList extends React.Component {
}
}
loadFirstPosts(id) {
+ if (this.loadInProgress) {
+ return;
+ }
+
+ if (this.props.channelId == null) {
+ return;
+ }
+
+ this.loadInProgress = true;
Client.getPosts(
id,
PostStore.getLatestUpdate(id),
function success() {
+ this.loadInProgress = false;
this.setState({isFirstLoadComplete: true});
}.bind(this),
function fail() {
+ this.loadInProgress = false;
this.setState({isFirstLoadComplete: true});
}.bind(this)
);
}
onChange() {
- var newState = this.getStateFromStores();
-
- // Special case where the channel wasn't yet set in componentDidMount
- if (!this.state.isFirstLoadComplete && this.state.channel.id == null && newState.channel.id != null) {
- this.loadFirstPosts(newState.channel.id);
- }
-
- if (!utils.areStatesEqual(newState, this.state)) {
- if (this.state.channel.id !== newState.channel.id) {
- PostStore.clearUnseenDeletedPosts(this.state.channel.id);
- this.userHasSeenNew = false;
- newState.numToDisplay = Constants.POST_CHUNK_SIZE;
- } else {
- newState.lastViewed = this.state.lastViewed;
- }
+ var newState = this.getStateFromStores(this.props.channelId);
+ if (!utils.areStatesEqual(newState.postList, this.state.postList)) {
this.setState(newState);
}
}
@@ -424,7 +409,7 @@ export default class PostList extends React.Component {
}
}
- var members = ChannelStore.getCurrentExtraInfo().members;
+ var members = ChannelStore.getExtraInfo(channel.id).members;
for (var i = 0; i < members.length; i++) {
if (members[i].roles.indexOf('admin') > -1) {
return members[i].username;
@@ -488,6 +473,11 @@ export default class PostList extends React.Component {
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) {
@@ -543,7 +533,7 @@ export default class PostList extends React.Component {
);
}
- if (post.user_id !== userId && post.create_at > this.state.lastViewed && !renderedLastViewed) {
+ if (post.user_id !== userId && post.create_at > lastViewed && !renderedLastViewed) {
renderedLastViewed = true;
// Temporary fix to solve ie10/11 rendering issue
@@ -577,7 +567,7 @@ export default class PostList extends React.Component {
var posts = this.state.postList.posts;
var order = this.state.postList.order;
- var channelId = this.state.channel.id;
+ var channelId = this.props.channelId;
$(React.findDOMNode(this.refs.loadmore)).text('Retrieving more messages...');
@@ -619,7 +609,7 @@ export default class PostList extends React.Component {
render() {
var order = [];
var posts;
- var channel = this.state.channel;
+ var channel = ChannelStore.get(this.props.channelId);
if (this.state.postList != null) {
posts = this.state.postList.posts;
@@ -628,7 +618,7 @@ export default class PostList extends React.Component {
var moreMessages = <p className='beginning-messages-text'>Beginning of Channel</p>;
if (channel != null) {
- if (order.length > this.state.numToDisplay) {
+ if (order.length >= this.state.numToDisplay) {
moreMessages = (
<a
ref='loadmore'
@@ -655,10 +645,15 @@ export default class PostList extends React.Component {
/>);
}
+ var activeClass = '';
+ if (!this.props.isActive) {
+ activeClass = 'inactive';
+ }
+
return (
<div
ref='postlist'
- className='post-list-holder-by-time'
+ className={'post-list-holder-by-time ' + activeClass}
>
<div className='post-list__table'>
<div className='post-list__content'>
@@ -670,3 +665,12 @@ export default class PostList extends React.Component {
);
}
}
+
+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
new file mode 100644
index 000000000..0815ac883
--- /dev/null
+++ b/web/react/components/post_list_container.jsx
@@ -0,0 +1,62 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const PostList = require('./post_list.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+
+export default class PostListContainer extends React.Component {
+ constructor() {
+ super();
+
+ this.onChange = this.onChange.bind(this);
+ this.onLeave = this.onLeave.bind(this);
+
+ let currentChannelId = ChannelStore.getCurrentId();
+ if (currentChannelId) {
+ this.state = {currentChannelId: currentChannelId, postLists: [currentChannelId]};
+ } else {
+ this.state = {currentChannelId: null, postLists: []};
+ }
+ }
+ componentDidMount() {
+ ChannelStore.addChangeListener(this.onChange);
+ ChannelStore.addLeaveListener(this.onLeave);
+ }
+ onChange() {
+ let channelId = ChannelStore.getCurrentId();
+ if (channelId === this.state.currentChannelId) {
+ return;
+ }
+
+ let postLists = this.state.postLists;
+ if (postLists.indexOf(channelId) === -1) {
+ postLists.push(channelId);
+ }
+ this.setState({currentChannelId: channelId, postLists: postLists});
+ }
+ onLeave(id) {
+ let postLists = this.state.postLists;
+ var index = postLists.indexOf(id);
+ if (index !== -1) {
+ postLists.splice(index, 1);
+ }
+ }
+ render() {
+ let postLists = this.state.postLists;
+ let channelId = this.state.currentChannelId;
+
+ let postListCtls = [];
+ for (let i = 0; i <= this.state.postLists.length - 1; i++) {
+ postListCtls.push(
+ <PostList
+ channelId={postLists[i]}
+ isActive={postLists[i] === channelId}
+ />
+ );
+ }
+
+ return (
+ <div>{postListCtls}</div>
+ );
+ }
+}
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index 71a03cde0..182721bef 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -5,7 +5,7 @@ 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 PostList = require('../components/post_list.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');
@@ -159,7 +159,7 @@ function setupChannelPage(teamName, teamType, teamId, channelName, channelId) {
);
React.render(
- <PostList />,
+ <PostListContainer />,
document.getElementById('post-list')
);
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index bd655b767..b9ba37c27 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -10,6 +10,7 @@ var ActionTypes = Constants.ActionTypes;
var BrowserStore = require('../stores/browser_store.jsx');
var CHANGE_EVENT = 'change';
+var LEAVE_EVENT = 'leave';
var MORE_CHANGE_EVENT = 'change';
var EXTRA_INFO_EVENT = 'extra_info';
@@ -48,6 +49,15 @@ class ChannelStoreClass extends EventEmitter {
removeExtraInfoChangeListener(callback) {
this.removeListener(EXTRA_INFO_EVENT, callback);
}
+ emitLeave(id) {
+ this.emit(LEAVE_EVENT, id);
+ }
+ addLeaveListener(callback) {
+ this.on(LEAVE_EVENT, callback);
+ }
+ removeLeaveListener(callback) {
+ this.removeListener(LEAVE_EVENT, callback);
+ }
findFirstBy(field, value) {
var channels = this.pGetChannels();
for (var i = 0; i < channels.length; i++) {
@@ -272,6 +282,10 @@ ChannelStore.dispatchToken = AppDispatcher.register(function handleAction(payloa
ChannelStore.emitExtraInfoChange();
break;
+ case ActionTypes.LEAVE_CHANNEL:
+ ChannelStore.emitLeave(action.id);
+ break;
+
default:
break;
}
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 18b7ff59c..7ead079d7 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -9,6 +9,7 @@ module.exports = {
CLICK_CHANNEL: null,
CREATE_CHANNEL: null,
+ LEAVE_CHANNEL: null,
RECIEVED_CHANNELS: null,
RECIEVED_CHANNEL: null,
RECIEVED_MORE_CHANNELS: null,
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index 9a6e6e4c7..5ae9e656d 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -143,6 +143,12 @@ body.ios {
&::-webkit-scrollbar {
width: 0px !important;
}
+ &.inactive {
+ display: none;
+ }
+ &.active {
+ display: inline;
+ }
}
.post-list__table {
display: table;
@@ -182,6 +188,25 @@ body.ios {
max-width: 850px;
padding: 0 0 2px;
position: relative;
+ .post-body__cell {
+ vertical-align: top;
+ position: relative;
+ }
+ .send-button {
+ display: none;
+ cursor: pointer;
+ padding-right: 4px;
+ width: 45px;
+ height: 37px;
+ font-size: 18px;
+ line-height: 37px;
+ vertical-align: top;
+ text-align: center;
+ @include single-transition(all, 0.15s);
+ &:active {
+ @include opacity(0.75);
+ }
+ }
.custom-textarea {
padding-top: 8px;
padding-right: 28px;
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 5f516704d..24c79e6c2 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -412,9 +412,22 @@
}
}
}
- #post-create {
+ .post-create__container {
+ form {
+ padding: 0;
+ }
.post-create-body {
padding-bottom: 10px;
+ display: table;
+ width: 100%;
+ table-layout: fixed;
+ .post-body__cell {
+ display: table-cell;
+ padding-left: 1em;
+ }
+ .send-button {
+ display: table-cell;
+ }
}
.post-create-footer .msg-typing {
display: none;