Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

Commit

Permalink
Fixes #9506 : Keyboard shortcut for adding reactions to last message …
Browse files Browse the repository at this point in the history
…in channel or thread (#4478)

* added shortcut in modal

* lk file

* ran i18n extract

* i18n

* added the constants

* added en translations

* added the ability to add last posts reaction to center text input

* added e2e tests for center area shortcut

* lint

* new tes

* short comment added

* added RHS shortcut for last message reaction

* fixed rhs and center shortcut

* added comments to def posts

* clear timeouts in comp unmount

* update snapshot

* fixed tests

* added tests

* new lines at the end of the file

* Spell check in comments

* reverted underscores

* spel in test files

* more spell check in test files

* added mac and win keyboard control button to shortcut

* trailing spc removed

* func name change

* new proposal change added as per comment #4478

* complicated issue regarding closing of shortcut emetter removed and simplified

* simplified action for emoji for last post

* proptypes changes after recent modifications

* addressed issue of improper proptypes, fixed point 2 of mattermost-webapp/pull/4478#pullrequestreview-339313236 for rhs comment

* fixed race condition to dissallow shortcut for mobile, system and other similar post types

* for cleaner git diff

* for cleaner git diff

* var name change

* more var name changes

* more more var changes

* all var names updated as per @deanwhillier comments

* updated tests

* rever package-lock

* blocked shortcut when post not in view

* hide shortcut when executed on post not visible to user

* - Reverted to old spec where in the shortcut should be executed when focus is not on center
- Blocked the shortcut when any modals or popups are open

* blocked the shortcut for dates, welcome messages and other non message posts

* fixed tests

* some minor code beautifications

* moved e2e test under emoji folder

* improved existing tests

* added more tests

* minor typo

* removed commented tests

* fix failing type tests

* changin few tests

* modified tests after test review

* lint check

* removed the one test

* Update components/rhs_comment/rhs_comment.jsx comment

Co-Authored-By: Sudheer <[email protected]>

* Update comment in components/post_view/post_list_row/post_list_row.jsx

Co-Authored-By: Sudheer <[email protected]>

* common function used

Co-authored-by: Sudheer <[email protected]>
  • Loading branch information
M-ZubairAhmed and sudheerDev committed Feb 26, 2020
1 parent 47ad2c9 commit 03de35a
Show file tree
Hide file tree
Showing 31 changed files with 1,102 additions and 31 deletions.
13 changes: 13 additions & 0 deletions actions/post_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,16 @@ export function toggleEmbedVisibility(postId) {
export function resetEmbedVisibility() {
return StorageActions.actionOnGlobalItemsWithPrefix(StoragePrefixes.EMBED_VISIBLE, () => null);
}

/**
* It is called from either center or rhs text input when shortcut for react to last message is pressed
*
* @param {string} emittedFrom - It can be either "CENTER", "RHS_ROOT" or "NO_WHERE"
*/

export function emitShortcutReactToLastPostFrom(emittedFrom) {
return {
type: ActionTypes.EMITTED_SHORTCUT_REACT_TO_LAST_POST,
payload: emittedFrom
};
}
25 changes: 24 additions & 1 deletion components/create_comment/create_comment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {sortFileInfos} from 'mattermost-redux/utils/file_utils';

import * as GlobalActions from 'actions/global_actions.jsx';

import Constants from 'utils/constants';
import Constants, {Locations} from 'utils/constants';
import {intlShape} from 'utils/react_intl';
import * as UserAgent from 'utils/user_agent';
import * as Utils from 'utils/utils.jsx';
Expand All @@ -29,6 +29,8 @@ import TextboxLinks from 'components/textbox/textbox_links.jsx';
import FormattedMarkdownMessage from 'components/formatted_markdown_message.jsx';
import MessageSubmitError from 'components/message_submit_error';

const KeyCodes = Constants.KeyCodes;

class CreateComment extends React.PureComponent {
static propTypes = {

Expand Down Expand Up @@ -185,6 +187,10 @@ class CreateComment extends React.PureComponent {
*/
selectedPostFocussedAt: PropTypes.number.isRequired,

/**
* Function to set or unset emoji picker for last message
*/
emitShortcutReactToLastPostFrom: PropTypes.func,
canPost: PropTypes.bool.isRequired,

/**
Expand Down Expand Up @@ -543,6 +549,16 @@ class CreateComment extends React.PureComponent {
this.emitTypingEvent();
}

reactToLastMessage = (e) => {
e.preventDefault();

const {emitShortcutReactToLastPostFrom} = this.props;

// Here we are not handling conditions such as check for modals, popups etc as shortcut is only trigger on
// textbox input focus. Since all of them will already be closed as soon as they loose focus.
emitShortcutReactToLastPostFrom(Locations.RHS_ROOT);
}

emitTypingEvent = () => {
const {channelId, rootId} = this.props;
GlobalActions.emitLocalUserTypingEvent(channelId, rootId);
Expand Down Expand Up @@ -580,6 +596,9 @@ class CreateComment extends React.PureComponent {
}

handleKeyDown = (e) => {
const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey;
const lastMessageReactionKeyCombo = ctrlOrMetaKeyPressed && e.shiftKey && Utils.isKeyPressed(e, KeyCodes.BACK_SLASH);

if (
(this.props.ctrlSend || this.props.codeBlockOnCtrlEnter) &&
Utils.isKeyPressed(e, Constants.KeyCodes.ENTER) &&
Expand Down Expand Up @@ -614,6 +633,10 @@ class CreateComment extends React.PureComponent {
this.props.onMoveHistoryIndexForward();
}
}

if (lastMessageReactionKeyCombo) {
this.reactToLastMessage(e);
}
}

handleFileUploadChange = () => {
Expand Down
2 changes: 2 additions & 0 deletions components/create_comment/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
makeOnSubmit,
makeOnEditLatestPost,
} from 'actions/views/create_comment';
import {emitShortcutReactToLastPostFrom} from 'actions/post_actions';
import {getPostDraft, getIsRhsExpanded, getSelectedPostFocussedAt} from 'selectors/rhs';

import CreateComment from './create_comment.jsx';
Expand Down Expand Up @@ -135,6 +136,7 @@ function makeMapDispatchToProps() {
onEditLatestPost,
resetCreatePostRequest,
getChannelTimezones,
emitShortcutReactToLastPostFrom
}, dispatch);
};
}
Expand Down
34 changes: 30 additions & 4 deletions components/create_post/create_post.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {Posts} from 'mattermost-redux/constants';
import {sortFileInfos} from 'mattermost-redux/utils/file_utils';

import * as GlobalActions from 'actions/global_actions.jsx';
import Constants, {StoragePrefixes, ModalIdentifiers} from 'utils/constants';
import Constants, {StoragePrefixes, ModalIdentifiers, Locations, A11yClassNames} from 'utils/constants';
import {t} from 'utils/i18n';
import {
containsAtChannel,
Expand Down Expand Up @@ -185,7 +185,6 @@ class CreatePost extends React.PureComponent {
useChannelMentions: PropTypes.bool.isRequired,

intl: intlShape.isRequired,

actions: PropTypes.shape({

/**
Expand Down Expand Up @@ -259,8 +258,12 @@ class CreatePost extends React.PureComponent {
* Function to get the users timezones in the channel
*/
getChannelTimezones: PropTypes.func.isRequired,

scrollPostListToBottom: PropTypes.func.isRequired,

/**
* Function to set or unset emoji picker for last message
*/
emitShortcutReactToLastPostFrom: PropTypes.func
}).isRequired,
}

Expand Down Expand Up @@ -880,11 +883,18 @@ class CreatePost extends React.PureComponent {
}

documentKeyHandler = (e) => {
if ((e.ctrlKey || e.metaKey) && Utils.isKeyPressed(e, KeyCodes.FORWARD_SLASH)) {
const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey;
const shortcutModalKeyCombo = ctrlOrMetaKeyPressed && Utils.isKeyPressed(e, KeyCodes.FORWARD_SLASH);
const lastMessageReactionKeyCombo = ctrlOrMetaKeyPressed && e.shiftKey && Utils.isKeyPressed(e, KeyCodes.BACK_SLASH);

if (shortcutModalKeyCombo) {
e.preventDefault();

GlobalActions.toggleShortcutsModal();
return;
} else if (lastMessageReactionKeyCombo) {
this.reactToLastMessage(e);
return;
}

this.focusTextboxIfNecessary(e);
Expand Down Expand Up @@ -987,6 +997,22 @@ class CreatePost extends React.PureComponent {
this.props.actions.moveHistoryIndexForward(Posts.MESSAGE_TYPES.POST).then(() => this.fillMessageFromHistory());
}

reactToLastMessage = (e) => {
e.preventDefault();

const {rhsExpanded, actions: {emitShortcutReactToLastPostFrom}} = this.props;
const noModalsAreOpen = document.getElementsByClassName(A11yClassNames.MODAL).length === 0;
const noPopupsDropdownsAreOpen = document.getElementsByClassName(A11yClassNames.POPUP).length === 0;

// Block keyboard shortcut react to last message when :
// - RHS is completely expanded
// - Any dropdown/popups are open
// - Any modals are open
if (!rhsExpanded && noModalsAreOpen && noPopupsDropdownsAreOpen) {
emitShortcutReactToLastPostFrom(Locations.CENTER);
}
}

handleBlur = () => {
this.lastBlurAt = Date.now();
}
Expand Down
7 changes: 5 additions & 2 deletions components/create_post/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ import {Permissions, Posts, Preferences as PreferencesRedux} from 'mattermost-re

import {connectionErrorCount} from 'selectors/views/system';

import {addReaction, createPost, setEditingPost} from 'actions/post_actions.jsx';
import {addReaction, createPost, setEditingPost, emitShortcutReactToLastPostFrom} from 'actions/post_actions.jsx';
import {scrollPostListToBottom} from 'actions/views/channel';
import {selectPostFromRightHandSideSearchByPostId} from 'actions/views/rhs';
import {executeCommand} from 'actions/command';
import {runMessageWillBePostedHooks, runSlashCommandWillBePostedHooks} from 'actions/hooks';
import {getPostDraft, getIsRhsExpanded} from 'selectors/rhs';
import {getCurrentLocale} from 'selectors/i18n';
import {getEmojiMap} from 'selectors/emojis';
import {getEmojiMap, getShortcutReactToLastPostEmittedFrom} from 'selectors/emojis';
import {setGlobalItem, actionOnGlobalItemsWithPrefix} from 'actions/storage';
import {openModal} from 'actions/views/modals';
import {Constants, Preferences, StoragePrefixes, TutorialSteps, UserStatuses} from 'utils/constants';
Expand Down Expand Up @@ -65,6 +65,7 @@ function makeMapStateToProps() {
const userIsOutOfOffice = getStatusForUserId(state, currentUserId) === UserStatuses.OUT_OF_OFFICE;
const badConnection = connectionErrorCount(state) > 1;
const isTimezoneEnabled = config.ExperimentalTimezone === 'true';
const shortcutReactToLastPostEmittedFrom = getShortcutReactToLastPostEmittedFrom(state);
const canPost = haveIChannelPermission(
state,
{
Expand Down Expand Up @@ -104,6 +105,7 @@ function makeMapStateToProps() {
emojiMap: getEmojiMap(state),
badConnection,
isTimezoneEnabled,
shortcutReactToLastPostEmittedFrom,
canPost,
useChannelMentions,
};
Expand All @@ -129,6 +131,7 @@ function mapDispatchToProps(dispatch) {
clearDraftUploads: actionOnGlobalItemsWithPrefix,
selectPostFromRightHandSideSearchByPostId,
setEditingPost,
emitShortcutReactToLastPostFrom,
openModal,
executeCommand,
getChannelTimezones,
Expand Down
7 changes: 6 additions & 1 deletion components/post_view/post/post.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,17 @@ class Post extends React.PureComponent {
*/
replyCount: PropTypes.number,

/**
* To Check if the current post is last in the list
*/
isLastPost: PropTypes.bool,

/**
* Whether or not the channel that contains this post is archived
*/
channelIsArchived: PropTypes.bool.isRequired,

intl: intlShape.isRequired,

actions: PropTypes.shape({
selectPost: PropTypes.func.isRequired,
selectPostCard: PropTypes.func.isRequired,
Expand Down Expand Up @@ -371,6 +375,7 @@ class Post extends React.PureComponent {
replyCount={this.props.replyCount}
showTimeWithoutHover={!hideProfilePicture}
hover={this.state.hover || this.state.a11yActive}
isLastPost={this.props.isLastPost}
/>
<PostBody
post={post}
Expand Down
7 changes: 7 additions & 0 deletions components/post_view/post_header/post_header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ export default class PostHeader extends React.PureComponent {
* If the user that made the post is a guest.
*/
isGuest: PropTypes.bool.isRequired,

/**
* To Check if the current post is last in the list
*/
isLastPost: PropTypes.bool,

}

render() {
Expand Down Expand Up @@ -171,6 +177,7 @@ export default class PostHeader extends React.PureComponent {
isFirstReply={this.props.isFirstReply}
showTimeWithoutHover={this.props.showTimeWithoutHover}
hover={this.props.hover}
isLastPost={this.props.isLastPost}
/>
</div>
</div>
Expand Down
5 changes: 5 additions & 0 deletions components/post_view/post_info/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {get} from 'mattermost-redux/selectors/entities/preferences';
import {getConfig} from 'mattermost-redux/selectors/entities/general';

import {emitShortcutReactToLastPostFrom} from 'actions/post_actions.jsx';
import {Preferences} from 'utils/constants';
import {getSelectedPostCard} from 'selectors/rhs.jsx';
import {getShortcutReactToLastPostEmittedFrom} from 'selectors/emojis';

import PostInfo from './post_info.jsx';

Expand All @@ -21,6 +23,7 @@ function mapStateToProps(state, ownProps) {
const channelIsArchived = channel ? channel.delete_at !== 0 : null;
const enableEmojiPicker = config.EnableEmojiPicker === 'true' && !channelIsArchived;
const teamId = getCurrentTeamId(state);
const shortcutReactToLastPostEmittedFrom = getShortcutReactToLastPostEmittedFrom(state);

return {
teamId,
Expand All @@ -29,13 +32,15 @@ function mapStateToProps(state, ownProps) {
isCardOpen: selectedCard && selectedCard.id === ownProps.post.id,
enableEmojiPicker,
isReadOnly: isCurrentChannelReadOnly(state) || channelIsArchived,
shortcutReactToLastPostEmittedFrom
};
}

function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
removePost,
emitShortcutReactToLastPostFrom,
}, dispatch),
};
}
Expand Down
Loading

0 comments on commit 03de35a

Please sign in to comment.