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 channelMember = members[channel.id];
|
||||||
var msgCount = channel.total_msg_count - channelMember.msg_count;
|
var msgCount = channel.total_msg_count - channelMember.msg_count;
|
||||||
if (msgCount > 0) {
|
if (msgCount > 0) {
|
||||||
channel.unread = msgCount;
|
|
||||||
showDirectChannels.push(channel);
|
showDirectChannels.push(channel);
|
||||||
} else if (currentId === channel.id) {
|
} else if (currentId === channel.id) {
|
||||||
showDirectChannels.push(channel);
|
showDirectChannels.push(channel);
|
||||||
@@ -70,6 +69,7 @@ function getStateFromStores() {
|
|||||||
tempChannel.display_name = utils.getDisplayName(teammate);
|
tempChannel.display_name = utils.getDisplayName(teammate);
|
||||||
tempChannel.status = UserStore.getStatus(teammate.id);
|
tempChannel.status = UserStore.getStatus(teammate.id);
|
||||||
tempChannel.last_post_at = 0;
|
tempChannel.last_post_at = 0;
|
||||||
|
tempChannel.total_msg_count = 0;
|
||||||
readDirectChannels.push(tempChannel);
|
readDirectChannels.push(tempChannel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,11 +132,17 @@ module.exports = React.createClass({
|
|||||||
$('.nav-pills__container').perfectScrollbar();
|
$('.nav-pills__container').perfectScrollbar();
|
||||||
|
|
||||||
this.updateTitle();
|
this.updateTitle();
|
||||||
|
this.updateUnreadIndicators();
|
||||||
|
|
||||||
|
$(window).on('resize', this.onResize);
|
||||||
},
|
},
|
||||||
componentDidUpdate: function() {
|
componentDidUpdate: function() {
|
||||||
this.updateTitle();
|
this.updateTitle();
|
||||||
|
this.updateUnreadIndicators();
|
||||||
},
|
},
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
|
$(window).off('resize', this.onResize);
|
||||||
|
|
||||||
ChannelStore.removeChangeListener(this.onChange);
|
ChannelStore.removeChangeListener(this.onChange);
|
||||||
UserStore.removeChangeListener(this.onChange);
|
UserStore.removeChangeListener(this.onChange);
|
||||||
UserStore.removeStatusesChangeListener(this.onChange);
|
UserStore.removeStatusesChangeListener(this.onChange);
|
||||||
@@ -157,7 +163,10 @@ module.exports = React.createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (UserStore.getCurrentId() !== msg.user_id) {
|
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 channel = ChannelStore.get(msg.channel_id);
|
||||||
|
|
||||||
var user = UserStore.getCurrentUser();
|
var user = UserStore.getCurrentUser();
|
||||||
@@ -175,7 +184,10 @@ module.exports = React.createClass({
|
|||||||
username = UserStore.getProfile(msg.user_id).username;
|
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 repRegex = new RegExp('<br>', 'g');
|
||||||
var post = JSON.parse(msg.props.post);
|
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() {
|
getInitialState: function() {
|
||||||
return getStateFromStores();
|
return getStateFromStores();
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
var members = this.state.members;
|
var members = this.state.members;
|
||||||
var newsActive = window.location.pathname === '/' ? 'active' : '';
|
var activeId = this.state.active_id;
|
||||||
var badgesActive = false;
|
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 self = this;
|
||||||
var channelItems = this.state.channels.map(function(channel) {
|
this.firstUnreadChannel = null;
|
||||||
if (channel.type != 'O') {
|
this.lastUnreadChannel = null;
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
function createChannelElement(channel) {
|
||||||
var channelMember = members[channel.id];
|
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 = '';
|
var titleClass = '';
|
||||||
if (msgCount > 0 && channelMember.notify_level !== 'quiet') {
|
if (unread) {
|
||||||
titleClass = 'unread-title';
|
titleClass = 'unread-title';
|
||||||
|
|
||||||
|
if (!self.firstUnreadChannel) {
|
||||||
|
self.firstUnreadChannel = channel.name;
|
||||||
|
}
|
||||||
|
self.lastUnreadChannel = channel.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
var badge = '';
|
var badge = null;
|
||||||
if (channelMember.mention_count > 0) {
|
if (channelMember) {
|
||||||
badge = <span className='badge pull-right small'>{channelMember.mention_count}</span>;
|
if (channel.type === 'D') {
|
||||||
badgesActive = true;
|
// direct message channels show badges for any number of unread posts
|
||||||
titleClass = 'unread-title';
|
var msgCount = channel.total_msg_count - channelMember.msg_count;
|
||||||
}
|
if (msgCount > 0) {
|
||||||
|
badge = <span className='badge pull-right small'>{msgCount}</span>;
|
||||||
return (
|
badgesActive = true;
|
||||||
<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>
|
}
|
||||||
);
|
} 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>;
|
||||||
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>;
|
|
||||||
badgesActive = true;
|
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');
|
var link = document.createElement('link');
|
||||||
link.type = 'image/x-icon';
|
link.type = 'image/x-icon';
|
||||||
link.rel = 'shortcut icon';
|
link.rel = 'shortcut icon';
|
||||||
@@ -348,19 +400,26 @@ module.exports = React.createClass({
|
|||||||
}
|
}
|
||||||
head.appendChild(link);
|
head.appendChild(link);
|
||||||
|
|
||||||
if (channelItems.length == 0) {
|
var directMessageMore = null;
|
||||||
<li><small>Loading...</small></li>
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SidebarHeader teamDisplayName={this.props.teamDisplayName} teamType={this.props.teamType} />
|
<SidebarHeader teamDisplayName={this.props.teamDisplayName} teamType={this.props.teamType} />
|
||||||
<SearchBox />
|
<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'>
|
<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>
|
<li><h4>Channels<a className='add-channel-btn' href='#' data-toggle='modal' data-target='#new_channel' data-channeltype='O'>+</a></h4></li>
|
||||||
{channelItems}
|
{channelItems}
|
||||||
@@ -374,9 +433,7 @@ module.exports = React.createClass({
|
|||||||
<ul className='nav nav-pills nav-stacked'>
|
<ul className='nav nav-pills nav-stacked'>
|
||||||
<li><h4>Private Messages</h4></li>
|
<li><h4>Private Messages</h4></li>
|
||||||
{directMessageItems}
|
{directMessageItems}
|
||||||
{ 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>
|
|
||||||
: '' }
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -35,7 +35,29 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: auto;
|
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 {
|
||||||
&.nav-stacked {
|
&.nav-stacked {
|
||||||
> li+li {
|
> li+li {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user