summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSaturnino Abril <saturnino.abril@gmail.com>2017-08-18 17:04:05 +0800
committerGitHub <noreply@github.com>2017-08-18 17:04:05 +0800
commit9097289c2ce2b719a5aa0f9d567594f2b6a7e30b (patch)
treec8fb16e11052489047157738ee454dafc8c37d04
parent96eab1202717e073782ec399a4e0820cae15b1bb (diff)
downloadchat-9097289c2ce2b719a5aa0f9d567594f2b6a7e30b.tar.gz
chat-9097289c2ce2b719a5aa0f9d567594f2b6a7e30b.tar.bz2
chat-9097289c2ce2b719a5aa0f9d567594f2b6a7e30b.zip
[PLT-3377] Open up a shortcuts dialog instead of printing help text (#7064)
* open up a shortcuts dialog instead of printing help text * Updating UI for keyboard shortcuts modal * update PR per PLT-7284 * fix typo error * fix mixed up shortcut keys * move to client side * fix shortcuts list, remove unused function and revert server side code for autocompletion * remove quick team switcher
-rw-r--r--api/command_shortcuts_test.go15
-rw-r--r--app/command_shortcuts.go63
-rw-r--r--i18n/en.json144
-rw-r--r--webapp/actions/channel_actions.jsx8
-rw-r--r--webapp/actions/global_actions.jsx7
-rw-r--r--webapp/components/create_post.jsx16
-rw-r--r--webapp/components/needs_team/needs_team.jsx2
-rw-r--r--webapp/components/shortcuts_modal.jsx394
-rw-r--r--webapp/components/sidebar_header_dropdown.jsx22
-rwxr-xr-xwebapp/i18n/en.json48
-rw-r--r--webapp/sass/routes/_module.scss1
-rw-r--r--webapp/sass/routes/_shortcuts-modal.scss77
-rw-r--r--webapp/stores/modal_store.jsx1
-rw-r--r--webapp/utils/constants.jsx1
-rw-r--r--webapp/utils/utils.jsx6
15 files changed, 561 insertions, 244 deletions
diff --git a/api/command_shortcuts_test.go b/api/command_shortcuts_test.go
index ce5019049..287223ec3 100644
--- a/api/command_shortcuts_test.go
+++ b/api/command_shortcuts_test.go
@@ -4,23 +4,10 @@
package api
import (
- "github.com/mattermost/platform/model"
- "strings"
"testing"
)
func TestShortcutsCommand(t *testing.T) {
th := Setup().InitBasic()
- Client := th.BasicClient
- channel := th.BasicChannel
-
- rs := Client.Must(Client.Command(channel.Id, "/shortcuts ")).Data.(*model.CommandResponse)
- if !strings.Contains(rs.Text, "CTRL") {
- t.Fatal("failed to display shortcuts")
- }
-
- rs = Client.Must(Client.Command(channel.Id, "/shortcuts mac")).Data.(*model.CommandResponse)
- if !strings.Contains(rs.Text, "CMD") {
- t.Fatal("failed to display Mac shortcuts")
- }
+ th.BasicClient.Must(th.BasicClient.Command(th.BasicChannel.Id, "/shortcuts"))
}
diff --git a/app/command_shortcuts.go b/app/command_shortcuts.go
index e10ae8a87..f6f365b04 100644
--- a/app/command_shortcuts.go
+++ b/app/command_shortcuts.go
@@ -4,9 +4,6 @@
package app
import (
- "bytes"
- "strings"
-
"github.com/mattermost/platform/model"
goi18n "github.com/nicksnyder/go-i18n/i18n"
)
@@ -37,61 +34,9 @@ func (me *ShortcutsProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
}
func (me *ShortcutsProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
- shortcutIds := [...]string{
- "api.command_shortcuts.header",
- // Nav shortcuts
- "api.command_shortcuts.nav.header",
- "api.command_shortcuts.nav.prev",
- "api.command_shortcuts.nav.next",
- "api.command_shortcuts.nav.unread_prev",
- "api.command_shortcuts.nav.unread_next",
- "api.command_shortcuts.nav.switcher",
- "api.command_shortcuts.nav.direct_messages_menu",
- "api.command_shortcuts.nav.settings",
- "api.command_shortcuts.nav.recent_mentions",
- // Files shortcuts
- "api.command_shortcuts.files.header",
- "api.command_shortcuts.files.upload",
- // Msg shortcuts
- "api.command_shortcuts.msgs.header",
- "api.command_shortcuts.msgs.mark_as_read",
- "api.command_shortcuts.msgs.reprint_prev",
- "api.command_shortcuts.msgs.reprint_next",
- "api.command_shortcuts.msgs.edit",
- "api.command_shortcuts.msgs.reply",
- "api.command_shortcuts.msgs.comp_username",
- "api.command_shortcuts.msgs.comp_channel",
- "api.command_shortcuts.msgs.comp_emoji",
- // Browser shortcuts
- "api.command_shortcuts.browser.header",
- "api.command_shortcuts.browser.channel_prev",
- "api.command_shortcuts.browser.channel_next",
- "api.command_shortcuts.browser.font_increase",
- "api.command_shortcuts.browser.font_decrease",
- "api.command_shortcuts.browser.highlight_prev",
- "api.command_shortcuts.browser.highlight_next",
- "api.command_shortcuts.browser.newline",
- }
-
- var osDependentWords map[string]interface{}
- if strings.Contains(message, "mac") {
- osDependentWords = map[string]interface{}{
- "CmdOrCtrl": args.T("api.command_shortcuts.cmd"),
- "ChannelPrevCmd": args.T("api.command_shortcuts.browser.channel_prev.cmd_mac"),
- "ChannelNextCmd": args.T("api.command_shortcuts.browser.channel_next.cmd_mac"),
- }
- } else {
- osDependentWords = map[string]interface{}{
- "CmdOrCtrl": args.T("api.command_shortcuts.ctrl"),
- "ChannelPrevCmd": args.T("api.command_shortcuts.browser.channel_prev.cmd"),
- "ChannelNextCmd": args.T("api.command_shortcuts.browser.channel_next.cmd"),
- }
+ // This command is handled client-side and shouldn't hit the server.
+ return &model.CommandResponse{
+ Text: args.T("api.command_shortcuts.unsupported.app_error"),
+ ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
}
-
- var buffer bytes.Buffer
- for _, element := range shortcutIds {
- buffer.WriteString(args.T(element, osDependentWords))
- }
-
- return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: buffer.String()}
}
diff --git a/i18n/en.json b/i18n/en.json
index 1b3691ecf..69b6f75f5 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -844,156 +844,16 @@
"translation": "The settings command is not supported on your device"
},
{
- "id": "api.command_shortcuts.browser.channel_next",
- "translation": "{{.ChannelNextCmd}}: Next channel in your history\n"
- },
- {
- "id": "api.command_shortcuts.browser.channel_next.cmd",
- "translation": "ALT+RIGHT"
- },
- {
- "id": "api.command_shortcuts.browser.channel_next.cmd_mac",
- "translation": "CMD+]"
- },
- {
- "id": "api.command_shortcuts.browser.channel_prev",
- "translation": "{{.ChannelPrevCmd}}: Previous channel in your history\n"
- },
- {
- "id": "api.command_shortcuts.browser.channel_prev.cmd",
- "translation": "ALT+LEFT"
- },
- {
- "id": "api.command_shortcuts.browser.channel_prev.cmd_mac",
- "translation": "CMD+["
- },
- {
- "id": "api.command_shortcuts.browser.font_decrease",
- "translation": "{{.CmdOrCtrl}}+MINUS: Decrease font size (zoom out)\n"
- },
- {
- "id": "api.command_shortcuts.browser.font_increase",
- "translation": "{{.CmdOrCtrl}}+PLUS: Increase font size (zoom in)\n"
- },
- {
- "id": "api.command_shortcuts.browser.header",
- "translation": "#### Built-in Browser Commands\n\n"
- },
- {
- "id": "api.command_shortcuts.browser.highlight_next",
- "translation": "SHIFT+DOWN (in input field): Highlight text to the next line\n"
- },
- {
- "id": "api.command_shortcuts.browser.highlight_prev",
- "translation": "SHIFT+UP (in input field): Highlight text to the previous line\n"
- },
- {
- "id": "api.command_shortcuts.browser.newline",
- "translation": "SHIFT+ENTER (in input field): Create a new line\n"
- },
- {
- "id": "api.command_shortcuts.cmd",
- "translation": "CMD"
- },
- {
- "id": "api.command_shortcuts.ctrl",
- "translation": "CTRL"
- },
- {
"id": "api.command_shortcuts.desc",
"translation": "Displays a list of keyboard shortcuts"
},
{
- "id": "api.command_shortcuts.files.header",
- "translation": "#### Files\n\n"
- },
- {
- "id": "api.command_shortcuts.files.upload",
- "translation": "{{.CmdOrCtrl}}+U: Upload file(s)\n\n"
- },
- {
- "id": "api.command_shortcuts.header",
- "translation": "### Keyboard Shortcuts\n\n"
- },
- {
- "id": "api.command_shortcuts.msgs.comp_channel",
- "translation": "~[character]+TAB: Autocomplete channel beginning with [character]\n"
- },
- {
- "id": "api.command_shortcuts.msgs.comp_emoji",
- "translation": ":[character]+TAB: Autocomplete emoji beginning with [character]\n\n"
- },
- {
- "id": "api.command_shortcuts.msgs.comp_username",
- "translation": "@[character]+TAB: Autocomplete @username beginning with [character]\n"
- },
- {
- "id": "api.command_shortcuts.msgs.edit",
- "translation": "UP (in empty input field): Edit your last message in the current channel\n"
- },
- {
- "id": "api.command_shortcuts.msgs.header",
- "translation": "#### Messages\n\n"
- },
- {
- "id": "api.command_shortcuts.msgs.mark_as_read",
- "translation": "ESC: Mark all messages in the current channel as read\n"
- },
- {
- "id": "api.command_shortcuts.msgs.reply",
- "translation": "SHIFT+UP (in empty input field): Reply to the most recent message in the current channel\n"
- },
- {
- "id": "api.command_shortcuts.msgs.reprint_next",
- "translation": "{{.CmdOrCtrl}}+DOWN (in empty input field): Reprint the next message or slash command you entered\n"
- },
- {
- "id": "api.command_shortcuts.msgs.reprint_prev",
- "translation": "{{.CmdOrCtrl}}+UP (in empty input field): Reprint the previous message or slash command you entered\n"
- },
- {
"id": "api.command_shortcuts.name",
"translation": "shortcuts"
},
{
- "id": "api.command_shortcuts.nav.direct_messages_menu",
- "translation": "{{.CmdOrCtrl}}+SHIFT+K: Open direct messages menu\n"
- },
- {
- "id": "api.command_shortcuts.nav.header",
- "translation": "#### Navigation\n\n"
- },
- {
- "id": "api.command_shortcuts.nav.next",
- "translation": "ALT+DOWN: Next channel or direct message in left hand sidebar\n"
- },
- {
- "id": "api.command_shortcuts.nav.prev",
- "translation": "ALT+UP: Previous channel or direct message in left hand sidebar\n"
- },
- {
- "id": "api.command_shortcuts.nav.recent_mentions",
- "translation": "{{.CmdOrCtrl}}+SHIFT+M: Open recent mentions\n\n"
- },
- {
- "id": "api.command_shortcuts.nav.settings",
- "translation": "{{.CmdOrCtrl}}+SHIFT+A: Open account settings\n"
- },
- {
- "id": "api.command_shortcuts.nav.switcher",
- "translation": "{{.CmdOrCtrl}}+K: Open a quick channel switcher dialog\n"
- },
- {
- "id": "api.command_shortcuts.nav.switcher_team",
- "translation": "{{.CmdOrCtrl}}+ALT+K: Open a quick team switcher dialog\n"
- },
- {
- "id": "api.command_shortcuts.nav.unread_next",
- "translation": "ALT+SHIFT+DOWN: Next channel or direct message in left hand sidebar with unread messages\n"
- },
- {
- "id": "api.command_shortcuts.nav.unread_prev",
- "translation": "ALT+SHIFT+UP: Previous channel or direct message in left hand sidebar with unread messages\n"
+ "id": "api.command_shortcuts.unsupported.app_error",
+ "translation": "The shortcuts command is not supported on your device"
},
{
"id": "api.command_shrug.desc",
diff --git a/webapp/actions/channel_actions.jsx b/webapp/actions/channel_actions.jsx
index 78df1ff17..1df0d12f5 100644
--- a/webapp/actions/channel_actions.jsx
+++ b/webapp/actions/channel_actions.jsx
@@ -66,12 +66,10 @@ export function executeCommand(message, args, success, error) {
const err = {message: Utils.localizeMessage('create_post.shortcutsNotSupported', 'Keyboard shortcuts are not supported on your device')};
error(err);
return;
- } else if (Utils.isMac()) {
- msg += ' mac';
- } else if (message.indexOf('mac') !== -1) {
- msg = '/shortcuts';
}
- break;
+
+ GlobalActions.showShortcutsModal();
+ return;
case '/leave': {
// /leave command not supported in reply threads.
if (args.channel_id && (args.root_id || args.parent_id)) {
diff --git a/webapp/actions/global_actions.jsx b/webapp/actions/global_actions.jsx
index a163db126..0b7c8f7b1 100644
--- a/webapp/actions/global_actions.jsx
+++ b/webapp/actions/global_actions.jsx
@@ -211,6 +211,13 @@ export function showAccountSettingsModal() {
});
}
+export function showShortcutsModal() {
+ AppDispatcher.handleViewAction({
+ type: ActionTypes.TOGGLE_SHORTCUTS_MODAL,
+ value: true
+ });
+}
+
export function showDeletePostModal(post, commentCount = 0) {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_DELETE_POST_MODAL,
diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx
index b57eb2f17..4b83f3051 100644
--- a/webapp/components/create_post.jsx
+++ b/webapp/components/create_post.jsx
@@ -466,20 +466,8 @@ export default class CreatePost extends React.Component {
showShortcuts(e) {
if ((e.ctrlKey || e.metaKey) && e.keyCode === Constants.KeyCodes.FORWARD_SLASH) {
e.preventDefault();
- const args = {};
- args.channel_id = this.state.channelId;
- args.team_id = TeamStore.getCurrentId();
- ChannelActions.executeCommand(
- '/shortcuts',
- args,
- null,
- (err) => {
- this.setState({
- serverError: err.message,
- submitting: false
- });
- }
- );
+
+ GlobalActions.showShortcutsModal();
}
}
diff --git a/webapp/components/needs_team/needs_team.jsx b/webapp/components/needs_team/needs_team.jsx
index 7b8630f40..387710c9d 100644
--- a/webapp/components/needs_team/needs_team.jsx
+++ b/webapp/components/needs_team/needs_team.jsx
@@ -46,6 +46,7 @@ import InviteMemberModal from 'components/invite_member_modal.jsx';
import LeaveTeamModal from 'components/leave_team_modal.jsx';
import ResetStatusModal from 'components/reset_status_modal';
import LeavePrivateChannelModal from 'components/modals/leave_private_channel_modal.jsx';
+import ShortcutsModal from 'components/shortcuts_modal.jsx';
import iNoBounce from 'inobounce';
import * as UserAgent from 'utils/user_agent.jsx';
@@ -233,6 +234,7 @@ export default class NeedsTeam extends React.Component {
<RemovedFromChannelModal/>
<ResetStatusModal/>
<LeavePrivateChannelModal/>
+ <ShortcutsModal/>
</div>
</div>
);
diff --git a/webapp/components/shortcuts_modal.jsx b/webapp/components/shortcuts_modal.jsx
new file mode 100644
index 000000000..32a3f9c4b
--- /dev/null
+++ b/webapp/components/shortcuts_modal.jsx
@@ -0,0 +1,394 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import Constants from 'utils/constants.jsx';
+import * as Utils from 'utils/utils.jsx';
+
+import ModalStore from 'stores/modal_store.jsx';
+
+import {intlShape, injectIntl, defineMessages} from 'react-intl';
+import {Modal} from 'react-bootstrap';
+import React from 'react';
+
+const allShortcuts = defineMessages({
+ mainHeader: {
+ id: 'shortcuts.header',
+ defaultMessage: 'Keyboard Shortcuts'
+ },
+ navHeader: {
+ id: 'shortcuts.nav.header',
+ defaultMessage: 'Navigation'
+ },
+ navPrev: {
+ default: {
+ id: 'shortcuts.nav.prev',
+ defaultMessage: 'Previous channel:\tAlt|Up'
+ },
+ mac: {
+ id: 'shortcuts.nav.prev.mac',
+ defaultMessage: 'Previous channel:\t⌥|Up'
+ }
+ },
+ navNext: {
+ default: {
+ id: 'shortcuts.nav.next',
+ defaultMessage: 'Next channel:\tAlt|Down'
+ },
+ mac: {
+ id: 'shortcuts.nav.next.mac',
+ defaultMessage: 'Next channel:\t⌥|Down'
+ }
+ },
+ navUnreadPrev: {
+ default: {
+ id: 'shortcuts.nav.unread_prev',
+ defaultMessage: 'Previous unread channel:\tAlt|Shift|Up'
+ },
+ mac: {
+ id: 'shortcuts.nav.unread_prev.mac',
+ defaultMessage: 'Previous unread channel:\t⌥|Shift|Up'
+ }
+ },
+ navUnreadNext: {
+ default: {
+ id: 'shortcuts.nav.unread_next',
+ defaultMessage: 'Next unread channel:\tAlt|Shift|Down'
+ },
+ mac: {
+ id: 'shortcuts.nav.unread_next.mac',
+ defaultMessage: 'Next unread channel:\t⌥|Shift|Down'
+ }
+ },
+ navSwitcher: {
+ default: {
+ id: 'shortcuts.nav.switcher',
+ defaultMessage: 'Quick channel switcher:\tCtrl|K'
+ },
+ mac: {
+ id: 'shortcuts.nav.switcher.mac',
+ defaultMessage: 'Quick channel switcher:\t⌘|K'
+ }
+ },
+ navDMMenu: {
+ default: {
+ id: 'shortcuts.nav.direct_messages_menu',
+ defaultMessage: 'Direct messages menu:\tCtrl|Shift|K'
+ },
+ mac: {
+ id: 'shortcuts.nav.direct_messages_menu.mac',
+ defaultMessage: 'Direct messages menu:\t⌘|Shift|K'
+ }
+ },
+ navSettings: {
+ default: {
+ id: 'shortcuts.nav.settings',
+ defaultMessage: 'Account settings:\tCtrl|Shift|A'
+ },
+ mac: {
+ id: 'shortcuts.nav.settings.mac',
+ defaultMessage: 'Account settings:\t⌘|Shift|A'
+ }
+ },
+ navMentions: {
+ default: {
+ id: 'shortcuts.nav.mentions',
+ defaultMessage: 'Recent mentions:\tCtrl|Shift|M'
+ },
+ mac: {
+ id: 'shortcuts.nav.mentions.mac',
+ defaultMessage: 'Recent mentions:\t⌘|Shift|M'
+ }
+ },
+ msgHeader: {
+ id: 'shortcuts.msgs.header',
+ defaultMessage: 'Messages'
+ },
+ msgMarkAsRead: {
+ id: 'shortcuts.msgs.mark_as_read',
+ defaultMessage: 'Mark current channel as read:\tEsc'
+ },
+ msgInputHeader: {
+ id: 'shortcuts.msgs.input.header',
+ defaultMessage: 'Works inside an empty input field'
+ },
+ msgEdit: {
+ id: 'shortcuts.msgs.edit',
+ defaultMessage: 'Edit last message in channel:\tUp'
+ },
+ msgReply: {
+ id: 'shortcuts.msgs.reply',
+ defaultMessage: 'Reply to last message in channel:\tShift|Up'
+ },
+ msgReprintPrev: {
+ default: {
+ id: 'shortcuts.msgs.reprint_prev',
+ defaultMessage: 'Reprint previous message:\tCtrl|Up'
+ },
+ mac: {
+ id: 'shortcuts.msgs.reprint_prev.mac',
+ defaultMessage: 'Reprint previous message:\t⌘|Up'
+ }
+ },
+ msgReprintNext: {
+ default: {
+ id: 'shortcuts.msgs.reprint_next',
+ defaultMessage: 'Reprint next message:\tCtrl|Down'
+ },
+ mac: {
+ id: 'shortcuts.msgs.reprint_next.mac',
+ defaultMessage: 'Reprint next message:\t⌘|Down'
+ }
+ },
+ msgCompHeader: {
+ id: 'shortcuts.msgs.comp.header',
+ defaultMessage: 'Autocomplete'
+ },
+ msgCompUsername: {
+ id: 'shortcuts.msgs.comp.username',
+ defaultMessage: 'Username:\t@|[a-z]|Tab'
+ },
+ msgCompChannel: {
+ id: 'shortcuts.msgs.comp.channel',
+ defaultMessage: 'Channel:\t~|[a-z]|Tab'
+ },
+ msgCompEmoji: {
+ id: 'shortcuts.msgs.comp.emoji',
+ defaultMessage: 'Emoji:\t:|[a-z]|Tab'
+ },
+ filesHeader: {
+ id: 'shortcuts.files.header',
+ defaultMessage: 'Files'
+ },
+ filesUpload: {
+ default: {
+ id: 'shortcuts.files.upload',
+ defaultMessage: 'Upload files:\tCtrl|U'
+ },
+ mac: {
+ id: 'shortcuts.files.upload.mac',
+ defaultMessage: 'Upload files:\t⌘|U'
+ }
+ },
+ browserHeader: {
+ id: 'shortcuts.browser.header',
+ defaultMessage: 'Built-in Browser Commands'
+ },
+ browserChannelPrev: {
+ default: {
+ id: 'shortcuts.browser.channel_prev',
+ defaultMessage: 'Back in history:\tAlt|Left'
+ },
+ mac: {
+ id: 'shortcuts.browser.channel_prev.mac',
+ defaultMessage: 'Back in history:\t⌘|['
+ }
+ },
+ browserChannelNext: {
+ default: {
+ id: 'shortcuts.browser.channel_next',
+ defaultMessage: 'Forward in history:\tAlt|Right'
+ },
+ mac: {
+ id: 'shortcuts.browser.channel_next.mac',
+ defaultMessage: 'Forward in history:\t⌘|]'
+ }
+ },
+ browserFontIncrease: {
+ default: {
+ id: 'shortcuts.browser.font_increase',
+ defaultMessage: 'Zoom in:\tCtrl|+'
+ },
+ mac: {
+ id: 'shortcuts.browser.font_increase.mac',
+ defaultMessage: 'Zoom in:\t⌘|+'
+ }
+ },
+ browserFontDecrease: {
+ default: {
+ id: 'shortcuts.browser.font_decrease',
+ defaultMessage: 'Zoom out:\tCtrl|-'
+ },
+ mac: {
+ id: 'shortcuts.browser.font_decrease.mac',
+ defaultMessage: 'Zoom out:\t⌘|-'
+ }
+ },
+ browserInputHeader: {
+ id: 'shortcuts.browser.input.header',
+ defaultMessage: 'Works inside an input field'
+ },
+ browserHighlightPrev: {
+ id: 'shortcuts.browser.highlight_prev',
+ defaultMessage: 'Highlight text to the previous line:\tShift|Up'
+ },
+ browserHighlightNext: {
+ id: 'shortcuts.browser.highlight_next',
+ defaultMessage: 'Highlight text to the next line:\tShift|Down'
+ },
+ browserNewline: {
+ id: 'shortcuts.browser.newline',
+ defaultMessage: 'Create a new line:\tShift|Enter'
+ },
+ info: {
+ id: 'shortcuts.info',
+ defaultMessage: 'Begin a message with / for a list of all the commands at your disposal.'
+ }
+});
+
+class ShortcutsModal extends React.PureComponent {
+ static propTypes = {
+ intl: intlShape.isRequired
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ show: false
+ };
+ }
+
+ componentDidMount() {
+ ModalStore.addModalListener(Constants.ActionTypes.TOGGLE_SHORTCUTS_MODAL, this.handleToggle);
+ }
+
+ componentWillUnmount() {
+ ModalStore.removeModalListener(Constants.ActionTypes.TOGGLE_SHORTCUTS_MODAL, this.handleToggle);
+ }
+
+ handleToggle = (value) => {
+ this.setState({
+ show: value
+ });
+ }
+
+ handleHide = () => {
+ this.setState({show: false});
+ }
+
+ getShortcuts(isMac) {
+ const shortcuts = {};
+ Object.keys(allShortcuts).forEach((s) => {
+ if (isMac && allShortcuts[s].mac) {
+ shortcuts[s] = allShortcuts[s].mac;
+ } else if (!isMac && allShortcuts[s].default) {
+ shortcuts[s] = allShortcuts[s].default;
+ } else {
+ shortcuts[s] = allShortcuts[s];
+ }
+ });
+
+ return shortcuts;
+ }
+
+ render() {
+ const shortcuts = this.getShortcuts(Utils.isMac());
+
+ const {formatMessage} = this.props.intl;
+
+ return (
+ <Modal
+ dialogClassName='shortcuts-modal'
+ show={this.state.show}
+ onHide={this.handleHide}
+ onExited={this.handleHide}
+ >
+ <div className='shortcuts-content'>
+ <Modal.Header closeButton={true}>
+ <Modal.Title>
+ <strong>{formatMessage(shortcuts.mainHeader)}</strong>
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body ref='modalBody'>
+ <div className='row'>
+ <div className='col-sm-4'>
+ <div className='section'>
+ <div>
+ <h4 className='section-title'><strong>{formatMessage(shortcuts.navHeader)}</strong></h4>
+ {renderShortcut(formatMessage(shortcuts.navPrev))}
+ {renderShortcut(formatMessage(shortcuts.navNext))}
+ {renderShortcut(formatMessage(shortcuts.navUnreadPrev))}
+ {renderShortcut(formatMessage(shortcuts.navUnreadNext))}
+ {renderShortcut(formatMessage(shortcuts.navSwitcher))}
+ {renderShortcut(formatMessage(shortcuts.navDMMenu))}
+ {renderShortcut(formatMessage(shortcuts.navSettings))}
+ {renderShortcut(formatMessage(shortcuts.navMentions))}
+ </div>
+ </div>
+ </div>
+ <div className='col-sm-4'>
+ <div className='section'>
+ <div>
+ <h4 className='section-title'><strong>{formatMessage(shortcuts.msgHeader)}</strong></h4>
+ {renderShortcut(formatMessage(shortcuts.msgMarkAsRead))}
+ <span><strong>{formatMessage(shortcuts.msgInputHeader)}</strong></span>
+ <div className='subsection'>
+ {renderShortcut(formatMessage(shortcuts.msgEdit))}
+ {renderShortcut(formatMessage(shortcuts.msgReply))}
+ {renderShortcut(formatMessage(shortcuts.msgReprintPrev))}
+ {renderShortcut(formatMessage(shortcuts.msgReprintNext))}
+ </div>
+ <span><strong>{formatMessage(shortcuts.msgCompHeader)}</strong></span>
+ <div className='subsection'>
+ {renderShortcut(formatMessage(shortcuts.msgCompUsername))}
+ {renderShortcut(formatMessage(shortcuts.msgCompChannel))}
+ {renderShortcut(formatMessage(shortcuts.msgCompEmoji))}
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className='col-sm-4'>
+ <div className='section'>
+ <div>
+ <h4 className='section-title'><strong>{formatMessage(shortcuts.filesHeader)}</strong></h4>
+ {renderShortcut(formatMessage(shortcuts.filesUpload))}
+ </div>
+ <div className='section--lower'>
+ <h4 className='section-title'><strong>{formatMessage(shortcuts.browserHeader)}</strong></h4>
+ {renderShortcut(formatMessage(shortcuts.browserChannelPrev))}
+ {renderShortcut(formatMessage(shortcuts.browserChannelNext))}
+ {renderShortcut(formatMessage(shortcuts.browserFontIncrease))}
+ {renderShortcut(formatMessage(shortcuts.browserFontDecrease))}
+ <span><strong>{formatMessage(shortcuts.browserInputHeader)}</strong></span>
+ <div className='subsection'>
+ {renderShortcut(formatMessage(shortcuts.browserHighlightPrev))}
+ {renderShortcut(formatMessage(shortcuts.browserHighlightNext))}
+ {renderShortcut(formatMessage(shortcuts.browserNewline))}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className='info__label'>{formatMessage(shortcuts.info)}</div>
+ </Modal.Body>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+function renderShortcut(text) {
+ if (!text) {
+ return null;
+ }
+
+ const shortcut = text.split('\t');
+ const description = <span>{shortcut[0]}</span>;
+ const keys = shortcut[1].split('|').map((key) =>
+ <span
+ className='shortcut-key'
+ key={key}
+ >
+ {key}
+ </span>
+ );
+
+ return (
+ <div className='shortcut-line'>
+ {description}
+ {keys}
+ </div>
+ );
+}
+
+export default injectIntl(ShortcutsModal);
diff --git a/webapp/components/sidebar_header_dropdown.jsx b/webapp/components/sidebar_header_dropdown.jsx
index 54f8c3a2d..63a5c2541 100644
--- a/webapp/components/sidebar_header_dropdown.jsx
+++ b/webapp/components/sidebar_header_dropdown.jsx
@@ -51,6 +51,7 @@ export default class SidebarHeaderDropdown extends React.Component {
this.showGetTeamInviteLinkModal = this.showGetTeamInviteLinkModal.bind(this);
this.showTeamMembersModal = this.showTeamMembersModal.bind(this);
this.hideTeamMembersModal = this.hideTeamMembersModal.bind(this);
+ this.showShortcutsModal = this.showShortcutsModal.bind(this);
this.onTeamChange = this.onTeamChange.bind(this);
@@ -109,6 +110,13 @@ export default class SidebarHeaderDropdown extends React.Component {
GlobalActions.showAccountSettingsModal();
}
+ showShortcutsModal(e) {
+ e.preventDefault();
+ this.setState({showDropdown: false});
+
+ GlobalActions.showShortcutsModal();
+ }
+
showAddUsersToTeamModal(e) {
e.preventDefault();
@@ -495,18 +503,18 @@ export default class SidebarHeaderDropdown extends React.Component {
);
}
- const keyboardShortcutsLink = (
+ const keyboardShortcuts = (
<li>
- <Link
- target='_blank'
- rel='noopener noreferrer'
- to='https://about.mattermost.com/default-keyboard_shortcut_link/'
+ <a
+ id='keyboardShortcuts'
+ href='#'
+ onClick={this.showShortcutsModal}
>
<FormattedMessage
id='navbar_dropdown.keyboardShortcuts'
defaultMessage='Keyboard Shortcuts'
/>
- </Link>
+ </a>
</li>
);
@@ -616,7 +624,7 @@ export default class SidebarHeaderDropdown extends React.Component {
{sysAdminLink}
{helpDivider}
{helpLink}
- {keyboardShortcutsLink}
+ {keyboardShortcuts}
{reportLink}
{nativeAppLink}
{about}
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 1bea0e0f5..00593e38d 100755
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -2105,6 +2105,54 @@
"setting_upload.import": "Import",
"setting_upload.noFile": "No file selected.",
"setting_upload.select": "Select file",
+ "shortcuts.info": "Begin a message with / for a list of all the commands at your disposal.",
+ "shortcuts.header": "Keyboard Shortcuts",
+ "shortcuts.nav.header": "Navigation",
+ "shortcuts.nav.prev": "Previous channel:\tAlt|Up",
+ "shortcuts.nav.prev.mac": "Previous channel:\t⌥|Up",
+ "shortcuts.nav.next": "Next channel:\tAlt|Down",
+ "shortcuts.nav.next.mac": "Next channel:\t⌥|Down",
+ "shortcuts.nav.unread_prev": "Previous unread channel:\tAlt|Shift|Up",
+ "shortcuts.nav.unread_prev.mac": "Previous unread channel:\t⌥|Shift|Up",
+ "shortcuts.nav.unread_next": "Next unread channel:\tAlt|Shift|Down",
+ "shortcuts.nav.unread_next.mac": "Next unread channel:\t⌥|Shift|Down",
+ "shortcuts.nav.switcher": "Quick channel switcher:\tCtrl|K",
+ "shortcuts.nav.switcher.mac": "Quick channel switcher:\t⌘|K",
+ "shortcuts.nav.direct_messages_menu": "Direct messages menu:\tCtrl|Shift|K",
+ "shortcuts.nav.direct_messages_menu.mac": "Direct messages menu:\t⌘|Shift|K",
+ "shortcuts.nav.settings": "Account settings:\tCtrl|Shift|A",
+ "shortcuts.nav.settings.mac": "Account settings:\t⌘|Shift|A",
+ "shortcuts.nav.recent_mentions": "Recent mentions:\tCtrl|Shift|M",
+ "shortcuts.nav.recent_mentions.mac": "Recent mentions:\t⌘|Shift|M",
+ "shortcuts.msgs.header": "Messages",
+ "shortcuts.msgs.mark_as_read": "Mark current channel as read:\tEsc",
+ "shortcuts.msgs.input.header": "Works inside an empty input field",
+ "shortcuts.msgs.edit": "Edit last message in channel:\tUp",
+ "shortcuts.msgs.reply": "Reply to last message in channel:\tShift|Up",
+ "shortcuts.msgs.reprint_prev": "Reprint previous message:\tCtrl|Up",
+ "shortcuts.msgs.reprint_prev.mac": "Reprint previous message:\t⌘|Up",
+ "shortcuts.msgs.reprint_next": "Reprint next message:\tCtrl|Down",
+ "shortcuts.msgs.reprint_next.mac": "Reprint next message:\t⌘|Down",
+ "shortcuts.msgs.comp.header": "Autocomplete",
+ "shortcuts.msgs.comp.username": "Username:\t@|[a-z]|Tab",
+ "shortcuts.msgs.comp.channel": "Channel:\t~|[a-z]|Tab",
+ "shortcuts.msgs.comp.emoji": "Emoji:\t:|[a-z]|Tab",
+ "shortcuts.files.header": "Files",
+ "shortcuts.files.upload": "Upload files:\tCtrl|U",
+ "shortcuts.files.upload.mac": "Upload files:\t⌘|U",
+ "shortcuts.browser.header": "Built-in Browser Commands",
+ "shortcuts.browser.channel_prev": "Back in history:\tAlt|Left",
+ "shortcuts.browser.channel_prev.mac": "Back in history:\t⌘|[",
+ "shortcuts.browser.channel_next": "Forward in history:\tAlt|Right",
+ "shortcuts.browser.channel_next.mac": "Forward in history:\t⌘|]",
+ "shortcuts.browser.font_increase": "Zoom in:\tCtrl|+",
+ "shortcuts.browser.font_increase.mac": "Zoom in:\t⌘|+",
+ "shortcuts.browser.font_decrease": "Zoom out:\tCtrl|-",
+ "shortcuts.browser.font_decrease.mac": "Zoom out:\t⌘|-",
+ "shortcuts.browser.input.header": "Works inside an input field",
+ "shortcuts.browser.highlight_prev": "Highlight text to the previous line:\tShift|Up",
+ "shortcuts.browser.highlight_next": "Highlight text to the next line:\tShift|Down",
+ "shortcuts.browser.newline": "Create a new line:\tShift|Enter",
"sidebar.channels": "PUBLIC CHANNELS",
"sidebar.createChannel": "Create new public channel",
"sidebar.createGroup": "Create new private channel",
diff --git a/webapp/sass/routes/_module.scss b/webapp/sass/routes/_module.scss
index c0a5b19bc..b7ecc08e7 100644
--- a/webapp/sass/routes/_module.scss
+++ b/webapp/sass/routes/_module.scss
@@ -11,5 +11,6 @@
@import 'loading';
@import 'print';
@import 'settings';
+@import 'shortcuts-modal';
@import 'signup';
@import 'statistics';
diff --git a/webapp/sass/routes/_shortcuts-modal.scss b/webapp/sass/routes/_shortcuts-modal.scss
new file mode 100644
index 000000000..817239f2f
--- /dev/null
+++ b/webapp/sass/routes/_shortcuts-modal.scss
@@ -0,0 +1,77 @@
+@charset 'UTF-8';
+
+.app__body {
+ .modal {
+ .shortcuts-modal {
+ margin-top: 50px;
+ width: 1100px;
+
+ .shortcuts-content {
+ .modal-header {
+ background: transparent;
+ border: none;
+ color: inherit;
+ padding: 40px 40px 20px;
+
+ .close {
+ color: inherit;
+ font-size: 28px;
+ font-weight: normal;
+ right: 35px;
+ }
+
+ .modal-title {
+ color: inherit;
+ font-size: 20px;
+ }
+ }
+ }
+
+ .modal-body {
+ max-height: calc(100vh - 67px);
+ padding: 0 40px 20px;
+ }
+
+ .section {
+ > div {
+ &:first-child {
+ margin-bottom: 2.5em;
+ }
+ }
+
+ .shortcut-line {
+ margin: 17px 0;
+
+ span {
+ &:first-child {
+ margin-right: 5px;
+ }
+ }
+
+ .shortcut-key {
+ border-radius: 3px;
+ font-size: 12px;
+ font-weight: 500;
+ margin: 5px 0 5px 5px;
+ padding: 1px 5px;
+ }
+ }
+ }
+
+ .section-title {
+ font-size: 18px;
+ margin: 1.5em 0;
+ }
+
+ .subsection {
+ border-left: 2px solid;
+ padding-left: 15px;
+ }
+
+ .info__label {
+ margin: 35px 0 10px;
+ text-align: center;
+ }
+ }
+ }
+}
diff --git a/webapp/stores/modal_store.jsx b/webapp/stores/modal_store.jsx
index 66be37b25..c47095d05 100644
--- a/webapp/stores/modal_store.jsx
+++ b/webapp/stores/modal_store.jsx
@@ -32,6 +32,7 @@ class ModalStoreClass extends EventEmitter {
switch (type) {
case ActionTypes.TOGGLE_ACCOUNT_SETTINGS_MODAL:
+ case ActionTypes.TOGGLE_SHORTCUTS_MODAL:
case ActionTypes.TOGGLE_IMPORT_THEME_MODAL:
case ActionTypes.TOGGLE_INVITE_MEMBER_MODAL:
case ActionTypes.TOGGLE_LEAVE_TEAM_MODAL:
diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx
index 8f7a72809..41ca17611 100644
--- a/webapp/utils/constants.jsx
+++ b/webapp/utils/constants.jsx
@@ -168,6 +168,7 @@ export const ActionTypes = keyMirror({
USER_TYPING: null,
TOGGLE_ACCOUNT_SETTINGS_MODAL: null,
+ TOGGLE_SHORTCUTS_MODAL: null,
TOGGLE_IMPORT_THEME_MODAL: null,
TOGGLE_INVITE_MEMBER_MODAL: null,
TOGGLE_LEAVE_TEAM_MODAL: null,
diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx
index 97f56fc4c..08205141e 100644
--- a/webapp/utils/utils.jsx
+++ b/webapp/utils/utils.jsx
@@ -603,7 +603,7 @@ export function applyTheme(theme) {
changeCss('.app__body .attachment__content', 'background:' + theme.centerChannelBg);
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 .shortcut-key, .app__body .post-list__new-messages-below', 'color:' + theme.centerChannelBg);
changeCss('.app__body .emoji-picker, .app__body .emoji-picker__search', 'background:' + theme.centerChannelBg);
changeCss('.app__body .nav-tabs, .app__body .nav-tabs > li.active > a', 'background:' + theme.centerChannelBg);
}
@@ -616,7 +616,7 @@ export function applyTheme(theme) {
changeCss('.app__body .channel-header__icon svg', 'fill:' + changeOpacity(theme.centerChannelColor, 0.4));
changeCss('.app__body .modal .status .offline--icon, .app__body .channel-header__links .icon, .app__body .sidebar--right .sidebar--right__subheader .usage__icon, .app__body .more-modal__header svg, .app__body .icon--body', 'fill:' + theme.centerChannelColor);
changeCss('@media(min-width: 768px){.app__body .post:hover .post__header .col__reply, .app__body .post.post--hovered .post__header .col__reply', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
- changeCss('.app__body .sidebar--right .sidebar--right__header, .app__body .channel-header, .app__body .nav-tabs > li > a:hover, .app__body .nav-tabs, .app__body .nav-tabs > li.active > a, .app__body .nav-tabs, .app__body .nav-tabs > li.active > a:focus, .app__body .nav-tabs, .app__body .nav-tabs > li.active > a:hover, .app__body .post .dropdown-menu a, .sidebar--left, .app__body .suggestion-list__content .command', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
+ changeCss('.app__body .modal .shortcuts-modal .subsection, .app__body .sidebar--right .sidebar--right__header, .app__body .channel-header, .app__body .nav-tabs > li > a:hover, .app__body .nav-tabs, .app__body .nav-tabs > li.active > a, .app__body .nav-tabs, .app__body .nav-tabs > li.active > a:focus, .app__body .nav-tabs, .app__body .nav-tabs > li.active > a:hover, .app__body .post .dropdown-menu a, .sidebar--left, .app__body .suggestion-list__content .command', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
changeCss('.app__body .post.post--system .post__body, .app__body .modal .channel-switch-modal .modal-header .close', 'color:' + changeOpacity(theme.centerChannelColor, 0.6));
changeCss('.app__body .nav-tabs, .app__body .nav-tabs > li.active > a, pp__body .input-group-addon, .app__body .app__content, .app__body .post-create__container .post-create-body .btn-file, .app__body .post-create__container .post-create-footer .msg-typing, .app__body .suggestion-list__content .command, .app__body .modal .modal-content, .app__body .dropdown-menu, .app__body .popover, .app__body .mentions__name, .app__body .tip-overlay, .app__body .form-control[disabled], .app__body .form-control[readonly], .app__body fieldset[disabled] .form-control', 'color:' + theme.centerChannelColor);
changeCss('.app__body .post .post__link', 'color:' + changeOpacity(theme.centerChannelColor, 0.65));
@@ -628,7 +628,7 @@ export function applyTheme(theme) {
changeCss('.app__body .dropdown-menu, .app__body .popover ', 'box-shadow: 0 17px 50px 0 ' + changeOpacity(theme.centerChannelColor, 0.1) + ', 0 12px 15px 0 ' + changeOpacity(theme.centerChannelColor, 0.1));
changeCss('.app__body .dropdown-menu, .app__body .popover ', '-moz-box-shadow: 0 17px 50px 0 ' + changeOpacity(theme.centerChannelColor, 0.1) + ', 0 12px 15px 0 ' + changeOpacity(theme.centerChannelColor, 0.1));
changeCss('.app__body .dropdown-menu, .app__body .popover ', '-webkit-box-shadow: 0 17px 50px 0 ' + changeOpacity(theme.centerChannelColor, 0.1) + ', 0 12px 15px 0 ' + changeOpacity(theme.centerChannelColor, 0.1));
- changeCss('.app__body .post__body hr, .app__body .loading-screen .loading__content .round, .app__body .tutorial__circles .circle', 'background:' + theme.centerChannelColor);
+ changeCss('.app__body .shortcut-key, .app__body .post__body hr, .app__body .loading-screen .loading__content .round, .app__body .tutorial__circles .circle', 'background:' + theme.centerChannelColor);
changeCss('.app__body .channel-header .heading', 'color:' + theme.centerChannelColor);
changeCss('.app__body .markdown__table tbody tr:nth-child(2n)', 'background:' + changeOpacity(theme.centerChannelColor, 0.07));
changeCss('.app__body .channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8));