diff --git a/webapp/components/post_view/components/new_message_indicator.jsx b/webapp/components/post_view/components/new_message_indicator.jsx
new file mode 100644
index 0000000000..7407c1024d
--- /dev/null
+++ b/webapp/components/post_view/components/new_message_indicator.jsx
@@ -0,0 +1,65 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+import React from 'react';
+import {FormattedMessage} from 'react-intl';
+
+export default class NewMessageIndicator extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ visible: false,
+ rendered: false
+ };
+ }
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.newMessages > 0) {
+ this.setState({rendered: true}, () => {
+ this.setState({visible: true});
+ });
+ } else {
+ this.setState({visible: false});
+ }
+ }
+ render() {
+ let className = 'nav-pills__unread-indicator-bottom';
+ if (this.state.visible > 0) {
+ className += ' visible';
+ }
+ if (!this.state.rendered) {
+ className += ' disabled';
+ }
+ return (
+
+
+
+
+
+
+ );
+ }
+
+ // Sync 'rendered' state with visibility param, only after transitions
+ // have ended
+ setRendered() {
+ this.setState({rendered: this.state.visible});
+ }
+}
+NewMessageIndicator.defaultProps = {
+ newMessages: 0
+};
+
+NewMessageIndicator.propTypes = {
+ onClick: React.PropTypes.func.isRequired,
+ newMessages: React.PropTypes.number
+};
diff --git a/webapp/components/post_view/components/post_list.jsx b/webapp/components/post_view/components/post_list.jsx
index a0cfb208ed..157e0dde2c 100644
--- a/webapp/components/post_view/components/post_list.jsx
+++ b/webapp/components/post_view/components/post_list.jsx
@@ -6,6 +6,7 @@ import $ from 'jquery';
import Post from './post.jsx';
import FloatingTimestamp from './floating_timestamp.jsx';
import ScrollToBottomArrows from './scroll_to_bottom_arrows.jsx';
+import NewMessageIndicator from './new_message_indicator.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
@@ -56,7 +57,8 @@ export default class PostList extends React.Component {
this.state = {
isScrolling: false,
fullWidthIntro: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN,
- topPostId: null
+ topPostId: null,
+ unViewedCount: 0
};
if (props.channel) {
@@ -74,6 +76,23 @@ export default class PostList extends React.Component {
this.introText = createChannelIntroMessage(this.props.channel, this.state.fullWidthIntro);
}
}
+
+ const posts = nextProps.postList.posts;
+ const order = nextProps.postList.order;
+ let unViewedCount = 0;
+
+ // Only count if we're not at the bottom, not in highlight view,
+ // or anything else
+ if (nextProps.scrollType === Constants.ScrollTypes.FREE) {
+ for (let i = order.length - 1; i >= 0; i--) {
+ const post = posts[order[i]];
+ if (post.create_at > nextProps.lastViewedBottom &&
+ post.user_id !== nextProps.currentUser.id) {
+ unViewedCount++;
+ }
+ }
+ }
+ this.setState({unViewedCount});
}
handleKeyDown(e) {
@@ -553,6 +572,10 @@ export default class PostList extends React.Component {
atBottom={this.wasAtBottom}
onClick={this.scrollToBottomAnimated}
/>
+
{users} and {lastUser} reacted with :{emojiName}:",
diff --git a/webapp/sass/layout/_post.scss b/webapp/sass/layout/_post.scss
index 7a119b7af4..9c93c09d70 100644
--- a/webapp/sass/layout/_post.scss
+++ b/webapp/sass/layout/_post.scss
@@ -258,6 +258,33 @@
outline: none;
text-align: center;
}
+ .nav-pills__unread-indicator-bottom {
+ position: absolute;
+ bottom: -30px;
+ left: 0;
+ right: 0;
+
+ @include border-radius(50px);
+ font-size: 13.5px;
+ margin: 0 auto;
+ padding: 3px 0 4px;
+ text-align: center;
+ max-width: 250px;
+ z-index: 1;
+ cursor: pointer;
+ transition: bottom 0.2s ease-out;
+
+ .fa {
+ margin-right: 0.5rem;
+ }
+
+ &.visible {
+ bottom: 0;
+ }
+ &.disabled {
+ display: none;
+ }
+ }
}
.post-list__timestamp {
diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx
index 00b7f53f83..7d7cc35833 100644
--- a/webapp/utils/utils.jsx
+++ b/webapp/utils/utils.jsx
@@ -560,6 +560,7 @@ export function applyTheme(theme) {
changeCss('body.app__body', 'scrollbar-face-color:' + theme.centerChannelBg);
changeCss('body.app__body', 'scrollbar-track-color:' + theme.centerChannelBg);
changeCss('.app__body .post-list__new-messages-below', 'color:' + theme.centerChannelBg);
+ changeCss('.app__body .nav-pills__unread-indicator-bottom', 'color:' + theme.centerChannelBg);
}
if (theme.centerChannelColor) {
@@ -642,6 +643,8 @@ export function applyTheme(theme) {
changeCss('.app__body a, .app__body a:focus, .app__body a:hover, .app__body .btn, .app__body .btn:focus, .app__body .btn:hover', 'color:' + theme.linkColor);
changeCss('.app__body .attachment .attachment__container', 'border-left-color:' + changeOpacity(theme.linkColor, 0.5));
changeCss('.app__body .channel-header__links .icon:hover, .app__body .post .flag-icon__container.visible, .app__body .post .comment-icon__container, .app__body .post .post__reply', 'fill:' + theme.linkColor);
+ changeCss('.app__body .nav-pills__unread-indicator-bottom', 'background:' + theme.linkColor);
+ changeCss('.app__body .nav-pills__unread-indicator-bottom:hover', 'background:' + changeColor(theme.linkColor, 0.1));
}
if (theme.buttonBg) {