From 7399d349dedc35c8c05b223a3c143a420bf2411f Mon Sep 17 00:00:00 2001 From: Pepijn Date: Thu, 8 Dec 2016 00:08:37 +0100 Subject: Created a new message indicator component to indicate new messages (#4299) * Created a new message indicator component to indicate new messages are present outside the user's view: * Created new component with styling * Theming and i18n support for new messages indicator * Count new unviewed messages and integrate with component * Coding style for new message indicator * Fixed bugs with new message indicator: * Fix display issues with new message indicator * Update text to deal with plurals in React * Fix coded style for ticket 'PLT-1917': add new message indicator. * Use only server generated timestamps for checking new messages at the bottom * Move transitionend to prop, fix style selectors --- .../post_view/components/new_message_indicator.jsx | 65 ++++++++++++++++++++++ .../components/post_view/components/post_list.jsx | 27 ++++++++- .../components/post_view/post_view_controller.jsx | 13 ++++- webapp/i18n/en.json | 1 + webapp/sass/layout/_post.scss | 27 +++++++++ webapp/utils/utils.jsx | 3 + 6 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 webapp/components/post_view/components/new_message_indicator.jsx (limited to 'webapp') 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 000000000..7407c1024 --- /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 a0cfb208e..157e0dde2 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 7a119b7af..9c93c09d7 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 00b7f53f8..7d7cc3583 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) { -- cgit v1.2.3-1-g7c22