summaryrefslogtreecommitdiffstats
path: root/web/react/components
diff options
context:
space:
mode:
authorChristopher Speller <crspeller@gmail.com>2015-08-11 12:05:46 -0400
committerChristopher Speller <crspeller@gmail.com>2015-08-11 12:05:46 -0400
commita386bb9293d68d28aa31107334bbd80619e5de80 (patch)
tree11189e1666ae3cc01431e6c9d48ec7a669f8cdc8 /web/react/components
parentbffc37be134cca4b6b8b2172726de39595b6091a (diff)
parent72e5d441ff471d75effe52b4ef0eafe0667aa54e (diff)
downloadchat-a386bb9293d68d28aa31107334bbd80619e5de80.tar.gz
chat-a386bb9293d68d28aa31107334bbd80619e5de80.tar.bz2
chat-a386bb9293d68d28aa31107334bbd80619e5de80.zip
Merge pull request #353 from hmhealey/mm401
MM-401 Add indicators to the sidebar for offscreen unread channels
Diffstat (limited to 'web/react/components')
-rw-r--r--web/react/components/sidebar.jsx217
1 files changed, 137 insertions, 80 deletions
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 1d39f5f67..fe73cbcf7 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -56,7 +56,6 @@ function getStateFromStores() {
var channelMember = members[channel.id];
var msgCount = channel.total_msg_count - channelMember.msg_count;
if (msgCount > 0) {
- channel.unread = msgCount;
showDirectChannels.push(channel);
} else if (currentId === channel.id) {
showDirectChannels.push(channel);
@@ -70,6 +69,7 @@ function getStateFromStores() {
tempChannel.display_name = utils.getDisplayName(teammate);
tempChannel.status = UserStore.getStatus(teammate.id);
tempChannel.last_post_at = 0;
+ tempChannel.total_msg_count = 0;
readDirectChannels.push(tempChannel);
}
}
@@ -132,11 +132,17 @@ module.exports = React.createClass({
$('.nav-pills__container').perfectScrollbar();
this.updateTitle();
+ this.updateUnreadIndicators();
+
+ $(window).on('resize', this.onResize);
},
componentDidUpdate: function() {
this.updateTitle();
+ this.updateUnreadIndicators();
},
componentWillUnmount: function() {
+ $(window).off('resize', this.onResize);
+
ChannelStore.removeChangeListener(this.onChange);
UserStore.removeChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onChange);
@@ -157,7 +163,10 @@ module.exports = React.createClass({
}
if (UserStore.getCurrentId() !== msg.user_id) {
- var mentions = msg.props.mentions ? JSON.parse(msg.props.mentions) : [];
+ var mentions = [];
+ if (msg.props.mentions) {
+ mentions = JSON.parse(msg.props.mentions);
+ }
var channel = ChannelStore.get(msg.channel_id);
var user = UserStore.getCurrentUser();
@@ -175,7 +184,10 @@ module.exports = React.createClass({
username = UserStore.getProfile(msg.user_id).username;
}
- var title = channel ? channel.display_name : 'Posted';
+ var title = 'Posted';
+ if (channel) {
+ title = channel.display_name;
+ }
var repRegex = new RegExp('<br>', 'g');
var post = JSON.parse(msg.props.post);
@@ -235,103 +247,143 @@ module.exports = React.createClass({
}
}
},
+ onScroll: function(e) {
+ this.updateUnreadIndicators();
+ },
+ onResize: function(e) {
+ this.updateUnreadIndicators();
+ },
+ updateUnreadIndicators: function() {
+ var container = $(this.refs.container.getDOMNode());
+
+ if (this.firstUnreadChannel) {
+ var firstUnreadElement = $(this.refs[this.firstUnreadChannel].getDOMNode());
+
+ if (firstUnreadElement.position().top + firstUnreadElement.height() < 0) {
+ $(this.refs.topUnreadIndicator.getDOMNode()).css('display', 'initial');
+ } else {
+ $(this.refs.topUnreadIndicator.getDOMNode()).css('display', 'none');
+ }
+ }
+
+ if (this.lastUnreadChannel) {
+ var lastUnreadElement = $(this.refs[this.lastUnreadChannel].getDOMNode());
+
+ if (lastUnreadElement.position().top > container.height()) {
+ $(this.refs.bottomUnreadIndicator.getDOMNode()).css('bottom', '0');
+ $(this.refs.bottomUnreadIndicator.getDOMNode()).css('display', 'initial');
+ } else {
+ $(this.refs.bottomUnreadIndicator.getDOMNode()).css('display', 'none');
+ }
+ }
+ },
getInitialState: function() {
return getStateFromStores();
},
render: function() {
var members = this.state.members;
- var newsActive = window.location.pathname === '/' ? 'active' : '';
+ var activeId = this.state.active_id;
var badgesActive = false;
+
+ // keep track of the first and last unread channels so we can use them to set the unread indicators
var self = this;
- var channelItems = this.state.channels.map(function(channel) {
- if (channel.type != 'O') {
- return '';
- }
+ this.firstUnreadChannel = null;
+ this.lastUnreadChannel = null;
+ function createChannelElement(channel) {
var channelMember = members[channel.id];
- var active = channel.id === self.state.active_id ? 'active' : '';
- var msgCount = channel.total_msg_count - channelMember.msg_count;
- var titleClass = '';
- if (msgCount > 0 && channelMember.notify_level !== 'quiet') {
- titleClass = 'unread-title';
+ var linkClass = '';
+ if (channel.id === self.state.active_id) {
+ linkClass = 'active';
}
- var badge = '';
- if (channelMember.mention_count > 0) {
- badge = <span className='badge pull-right small'>{channelMember.mention_count}</span>;
- badgesActive = true;
- titleClass = 'unread-title';
+ var unread = false;
+ if (channelMember) {
+ var msgCount = channel.total_msg_count - channelMember.msg_count;
+ unread = (msgCount > 0 && channelMember.notify_level !== 'quiet') || channelMember.mention_count > 0;
}
- return (
- <li key={channel.id} className={active}><a className={'sidebar-channel ' + titleClass} href='#' onClick={function(e){e.preventDefault(); utils.switchChannel(channel);}}>{badge}{channel.display_name}</a></li>
- );
- });
+ var titleClass = '';
+ if (unread) {
+ titleClass = 'unread-title';
- var privateChannelItems = this.state.channels.map(function(channel) {
- if (channel.type !== 'P') {
- return '';
+ if (!self.firstUnreadChannel) {
+ self.firstUnreadChannel = channel.name;
+ }
+ self.lastUnreadChannel = channel.name;
}
- var channelMember = members[channel.id];
- var active = channel.id === self.state.active_id ? 'active' : '';
+ var badge = null;
+ if (channelMember) {
+ if (channel.type === 'D') {
+ // direct message channels show badges for any number of unread posts
+ var msgCount = channel.total_msg_count - channelMember.msg_count;
+ if (msgCount > 0) {
+ badge = <span className='badge pull-right small'>{msgCount}</span>;
+ badgesActive = true;
+ }
+ } else if (channelMember.mention_count > 0) {
+ // public and private channels only show badges for mentions
+ badge = <span className='badge pull-right small'>{channelMember.mention_count}</span>;
+ badgesActive = true;
+ }
+ }
- var msgCount = channel.total_msg_count - channelMember.msg_count;
- var titleClass = ''
- if (msgCount > 0 && channelMember.notify_level !== 'quiet') {
- titleClass = 'unread-title'
+ // set up status icon for direct message channels
+ var status = null;
+ if (channel.type === 'D') {
+ var statusIcon = '';
+ if (channel.status === 'online') {
+ statusIcon = Constants.ONLINE_ICON_SVG;
+ } else if (channel.status === 'away') {
+ statusIcon = Constants.ONLINE_ICON_SVG;
+ } else {
+ statusIcon = Constants.OFFLINE_ICON_SVG;
+ }
+ status = <span className='status' dangerouslySetInnerHTML={{__html: statusIcon}} />;
}
- var badge = '';
- if (channelMember.mention_count > 0) {
- badge = <span className='badge pull-right small'>{channelMember.mention_count}</span>;
- badgesActive = true;
- titleClass = 'unread-title';
+ // set up click handler to switch channels (or create a new channel for non-existant ones)
+ var clickHandler = null;
+ var href;
+ if (!channel.fake) {
+ clickHandler = function(e) {
+ e.preventDefault();
+ utils.switchChannel(channel);
+ };
+ href = '#';
+ } else {
+ href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
}
return (
- <li key={channel.id} className={active}><a className={'sidebar-channel ' + titleClass} href='#' onClick={function(e){e.preventDefault(); utils.switchChannel(channel);}}>{badge}{channel.display_name}</a></li>
+ <li key={channel.name} ref={channel.name} className={linkClass}>
+ <a className={'sidebar-channel ' + titleClass} href={href} onClick={clickHandler}>
+ {status}
+ {badge}
+ {channel.display_name}
+ </a>
+ </li>
);
- });
-
- var directMessageItems = this.state.showDirectChannels.map(function(channel) {
- var badge = '';
- var titleClass = '';
+ };
- var statusIcon = '';
- if (channel.status === 'online') {
- statusIcon = Constants.ONLINE_ICON_SVG;
- } else if (channel.status === 'away') {
- statusIcon = Constants.ONLINE_ICON_SVG;
- } else {
- statusIcon = Constants.OFFLINE_ICON_SVG;
+ // create elements for all 3 types of channels
+ var channelItems = this.state.channels.filter(
+ function(channel) {
+ return channel.type === 'O';
}
+ ).map(createChannelElement);
- if (!channel.fake) {
- var active = channel.id === self.state.active_id ? 'active' : '';
-
- if (channel.unread) {
- badge = <span className='badge pull-right small'>{channel.unread}</span>;
- badgesActive = true;
- titleClass = 'unread-title';
- }
-
- function handleClick(e) {
- e.preventDefault();
- utils.switchChannel(channel, channel.teammate_username);
- }
-
- return (
- <li key={channel.name} className={active}><a className={'sidebar-channel ' + titleClass} href='#' onClick={handleClick}><span className='status' dangerouslySetInnerHTML={{__html: statusIcon}} /> {badge}{channel.display_name}</a></li>
- );
- } else {
- return (
- <li key={channel.name} className={active}><a className={'sidebar-channel ' + titleClass} href={TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name}><span className='status' dangerouslySetInnerHTML={{__html: statusIcon}} /> {badge}{channel.display_name}</a></li>
- );
+ var privateChannelItems = this.state.channels.filter(
+ function(channel) {
+ return channel.type === 'P';
}
- });
+ ).map(createChannelElement);
+
+ var directMessageItems = this.state.showDirectChannels.map(createChannelElement);
+ // update the favicon to show if there are any notifications
var link = document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
@@ -348,19 +400,26 @@ module.exports = React.createClass({
}
head.appendChild(link);
- if (channelItems.length == 0) {
- <li><small>Loading...</small></li>
+ var directMessageMore = null;
+ if (this.state.hideDirectChannels.length > 0) {
+ directMessageMore = (
+ <li>
+ <a href='#' data-toggle='modal' className='nav-more' data-target='#more_direct_channels' data-channels={JSON.stringify(this.state.hideDirectChannels)}>
+ {'More ('+this.state.hideDirectChannels.length+')'}
+ </a>
+ </li>
+ );
}
- if (privateChannelItems.length == 0) {
- <li><small>Loading...</small></li>
- }
return (
<div>
<SidebarHeader teamDisplayName={this.props.teamDisplayName} teamType={this.props.teamType} />
<SearchBox />
- <div className='nav-pills__container'>
+ <div ref='topUnreadIndicator' className='nav-pills__unread-indicator nav-pills__unread-indicator-top' style={{display: 'none'}}>Unread post(s) above</div>
+ <div ref='bottomUnreadIndicator' className='nav-pills__unread-indicator nav-pills__unread-indicator-bottom' style={{display: 'none'}}>Unread post(s) below</div>
+
+ <div ref='container' className='nav-pills__container' onScroll={this.onScroll}>
<ul className='nav nav-pills nav-stacked'>
<li><h4>Channels<a className='add-channel-btn' href='#' data-toggle='modal' data-target='#new_channel' data-channeltype='O'>+</a></h4></li>
{channelItems}
@@ -374,9 +433,7 @@ module.exports = React.createClass({
<ul className='nav nav-pills nav-stacked'>
<li><h4>Private Messages</h4></li>
{directMessageItems}
- { this.state.hideDirectChannels.length > 0 ?
- <li><a href='#' data-toggle='modal' className='nav-more' data-target='#more_direct_channels' data-channels={JSON.stringify(this.state.hideDirectChannels)}>{'More ('+this.state.hideDirectChannels.length+')'}</a></li>
- : '' }
+ {directMessageMore}
</ul>
</div>
</div>