Skip to content

Commit

Permalink
PLT-3366 Holding down the ALT key and clicking on a message adds a ne…
Browse files Browse the repository at this point in the history
…w messages indicator (squashed) (mattermost#3374)
  • Loading branch information
samogot authored and jwilander committed Jul 14, 2016
1 parent 682eede commit 2099e09
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 36 deletions.
12 changes: 12 additions & 0 deletions actions/channel_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {browserHistory} from 'react-router/es6';
import * as Utils from 'utils/utils.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import Client from 'utils/web_client.jsx';

export function goToChannel(channel) {
Expand All @@ -24,3 +26,13 @@ export function goToChannel(channel) {
export function executeCommand(channelId, message, suggest, success, error) {
Client.executeCommand(channelId, message, suggest, success, error);
}

export function setChannelAsRead(channelIdParam) {
const channelId = channelIdParam || ChannelStore.getCurrentId();
AsyncClient.updateLastViewedAt();
ChannelStore.resetCounts(channelId);
ChannelStore.emitChange();
if (channelId === ChannelStore.getCurrentId()) {
ChannelStore.emitLastViewed(Number.MAX_VALUE, false);
}
}
52 changes: 52 additions & 0 deletions actions/post_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import PostStore from 'stores/post_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';

import * as PostUtils from 'utils/post_utils.jsx';
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;

Expand Down Expand Up @@ -62,3 +64,53 @@ export function handleNewPost(post, msg) {
websocketMessageProps
});
}

export function setUnreadPost(channelId, postId) {
let lastViewed = 0;
let ownNewMessage = false;
const post = PostStore.getPost(channelId, postId);
const posts = PostStore.getVisiblePosts(channelId).posts;
var currentUsedId = UserStore.getCurrentId();
if (currentUsedId === post.user_id || PostUtils.isSystemMessage(post)) {
for (const otherPostId in posts) {
if (lastViewed < posts[otherPostId].create_at && currentUsedId !== posts[otherPostId].user_id && !PostUtils.isSystemMessage(posts[otherPostId])) {
lastViewed = posts[otherPostId].create_at;
}
}
if (lastViewed === 0) {
lastViewed = Number.MAX_VALUE;
} else if (lastViewed > post.create_at) {
lastViewed = post.create_at - 1;
ownNewMessage = true;
} else {
lastViewed -= 1;
}
} else {
lastViewed = post.create_at - 1;
}

if (lastViewed === Number.MAX_VALUE) {
AsyncClient.updateLastViewedAt();
ChannelStore.resetCounts(ChannelStore.getCurrentId());
ChannelStore.emitChange();
} else {
let unreadPosts = 0;
for (const otherPostId in posts) {
if (posts[otherPostId].create_at > lastViewed) {
unreadPosts += 1;
}
}
const member = ChannelStore.getMember(channelId);
const channel = ChannelStore.get(channelId);
member.last_viewed_at = lastViewed;
member.msg_count = channel.total_msg_count - unreadPosts;
member.mention_count = 0;
ChannelStore.setChannelMember(member);
ChannelStore.setUnreadCount(channelId);
AsyncClient.setLastViewedAt(lastViewed, channelId);
}

if (channelId === ChannelStore.getCurrentId()) {
ChannelStore.emitLastViewed(lastViewed, ownNewMessage);
}
}
82 changes: 48 additions & 34 deletions components/edit_post_modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class EditPostModal extends React.Component {
this.handleEditPostEvent = this.handleEditPostEvent.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.onPreferenceChange = this.onPreferenceChange.bind(this);
this.onModalHidden = this.onModalHidden.bind(this);
this.onModalShow = this.onModalShow.bind(this);
this.onModalShown = this.onModalShown.bind(this);
this.onModalHide = this.onModalHide.bind(this);
this.onModalKeyDown = this.onModalKeyDown.bind(this);

this.state = {editText: '', originalText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: '', typing: false};
}
Expand Down Expand Up @@ -116,46 +121,55 @@ class EditPostModal extends React.Component {
ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter')
});
}
componentDidMount() {
var self = this;

$(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', () => {
self.setState({editText: '', originalText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: '', typing: false});
onModalHidden() {
this.setState({editText: '', originalText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: '', typing: false});
}
onModalShow(e) {
var button = e.relatedTarget;
if (!button) {
return;
}
this.setState({
editText: $(button).attr('data-message'),
originalText: $(button).attr('data-message'),
title: $(button).attr('data-title'),
channel_id: $(button).attr('data-channelid'),
post_id: $(button).attr('data-postid'),
comments: $(button).attr('data-comments'),
refocusId: $(button).attr('data-refocusid'),
typing: false
});

$(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', (e) => {
var button = e.relatedTarget;
if (!button) {
return;
}
self.setState({
editText: $(button).attr('data-message'),
originalText: $(button).attr('data-message'),
title: $(button).attr('data-title'),
channel_id: $(button).attr('data-channelid'),
post_id: $(button).attr('data-postid'),
comments: $(button).attr('data-comments'),
refocusId: $(button).attr('data-refocusid'),
typing: false
}
onModalShown() {
this.refs.editbox.focus();
}
onModalHide() {
if (this.state.refocusId !== '') {
setTimeout(() => {
$(this.state.refocusId).get(0).focus();
});
});

$(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', () => {
self.refs.editbox.focus();
});

$(ReactDOM.findDOMNode(this.refs.modal)).on('hide.bs.modal', () => {
if (self.state.refocusId !== '') {
setTimeout(() => {
$(self.state.refocusId).get(0).focus();
});
}
});

}
}
onModalKeyDown(e) {
if (e.which === Constants.KeyCodes.ESCAPE) {
e.stopPropagation();
}
}
componentDidMount() {
$(this.refs.modal).on('hidden.bs.modal', this.onModalHidden);
$(this.refs.modal).on('show.bs.modal', this.onModalShow);
$(this.refs.modal).on('shown.bs.modal', this.onModalShown);
$(this.refs.modal).on('hide.bs.modal', this.onModalHide);
$(this.refs.modal).on('keydown', this.onModalKeyDown);
PostStore.addEditPostListener(this.handleEditPostEvent);
PreferenceStore.addChangeListener(this.onPreferenceChange);
}
componentWillUnmount() {
$(this.refs.modal).off('hidden.bs.modal', this.onModalHidden);
$(this.refs.modal).off('show.bs.modal', this.onModalShow);
$(this.refs.modal).off('shown.bs.modal', this.onModalShown);
$(this.refs.modal).off('hide.bs.modal', this.onModalHide);
$(this.refs.modal).off('keydown', this.onModalKeyDown);
PostStore.removeEditPostListner(this.handleEditPostEvent);
PreferenceStore.removeChangeListener(this.onPreferenceChange);
}
Expand Down
9 changes: 9 additions & 0 deletions components/post_view/components/post.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const ActionTypes = Constants.ActionTypes;
import * as Utils from 'utils/utils.jsx';
import * as PostUtils from 'utils/post_utils.jsx';
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import * as PostActions from 'actions/post_actions.jsx';

import React from 'react';

Expand All @@ -20,6 +21,7 @@ export default class Post extends React.Component {
this.handleCommentClick = this.handleCommentClick.bind(this);
this.handleDropdownOpened = this.handleDropdownOpened.bind(this);
this.forceUpdateInfo = this.forceUpdateInfo.bind(this);
this.handlePostClick = this.handlePostClick.bind(this);

this.state = {
dropdownOpened: false
Expand Down Expand Up @@ -47,6 +49,12 @@ export default class Post extends React.Component {
this.refs.info.forceUpdate();
this.refs.header.forceUpdate();
}
handlePostClick(e) {
if (e.altKey) {
e.preventDefault();
PostActions.setUnreadPost(this.props.post.channel_id, this.props.post.id);
}
}
shouldComponentUpdate(nextProps, nextState) {
if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
return true;
Expand Down Expand Up @@ -213,6 +221,7 @@ export default class Post extends React.Component {
<div
id={'post_' + post.id}
className={'post ' + sameUserClass + ' ' + compactClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss + ' ' + shouldHighlightClass + ' ' + systemMessageClass + ' ' + hideControls + ' ' + dropdownOpenedClass}
onClick={this.handlePostClick}
>
<div className={'post__content ' + centerClass}>
{profilePicContainer}
Expand Down
18 changes: 16 additions & 2 deletions components/post_view/components/post_list.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import * as Utils from 'utils/utils.jsx';
import * as PostUtils from 'utils/post_utils.jsx';
import DelayedAction from 'utils/delayed_action.jsx';

import * as ChannelActions from 'actions/channel_actions.jsx';

import Constants from 'utils/constants.jsx';
const ScrollTypes = Constants.ScrollTypes;

Expand All @@ -41,6 +43,7 @@ export default class PostList extends React.Component {
this.handleResize = this.handleResize.bind(this);
this.scrollToBottom = this.scrollToBottom.bind(this);
this.scrollToBottomAnimated = this.scrollToBottomAnimated.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);

this.jumpToPostNode = null;
this.wasAtBottom = true;
Expand All @@ -61,6 +64,13 @@ export default class PostList extends React.Component {
}
}

handleKeyDown(e) {
if (e.which === Constants.KeyCodes.ESCAPE && $('.popover.in,.modal.in').length === 0) {
e.preventDefault();
ChannelActions.setChannelAsRead();
}
}

isAtBottom() {
// consider the view to be at the bottom if it's within this many pixels of the bottom
const atBottomMargin = 10;
Expand Down Expand Up @@ -297,7 +307,7 @@ export default class PostList extends React.Component {
);
}

if (postUserId !== userId &&
if ((postUserId !== userId || this.props.ownNewMessage) &&
this.props.lastViewed !== 0 &&
post.create_at > this.props.lastViewed &&
!renderedLastViewed) {
Expand Down Expand Up @@ -417,10 +427,12 @@ export default class PostList extends React.Component {
}

window.addEventListener('resize', this.handleResize);
window.addEventListener('keydown', this.handleKeyDown);
}

componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
window.removeEventListener('keydown', this.handleKeyDown);
this.scrollStopAction.cancel();
}

Expand Down Expand Up @@ -515,7 +527,8 @@ export default class PostList extends React.Component {
}

PostList.defaultProps = {
lastViewed: 0
lastViewed: 0,
ownNewMessage: false
};

PostList.propTypes = {
Expand All @@ -529,6 +542,7 @@ PostList.propTypes = {
showMoreMessagesTop: React.PropTypes.bool,
showMoreMessagesBottom: React.PropTypes.bool,
lastViewed: React.PropTypes.number,
ownNewMessage: React.PropTypes.bool,
postsToHighlight: React.PropTypes.object,
displayNameType: React.PropTypes.string,
displayPostsInCenter: React.PropTypes.bool,
Expand Down
14 changes: 14 additions & 0 deletions components/post_view/post_view_controller.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default class PostViewController extends React.Component {
this.onPostsChange = this.onPostsChange.bind(this);
this.onEmojisChange = this.onEmojisChange.bind(this);
this.onPostsViewJumpRequest = this.onPostsViewJumpRequest.bind(this);
this.onSetNewMessageIndicator = this.onSetNewMessageIndicator.bind(this);
this.onPostListScroll = this.onPostListScroll.bind(this);
this.onActivate = this.onActivate.bind(this);
this.onDeactivate = this.onDeactivate.bind(this);
Expand All @@ -50,6 +51,7 @@ export default class PostViewController extends React.Component {
profiles,
atTop: PostStore.getVisibilityAtTop(channel.id),
lastViewed,
ownNewMessage: false,
scrollType: ScrollTypes.NEW_MESSAGE,
displayNameType: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false'),
displayPostsInCenter: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
Expand Down Expand Up @@ -117,6 +119,7 @@ export default class PostViewController extends React.Component {
PostStore.addChangeListener(this.onPostsChange);
PostStore.addPostsViewJumpListener(this.onPostsViewJumpRequest);
EmojiStore.addChangeListener(this.onEmojisChange);
ChannelStore.addLastViewedListener(this.onSetNewMessageIndicator);
}

onDeactivate() {
Expand All @@ -125,6 +128,7 @@ export default class PostViewController extends React.Component {
PostStore.removeChangeListener(this.onPostsChange);
PostStore.removePostsViewJumpListener(this.onPostsViewJumpRequest);
EmojiStore.removeChangeListener(this.onEmojisChange);
ChannelStore.removeLastViewedListener(this.onSetNewMessageIndicator);
}

componentWillReceiveProps(nextProps) {
Expand All @@ -149,6 +153,7 @@ export default class PostViewController extends React.Component {
this.setState({
channel,
lastViewed,
ownNewMessage: false,
profiles: JSON.parse(JSON.stringify(profiles)),
postList: JSON.parse(JSON.stringify(PostStore.getVisiblePosts(channel.id))),
displayNameType: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false'),
Expand Down Expand Up @@ -178,6 +183,10 @@ export default class PostViewController extends React.Component {
}
}

onSetNewMessageIndicator(lastViewed, ownNewMessage) {
this.setState({lastViewed, ownNewMessage});
}

onPostListScroll(atBottom) {
if (atBottom) {
this.setState({scrollType: ScrollTypes.BOTTOM});
Expand Down Expand Up @@ -219,6 +228,10 @@ export default class PostViewController extends React.Component {
return true;
}

if (nextState.ownNewMessage !== this.state.ownNewMessage) {
return true;
}

if (nextState.showMoreMessagesTop !== this.state.showMoreMessagesTop) {
return true;
}
Expand Down Expand Up @@ -277,6 +290,7 @@ export default class PostViewController extends React.Component {
useMilitaryTime={this.state.useMilitaryTime}
lastViewed={this.state.lastViewed}
emojis={this.state.emojis}
ownNewMessage={this.state.ownNewMessage}
/>
);
}
Expand Down
1 change: 1 addition & 0 deletions components/suggestion/suggestion_box.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export default class SuggestionBox extends React.Component {
e.preventDefault();
} else if (e.which === KeyCodes.ESCAPE) {
GlobalActions.emitClearSuggestions(this.suggestionId);
e.stopPropagation();
} else if (this.props.onKeyDown) {
this.props.onKeyDown(e);
}
Expand Down
4 changes: 4 additions & 0 deletions sass/layout/_sidebar-left.scss
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@
border-radius: 0;
font-weight: 400;
position: relative;

&.unread-title {
font-weight: 600;
}
}
}
}
Expand Down
Loading

0 comments on commit 2099e09

Please sign in to comment.