From 4fea0b452e773631864f557cff910e880eb459c4 Mon Sep 17 00:00:00 2001
From: hmhealey <harrisonmhealey@gmail.com>
Date: Thu, 6 Aug 2015 14:03:45 -0400
Subject: Refactored sidebar in preparation for adding unread indicators

---
 web/react/components/sidebar.jsx | 145 ++++++++++++++++++---------------------
 1 file changed, 67 insertions(+), 78 deletions(-)

(limited to 'web/react/components/sidebar.jsx')

diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 1d39f5f67..0358032ab 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);
         }
     }
@@ -240,98 +240,94 @@ module.exports = React.createClass({
     },
     render: function() {
         var members = this.state.members;
-        var newsActive = window.location.pathname === '/' ? 'active' : '';
+        var activeId = this.state.active_id;
         var badgesActive = false;
-        var self = this;
-        var channelItems = this.state.channels.map(function(channel) {
-            if (channel.type != 'O') {
-                return '';
-            }
 
+        function createChannelElement(channel) {
             var channelMember = members[channel.id];
-            var active = channel.id === self.state.active_id ? 'active' : '';
+            var active = channel.id === activeId ? 'active' : '';
 
-            var msgCount = channel.total_msg_count - channelMember.msg_count;
-            var titleClass = '';
-            if (msgCount > 0 && channelMember.notify_level !== 'quiet') {
-                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;
             }
 
-            var badge = '';
-            if (channelMember.mention_count > 0) {
-                badge = <span className='badge pull-right small'>{channelMember.mention_count}</span>;
-                badgesActive = true;
+            var titleClass = '';
+            if (unread) {
                 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 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 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'
+            // 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} className={active}>
+                    <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,13 +344,6 @@ module.exports = React.createClass({
         }
         head.appendChild(link);
 
-        if (channelItems.length == 0) {
-            <li><small>Loading...</small></li>
-        }
-
-        if (privateChannelItems.length == 0) {
-            <li><small>Loading...</small></li>
-        }
         return (
             <div>
                 <SidebarHeader teamDisplayName={this.props.teamDisplayName} teamType={this.props.teamType} />
-- 
cgit v1.2.3-1-g7c22


From 38e2b69dba76a83a5da25d09b36293dfe2a85630 Mon Sep 17 00:00:00 2001
From: hmhealey <harrisonmhealey@gmail.com>
Date: Thu, 6 Aug 2015 16:28:43 -0400
Subject: Added the ability for the sidebar to determine whether or not there's
 any unread channels offscreen

---
 web/react/components/sidebar.jsx | 42 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 40 insertions(+), 2 deletions(-)

(limited to 'web/react/components/sidebar.jsx')

diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 0358032ab..abbcd306d 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -132,11 +132,17 @@ module.exports = React.createClass({
         $('.nav-pills__container').perfectScrollbar();
 
         this.updateTitle();
+        this.updateUnreadIndicators();
+
+        $('.nav-pills__container').on('scroll', null, this.onScroll);
     },
     componentDidUpdate: function() {
         this.updateTitle();
+        this.updateUnreadIndicators();
     },
     componentWillUnmount: function() {
+        $('.nav-pills__container').off('scroll', null, this.onScroll);
+
         ChannelStore.removeChangeListener(this.onChange);
         UserStore.removeChangeListener(this.onChange);
         UserStore.removeStatusesChangeListener(this.onChange);
@@ -235,6 +241,28 @@ module.exports = React.createClass({
             }
         }
     },
+    onScroll: 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) {
+                console.log("off top");
+            }
+        }
+
+        if (this.lastUnreadChannel) {
+            var lastUnreadElement = $(this.refs[this.lastUnreadChannel].getDOMNode());
+
+            if (lastUnreadElement.position().top > container.height()) {
+                console.log("off bottom");
+            }
+        }
+    },
     getInitialState: function() {
         return getStateFromStores();
     },
@@ -243,6 +271,11 @@ module.exports = React.createClass({
         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;
+        this.firstUnreadChannel = null;
+        this.lastUnreadChannel = null;
+
         function createChannelElement(channel) {
             var channelMember = members[channel.id];
             var active = channel.id === activeId ? 'active' : '';
@@ -256,6 +289,11 @@ module.exports = React.createClass({
             var titleClass = '';
             if (unread) {
                 titleClass = 'unread-title';
+
+                if (!self.firstUnreadChannel) {
+                    self.firstUnreadChannel = channel.name;
+                }
+                self.lastUnreadChannel = channel.name;
             }
 
             var badge = null;
@@ -302,7 +340,7 @@ module.exports = React.createClass({
             }
 
             return (
-                <li key={channel.name} className={active}>
+                <li key={channel.name} ref={channel.name} className={active}>
                     <a className={'sidebar-channel ' + titleClass} href={href} onClick={clickHandler}>
                         {status}
                         {badge}
@@ -349,7 +387,7 @@ module.exports = React.createClass({
                 <SidebarHeader teamDisplayName={this.props.teamDisplayName} teamType={this.props.teamType} />
                 <SearchBox />
 
-                <div className='nav-pills__container'>
+                <div ref='container' className='nav-pills__container'>
                     <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}
-- 
cgit v1.2.3-1-g7c22


From 34f7451c4ad50fe64cc1c06dbd204c5b6446bca5 Mon Sep 17 00:00:00 2001
From: hmhealey <harrisonmhealey@gmail.com>
Date: Thu, 6 Aug 2015 18:57:57 -0400
Subject: Added indicators for when a channel with unread posts is offscreen

---
 web/react/components/sidebar.jsx | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

(limited to 'web/react/components/sidebar.jsx')

diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index abbcd306d..d6c22230e 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -134,14 +134,14 @@ module.exports = React.createClass({
         this.updateTitle();
         this.updateUnreadIndicators();
 
-        $('.nav-pills__container').on('scroll', null, this.onScroll);
+        $('.nav-pills__container').on('scroll', this.onScroll);
     },
     componentDidUpdate: function() {
         this.updateTitle();
         this.updateUnreadIndicators();
     },
     componentWillUnmount: function() {
-        $('.nav-pills__container').off('scroll', null, this.onScroll);
+        $('.nav-pills__container').off('scroll', this.onScroll);
 
         ChannelStore.removeChangeListener(this.onChange);
         UserStore.removeChangeListener(this.onChange);
@@ -251,7 +251,9 @@ module.exports = React.createClass({
             var firstUnreadElement = $(this.refs[this.firstUnreadChannel].getDOMNode());
 
             if (firstUnreadElement.position().top + firstUnreadElement.height() < 0) {
-                console.log("off top");
+                $(this.refs.topUnreadIndicator.getDOMNode()).css('display', 'initial');
+            } else {
+                $(this.refs.topUnreadIndicator.getDOMNode()).css('display', 'none');
             }
         }
 
@@ -259,7 +261,10 @@ module.exports = React.createClass({
             var lastUnreadElement = $(this.refs[this.lastUnreadChannel].getDOMNode());
 
             if (lastUnreadElement.position().top > container.height()) {
-                console.log("off bottom");
+                $(this.refs.bottomUnreadIndicator.getDOMNode()).css('bottom', '0');
+                $(this.refs.bottomUnreadIndicator.getDOMNode()).css('display', 'initial');
+            } else {
+                $(this.refs.bottomUnreadIndicator.getDOMNode()).css('display', 'none');
             }
         }
     },
@@ -387,6 +392,9 @@ module.exports = React.createClass({
                 <SidebarHeader teamDisplayName={this.props.teamDisplayName} teamType={this.props.teamType} />
                 <SearchBox />
 
+                <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'>
                     <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>
-- 
cgit v1.2.3-1-g7c22


From 02eabee792a1bb220c8fac400ac6b80486d1d954 Mon Sep 17 00:00:00 2001
From: hmhealey <harrisonmhealey@gmail.com>
Date: Thu, 6 Aug 2015 19:07:51 -0400
Subject: Removed ternary statements from sidebar

---
 web/react/components/sidebar.jsx | 33 ++++++++++++++++++++++++++-------
 1 file changed, 26 insertions(+), 7 deletions(-)

(limited to 'web/react/components/sidebar.jsx')

diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index d6c22230e..82a14915c 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -163,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();
@@ -181,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);
@@ -283,7 +289,11 @@ module.exports = React.createClass({
 
         function createChannelElement(channel) {
             var channelMember = members[channel.id];
-            var active = channel.id === activeId ? 'active' : '';
+
+            var linkClass = '';
+            if (channel.id === self.state.active_id) {
+                linkClass = 'active';
+            }
 
             var unread = false;
             if (channelMember) {
@@ -345,7 +355,7 @@ module.exports = React.createClass({
             }
 
             return (
-                <li key={channel.name} ref={channel.name} className={active}>
+                <li key={channel.name} ref={channel.name} className={linkClass}>
                     <a className={'sidebar-channel ' + titleClass} href={href} onClick={clickHandler}>
                         {status}
                         {badge}
@@ -387,6 +397,17 @@ module.exports = React.createClass({
         }
         head.appendChild(link);
 
+        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>
+            );
+        }
+
         return (
             <div>
                 <SidebarHeader teamDisplayName={this.props.teamDisplayName} teamType={this.props.teamType} />
@@ -409,9 +430,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>
-- 
cgit v1.2.3-1-g7c22


From 5b831e78a79dba6f8dbea91c0cc2ba0b9253eb15 Mon Sep 17 00:00:00 2001
From: hmhealey <harrisonmhealey@gmail.com>
Date: Fri, 7 Aug 2015 10:54:13 -0400
Subject: Changed sidebar scroll handler to use React's onScroll

---
 web/react/components/sidebar.jsx | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

(limited to 'web/react/components/sidebar.jsx')

diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 82a14915c..5ce9e5d02 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -133,16 +133,12 @@ module.exports = React.createClass({
 
         this.updateTitle();
         this.updateUnreadIndicators();
-
-        $('.nav-pills__container').on('scroll', this.onScroll);
     },
     componentDidUpdate: function() {
         this.updateTitle();
         this.updateUnreadIndicators();
     },
     componentWillUnmount: function() {
-        $('.nav-pills__container').off('scroll', this.onScroll);
-
         ChannelStore.removeChangeListener(this.onChange);
         UserStore.removeChangeListener(this.onChange);
         UserStore.removeStatusesChangeListener(this.onChange);
@@ -416,7 +412,7 @@ module.exports = React.createClass({
                 <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'>
+                <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}
-- 
cgit v1.2.3-1-g7c22


From 72e5d441ff471d75effe52b4ef0eafe0667aa54e Mon Sep 17 00:00:00 2001
From: hmhealey <harrisonmhealey@gmail.com>
Date: Fri, 7 Aug 2015 10:58:03 -0400
Subject: Added handlers for the sidebar to update unread message indicators on
 page resize

---
 web/react/components/sidebar.jsx | 7 +++++++
 1 file changed, 7 insertions(+)

(limited to 'web/react/components/sidebar.jsx')

diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 5ce9e5d02..fe73cbcf7 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -133,12 +133,16 @@ module.exports = React.createClass({
 
         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);
@@ -246,6 +250,9 @@ module.exports = React.createClass({
     onScroll: function(e) {
         this.updateUnreadIndicators();
     },
+    onResize: function(e) {
+        this.updateUnreadIndicators();
+    },
     updateUnreadIndicators: function() {
         var container = $(this.refs.container.getDOMNode());
 
-- 
cgit v1.2.3-1-g7c22

