Merge pull request #353 from hmhealey/mm401
MM-401 Add indicators to the sidebar for offscreen unread channels
Этот коммит содержится в:
@@ -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 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 = <span className='badge pull-right small'>{channelMember.mention_count}</span>;
|
||||
badgesActive = true;
|
||||
titleClass = 'unread-title';
|
||||
}
|
||||
|
||||
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 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 = <span className='badge pull-right small'>{channelMember.mention_count}</span>;
|
||||
badgesActive = true;
|
||||
titleClass = 'unread-title';
|
||||
}
|
||||
|
||||
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 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 = <span className='badge pull-right small'>{channel.unread}</span>;
|
||||
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;
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// 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}} />;
|
||||
}
|
||||
|
||||
// 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.name} ref={channel.name} className={linkClass}>
|
||||
<a className={'sidebar-channel ' + titleClass} href={href} onClick={clickHandler}>
|
||||
{status}
|
||||
{badge}
|
||||
{channel.display_name}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
// 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) {
|
||||
<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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Ссылка в новой задаче
Block a user