diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 1d39f5f672..fe73cbcf72 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('
', '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 linkClass = ''; + if (channel.id === self.state.active_id) { + linkClass = 'active'; + } + + 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; + } + var titleClass = ''; - if (msgCount > 0 && channelMember.notify_level !== 'quiet') { + if (unread) { titleClass = 'unread-title'; + + if (!self.firstUnreadChannel) { + self.firstUnreadChannel = channel.name; + } + self.lastUnreadChannel = channel.name; } - var badge = ''; - if (channelMember.mention_count > 0) { - badge = {channelMember.mention_count}; - badgesActive = true; - titleClass = 'unread-title'; - } - - return ( -
  • {badge}{channel.display_name}
  • - ); - }); - - var privateChannelItems = this.state.channels.map(function(channel) { - if (channel.type !== 'P') { - return ''; - } - - 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 badge = ''; - if (channelMember.mention_count > 0) { - badge = {channelMember.mention_count}; - badgesActive = true; - titleClass = 'unread-title'; - } - - return ( -
  • {badge}{channel.display_name}
  • - ); - }); - - 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; - } - - if (!channel.fake) { - var active = channel.id === self.state.active_id ? 'active' : ''; - - if (channel.unread) { - badge = {channel.unread}; + 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 = {msgCount}; + badgesActive = true; + } + } else if (channelMember.mention_count > 0) { + // public and private channels only show badges for mentions + badge = {channelMember.mention_count}; badgesActive = true; - titleClass = 'unread-title'; } - - function handleClick(e) { - e.preventDefault(); - utils.switchChannel(channel, channel.teammate_username); - } - - return ( -
  • {badge}{channel.display_name}
  • - ); - } else { - return ( -
  • {badge}{channel.display_name}
  • - ); } - }); + // 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 = ; + } + + // 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 ( +
  • + + {status} + {badge} + {channel.display_name} + +
  • + ); + }; + + // create elements for all 3 types of channels + var channelItems = this.state.channels.filter( + function(channel) { + return channel.type === 'O'; + } + ).map(createChannelElement); + + 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) { -
  • Loading...
  • + var directMessageMore = null; + if (this.state.hideDirectChannels.length > 0) { + directMessageMore = ( +
  • + + {'More ('+this.state.hideDirectChannels.length+')'} + +
  • + ); } - if (privateChannelItems.length == 0) { -
  • Loading...
  • - } return (
    -
    +
    Unread post(s) above
    +
    Unread post(s) below
    + +
    diff --git a/web/sass-files/sass/partials/_sidebar--left.scss b/web/sass-files/sass/partials/_sidebar--left.scss index 89d1ff4160..5d866715e6 100644 --- a/web/sass-files/sass/partials/_sidebar--left.scss +++ b/web/sass-files/sass/partials/_sidebar--left.scss @@ -35,7 +35,29 @@ height: 100%; position: relative; overflow: auto; + } + + .nav-pills__unread-indicator { + position: absolute; + left: 0; + right: 0; + width: 70%; + background-color: darken($primary-color, 5%); + color: white; + margin: 0 auto; + padding: 2px; + text-align: center; + z-index: 1; + } + + .nav-pills__unread-indicator-top { + top: 56px; + } + .nav-pills__unread-indicator-bottom { + bottom: 0px; + } + .nav { &.nav-stacked { > li+li {