diff --git a/components/__snapshots__/shortcuts_modal.test.jsx.snap b/components/__snapshots__/shortcuts_modal.test.jsx.snap deleted file mode 100644 index 2b05dcbede4e..000000000000 --- a/components/__snapshots__/shortcuts_modal.test.jsx.snap +++ /dev/null @@ -1,201 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`components/ShortcutsModal should match snapshot modal for Mac 1`] = ` - - - - - -`; - -exports[`components/ShortcutsModal should match snapshot modal for non-Mac like Windows/Linux 1`] = ` - - - - - -`; diff --git a/components/channel_header/components/__snapshots__/header_icon_wrapper.test.tsx.snap b/components/channel_header/components/__snapshots__/header_icon_wrapper.test.tsx.snap index 68a9c0ea3ad1..c7c2198ca9a2 100644 --- a/components/channel_header/components/__snapshots__/header_icon_wrapper.test.tsx.snap +++ b/components/channel_header/components/__snapshots__/header_icon_wrapper.test.tsx.snap @@ -93,6 +93,22 @@ exports[`components/channel_header/components/HeaderIconWrapper should match sna defaultMessage="Recent mentions" id="channel_header.recentMentions" /> + } placement="bottom" diff --git a/components/channel_header/components/header_icon_wrapper.tsx b/components/channel_header/components/header_icon_wrapper.tsx index 22ef34519f64..66464c663e46 100644 --- a/components/channel_header/components/header_icon_wrapper.tsx +++ b/components/channel_header/components/header_icon_wrapper.tsx @@ -10,6 +10,10 @@ import OverlayTrigger from 'components/overlay_trigger'; import {localizeMessage} from 'utils/utils.jsx'; import {Constants} from 'utils/constants'; import {t} from 'utils/i18n'; +import KeyboardShortcutSequence, { + KEYBOARD_SHORTCUTS, + KeyboardShortcutDescriptor, +} from 'components/keyboard_shortcuts/keyboard_shortcuts_sequence'; type Props = { ariaLabel?: boolean; @@ -27,6 +31,7 @@ type TooltipInfo = { id: string; messageID: string; message: string; + keyboardShortcut?: KeyboardShortcutDescriptor; } const HeaderIconWrapper: React.FC = (props: Props) => { @@ -59,6 +64,7 @@ const HeaderIconWrapper: React.FC = (props: Props) => { id: 'recentMentionsTooltip', messageID: t('channel_header.recentMentions'), message: 'Recent mentions', + keyboardShortcut: KEYBOARD_SHORTCUTS.navMentions, }, search: { class: '', @@ -88,6 +94,13 @@ const HeaderIconWrapper: React.FC = (props: Props) => { id={toolTips[key].messageID} defaultMessage={toolTips[key].message} /> + {toolTips[key].keyboardShortcut && + + } ); } diff --git a/components/channel_layout/channel_controller.jsx b/components/channel_layout/channel_controller.jsx index 7b6fd6732a5d..5cd4fe2d205d 100644 --- a/components/channel_layout/channel_controller.jsx +++ b/components/channel_layout/channel_controller.jsx @@ -14,18 +14,17 @@ import EditPostModal from 'components/edit_post_modal'; import GetPublicLinkModal from 'components/get_public_link_modal'; import LeavePrivateChannelModal from 'components/leave_private_channel_modal'; import ResetStatusModal from 'components/reset_status_modal'; -import ShortcutsModal from 'components/shortcuts_modal.jsx'; import SidebarRight from 'components/sidebar_right'; import SidebarRightMenu from 'components/sidebar_right_menu'; import ImportThemeModal from 'components/user_settings/import_theme_modal'; import TeamSidebar from 'components/team_sidebar'; import Sidebar from 'components/sidebar'; -import * as Utils from 'utils/utils'; import * as UserAgent from 'utils/user_agent'; import CenterChannel from 'components/channel_layout/center_channel'; import LoadingScreen from 'components/loading_screen'; import FaviconTitleHandler from 'components/favicon_title_handler'; import ProductNoticesModal from 'components/product_notices_modal'; +import KeyboardShortcutsModal from 'components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal.tsx'; export default class ChannelController extends React.PureComponent { static propTypes = { @@ -77,7 +76,7 @@ export default class ChannelController extends React.PureComponent { - + ); diff --git a/components/file_upload/__snapshots__/file_upload.test.jsx.snap b/components/file_upload/__snapshots__/file_upload.test.jsx.snap index de73c48a6024..2fd146538bdf 100644 --- a/components/file_upload/__snapshots__/file_upload.test.jsx.snap +++ b/components/file_upload/__snapshots__/file_upload.test.jsx.snap @@ -1,32 +1,63 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`components/FileUpload should match snapshot 1`] = ` -
-
- + - - +
- + `; diff --git a/components/file_upload/file_upload.jsx b/components/file_upload/file_upload.jsx index 03fb87337e4f..3665c1e33924 100644 --- a/components/file_upload/file_upload.jsx +++ b/components/file_upload/file_upload.jsx @@ -5,6 +5,7 @@ import PropTypes from 'prop-types'; import React, {PureComponent} from 'react'; import ReactDOM from 'react-dom'; import {defineMessages, FormattedMessage, injectIntl} from 'react-intl'; +import {Tooltip} from 'react-bootstrap'; import dragster from 'utils/dragster'; import Constants from 'utils/constants'; @@ -30,6 +31,8 @@ import MenuWrapper from 'components/widgets/menu/menu_wrapper'; import Menu from 'components/widgets/menu/menu'; import AttachmentIcon from 'components/widgets/icons/attachment_icon'; +import KeyboardShortcutSequence, {KEYBOARD_SHORTCUTS} from 'components/keyboard_shortcuts/keyboard_shortcuts_sequence'; +import OverlayTrigger from 'components/overlay_trigger'; const holders = defineMessages({ limited: { @@ -695,9 +698,24 @@ export class FileUpload extends PureComponent { } return ( -
- {bodyAction} -
+ + + + } + > +
+ {bodyAction} +
+
); } } diff --git a/components/global/at_mentions_button/at_mentions_button.tsx b/components/global/at_mentions_button/at_mentions_button.tsx index a531bcc608df..7a780496da84 100644 --- a/components/global/at_mentions_button/at_mentions_button.tsx +++ b/components/global/at_mentions_button/at_mentions_button.tsx @@ -5,7 +5,6 @@ import React from 'react'; import {Tooltip} from 'react-bootstrap'; import {FormattedMessage} from 'react-intl'; import {useDispatch, useSelector} from 'react-redux'; - import IconButton from '@mattermost/compass-components/components/icon-button'; import {closeRightHandSide, showMentions} from 'actions/views/rhs'; @@ -13,6 +12,7 @@ import OverlayTrigger from 'components/overlay_trigger'; import {getIsRhsOpen, getRhsState} from 'selectors/rhs'; import {GlobalState} from 'types/store'; import Constants, {RHSStates} from 'utils/constants'; +import KeyboardShortcutSequence, {KEYBOARD_SHORTCUTS} from 'components/keyboard_shortcuts/keyboard_shortcuts_sequence'; const AtMentionsButton = (): JSX.Element => { const dispatch = useDispatch(); @@ -34,6 +34,11 @@ const AtMentionsButton = (): JSX.Element => { id='channel_header.recentMentions' defaultMessage='Recent mentions' /> + ); diff --git a/components/global/history_buttons/history_buttons.tsx b/components/global/history_buttons/history_buttons.tsx index 40b8691cd612..89ab06eccf45 100644 --- a/components/global/history_buttons/history_buttons.tsx +++ b/components/global/history_buttons/history_buttons.tsx @@ -3,11 +3,18 @@ import React from 'react'; import styled from 'styled-components'; +import {Tooltip} from 'react-bootstrap'; import IconButton from '@mattermost/compass-components/components/icon-button'; import {trackEvent} from 'actions/telemetry_actions'; import * as Utils from 'utils/utils'; import {browserHistory} from 'utils/browser_history'; +import Constants from 'utils/constants'; +import KeyboardShortcutSequence, { + KEYBOARD_SHORTCUTS, + KeyboardShortcutDescriptor, +} from 'components/keyboard_shortcuts/keyboard_shortcuts_sequence'; +import OverlayTrigger from 'components/overlay_trigger'; const HistoryButtonsContainer = styled.nav` display: flex; @@ -19,6 +26,17 @@ const HistoryButtonsContainer = styled.nav` `; const HistoryButtons = (): JSX.Element => { + const getTooltip = (shortcut: KeyboardShortcutDescriptor) => ( + + + + ); const goBack = () => { trackEvent('ui', 'ui_history_back'); browserHistory.goBack(); @@ -31,22 +49,36 @@ const HistoryButtons = (): JSX.Element => { return ( - - + + + + + + ); }; diff --git a/components/keyboard_shortcuts/keyboard_shortcuts_modal/__snapshots__/keyboard_shortcuts_modal.test.tsx.snap b/components/keyboard_shortcuts/keyboard_shortcuts_modal/__snapshots__/keyboard_shortcuts_modal.test.tsx.snap new file mode 100644 index 000000000000..5e072d0119e1 --- /dev/null +++ b/components/keyboard_shortcuts/keyboard_shortcuts_modal/__snapshots__/keyboard_shortcuts_modal.test.tsx.snap @@ -0,0 +1,64 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/KeyboardShortcutsModal should match snapshot modal 1`] = ` + + + + + +`; diff --git a/sass/routes/_shortcuts-modal.scss b/components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal.scss similarity index 68% rename from sass/routes/_shortcuts-modal.scss rename to components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal.scss index ee82d6319e1f..c97060c99baa 100644 --- a/sass/routes/_shortcuts-modal.scss +++ b/components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal.scss @@ -54,16 +54,6 @@ margin-right: 5px; } } - - // TODO: It probably makes sense to switch to component - // in order to have universal shortcut key appearance - .shortcut-key { - padding: 4px 6px; - margin: 5px 0 5px 5px; - border-radius: 3px; - font-size: 12px; - font-weight: 500; - } } } @@ -84,26 +74,6 @@ } } -.shortcut-line { - margin: 16px 0; - - span { - &:first-child { - margin-right: 5px; - } - } - - // TODO: It probably makes sense to switch to component - // in order to have universal shortcut key appearance - .shortcut-key { - padding: 4px 6px; - margin: 5px 0 5px 5px; - border-radius: 3px; - font-size: 0.9em; - font-weight: 500; - } -} - @media (max-height: 800px) { .modal { .shortcuts-modal { @@ -116,8 +86,4 @@ } } } - - .shortcut-line { - margin-top: 14px; - } } diff --git a/components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal.test.tsx b/components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal.test.tsx new file mode 100644 index 000000000000..357cba868928 --- /dev/null +++ b/components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal.test.tsx @@ -0,0 +1,17 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import {mountWithIntl} from 'tests/helpers/intl-test-helper'; +import KeyboardShortcutsModal from 'components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal'; + +describe('components/KeyboardShortcutsModal', () => { + test('should match snapshot modal', () => { + const wrapper = mountWithIntl( + , + ); + + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal.tsx b/components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal.tsx new file mode 100644 index 000000000000..4f6761e9d35a --- /dev/null +++ b/components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal.tsx @@ -0,0 +1,169 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useEffect, useState} from 'react'; +import {Modal} from 'react-bootstrap'; +import {defineMessages, useIntl} from 'react-intl'; + +import ModalStore from 'stores/modal_store.jsx'; +import Constants from 'utils/constants'; +import {t} from 'utils/i18n'; +import * as Utils from 'utils/utils'; +import KeyboardShortcutSequence, { + KEYBOARD_SHORTCUTS, +} from 'components/keyboard_shortcuts/keyboard_shortcuts_sequence'; +import './keyboard_shortcuts_modal.scss'; + +const modalMessages = defineMessages({ + msgHeader: { + id: t('shortcuts.msgs.header'), + defaultMessage: 'Messages', + }, + msgInputHeader: { + id: t('shortcuts.msgs.input.header'), + defaultMessage: 'Works inside an empty input field', + }, + filesHeader: { + id: t('shortcuts.files.header'), + defaultMessage: 'Files', + }, + browserHeader: { + id: t('shortcuts.browser.header'), + defaultMessage: 'Built-in Browser Commands', + }, + msgCompHeader: { + id: t('shortcuts.msgs.comp.header'), + defaultMessage: 'Autocomplete', + }, + browserInputHeader: { + id: t('shortcuts.browser.input.header'), + defaultMessage: 'Works inside an input field', + }, + msgMarkdownHeader: { + id: t('shortcuts.msgs.markdown.header'), + defaultMessage: 'Formatting', + }, + info: { + id: t('shortcuts.info'), + defaultMessage: + 'Begin a message with / for a list of all the commands at your disposal.', + }, + navHeader: { + id: t('shortcuts.nav.header'), + defaultMessage: 'Navigation', + }, +}); + +const KeyboardShortcutsModal = (): JSX.Element => { + const [show, setShow] = useState(false); + + useEffect(() => { + //toggles the state of shortcut dialog + const handleToggle = (): void => setShow(!show); + ModalStore.addModalListener(Constants.ActionTypes.TOGGLE_SHORTCUTS_MODAL, handleToggle); + return () => { + ModalStore.removeModalListener(Constants.ActionTypes.TOGGLE_SHORTCUTS_MODAL, handleToggle); + }; + }, []); + + const handleHide = (): void => setShow(false); + const {formatMessage} = useIntl(); + const isLinux = Utils.isLinux(); + + return ( + +
+ + + + + + +
+
+
+
+

{formatMessage(modalMessages.navHeader)}

+ + + + + {!isLinux && } + {!isLinux && } + + + + + + + +
+
+
+
+
+
+

{formatMessage(modalMessages.msgHeader)}

+ {formatMessage(modalMessages.msgInputHeader)} +
+ + + + + +
+ {formatMessage(modalMessages.msgCompHeader)} +
+ + + +
+ {formatMessage(modalMessages.msgMarkdownHeader)} +
+ + + +
+
+
+
+
+
+
+

{formatMessage(modalMessages.filesHeader)}

+ +
+
+

{formatMessage(modalMessages.browserHeader)}

+ + + + + {formatMessage(modalMessages.browserInputHeader)} +
+ + + +
+
+
+
+
+
{formatMessage(modalMessages.info)}
+
+
+
+ ); +}; + +export default KeyboardShortcutsModal; diff --git a/components/keyboard_shortcuts/keyboard_shortcuts_sequence/__snapshots__/keyboard_shortcuts_sequence.test.tsx.snap b/components/keyboard_shortcuts/keyboard_shortcuts_sequence/__snapshots__/keyboard_shortcuts_sequence.test.tsx.snap new file mode 100644 index 000000000000..bb6161b8aaf6 --- /dev/null +++ b/components/keyboard_shortcuts/keyboard_shortcuts_sequence/__snapshots__/keyboard_shortcuts_sequence.test.tsx.snap @@ -0,0 +1,215 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/shortcuts/KeyboardShortcutsSequence should create sequence with order 1`] = ` + +
+ + + Ctrl + + + + + Alt + + + + + 3 + + +
+
+`; + +exports[`components/shortcuts/KeyboardShortcutsSequence should match snapshot when used for modal with description 1`] = ` + +
+ + Keyboard Shortcuts + + + + ⌘ + + + + + / + + +
+
+`; + +exports[`components/shortcuts/KeyboardShortcutsSequence should render sequence hoisting description 1`] = ` + + Keyboard Shortcuts +
+ + + ⌘ + + + + + / + + +
+
+`; + +exports[`components/shortcuts/KeyboardShortcutsSequence should render sequence without description 1`] = ` + +
+ + + ⌘ + + + + + / + + +
+
+`; + +exports[`components/shortcuts/KeyboardShortcutsSequence should render sequence without description 2`] = ` + +
+ + Keyboard Shortcuts + + + + ⌘ + + + + + / + + +
+
+`; diff --git a/components/keyboard_shortcuts/keyboard_shortcuts_sequence/index.ts b/components/keyboard_shortcuts/keyboard_shortcuts_sequence/index.ts new file mode 100644 index 000000000000..b7db22946cbd --- /dev/null +++ b/components/keyboard_shortcuts/keyboard_shortcuts_sequence/index.ts @@ -0,0 +1,7 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import KeyboardShortcutSequence from './keyboard_shortcuts_sequence'; + +export * from './keyboard_shortcuts'; +export default KeyboardShortcutSequence; diff --git a/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts.ts b/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts.ts new file mode 100644 index 000000000000..2015a413e28f --- /dev/null +++ b/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts.ts @@ -0,0 +1,307 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {MessageDescriptor} from 'react-intl'; + +import {t} from 'utils/i18n'; + +export type KeyboardShortcutDescriptor = MessageDescriptor | {default: MessageDescriptor; mac?: MessageDescriptor}; + +export function isMessageDescriptor(descriptor: KeyboardShortcutDescriptor): descriptor is MessageDescriptor { + return Boolean((descriptor as MessageDescriptor).id); +} + +export const KEYBOARD_SHORTCUTS = { + mainHeader: { + default: { + id: t('shortcuts.header'), + defaultMessage: 'Keyboard Shortcuts\tCtrl|/', + }, + mac: { + id: t('shortcuts.header.mac'), + defaultMessage: 'Keyboard Shortcuts\t⌘|/', + }, + }, + navPrev: { + default: { + id: t('shortcuts.nav.prev'), + defaultMessage: 'Previous channel:\tAlt|Up', + }, + mac: { + id: t('shortcuts.nav.prev.mac'), + defaultMessage: 'Previous channel:\t⌥|Up', + }, + }, + navNext: { + default: { + id: t('shortcuts.nav.next'), + defaultMessage: 'Next channel:\tAlt|Down', + }, + mac: { + id: t('shortcuts.nav.next.mac'), + defaultMessage: 'Next channel:\t⌥|Down', + }, + }, + navUnreadPrev: { + default: { + id: t('shortcuts.nav.unread_prev'), + defaultMessage: 'Previous unread channel:\tAlt|Shift|Up', + }, + mac: { + id: t('shortcuts.nav.unread_prev.mac'), + defaultMessage: 'Previous unread channel:\t⌥|Shift|Up', + }, + }, + navUnreadNext: { + default: { + id: t('shortcuts.nav.unread_next'), + defaultMessage: 'Next unread channel:\tAlt|Shift|Down', + }, + mac: { + id: t('shortcuts.nav.unread_next.mac'), + defaultMessage: 'Next unread channel:\t⌥|Shift|Down', + }, + }, + teamNavPrev: { + default: { + id: t('shortcuts.team_nav.prev'), + defaultMessage: 'Previous team:\tCtrl|Alt|Up', + }, + mac: { + id: t('shortcuts.team_nav.prev.mac'), + defaultMessage: 'Previous team:\t⌘|⌥|Up', + }, + }, + teamNavNext: { + default: { + id: t('shortcuts.team_nav.next'), + defaultMessage: 'Next team:\tCtrl|Alt|Down', + }, + mac: { + id: t('shortcuts.team_nav.next.mac'), + defaultMessage: 'Next team:\t⌘|⌥|Down', + }, + }, + teamNavSwitcher: { + default: { + id: t('shortcuts.team_nav.switcher'), + defaultMessage: 'Switch to a specific team:\tCtrl|Alt|[1-9]', + }, + mac: { + id: t('shortcuts.team_nav.switcher.mac'), + defaultMessage: 'Switch to a specific team:\t⌘|⌥|[1-9]', + }, + }, + teamNavigation: { + default: { + id: t('team.button.tooltip'), + defaultMessage: 'Ctrl|Alt|{order}', + }, + mac: { + id: t('team.button.tooltip.mac'), + defaultMessage: '⌘|⌥|{order}', + }, + }, + navSwitcher: { + default: { + id: t('shortcuts.nav.switcher'), + defaultMessage: 'Quick channel switcher:\tCtrl|K', + }, + mac: { + id: t('shortcuts.nav.switcher.mac'), + defaultMessage: 'Quick channel switcher:\t⌘|K', + }, + }, + navDMMenu: { + default: { + id: t('shortcuts.nav.direct_messages_menu'), + defaultMessage: 'Direct messages menu:\tCtrl|Shift|K', + }, + mac: { + id: t('shortcuts.nav.direct_messages_menu.mac'), + defaultMessage: 'Direct messages menu:\t⌘|Shift|K', + }, + }, + navSettings: { + default: { + id: t('shortcuts.nav.settings'), + defaultMessage: 'Account settings:\tCtrl|Shift|A', + }, + mac: { + id: t('shortcuts.nav.settings.mac'), + defaultMessage: 'Account settings:\t⌘|Shift|A', + }, + }, + navMentions: { + default: { + id: t('shortcuts.nav.recent_mentions'), + defaultMessage: 'Recent mentions:\tCtrl|Shift|M', + }, + mac: { + id: t('shortcuts.nav.recent_mentions.mac'), + defaultMessage: 'Recent mentions:\t⌘|Shift|M', + }, + }, + navFocusCenter: { + default: { + id: t('shortcuts.nav.focus_center'), + defaultMessage: 'Set focus to input field:\tCtrl|Shift|L', + }, + mac: { + id: t('shortcuts.nav.focus_center.mac'), + defaultMessage: 'Set focus to input field:\t⌘|Shift|L', + }, + }, + navOpenCloseSidebar: { + default: { + id: t('shortcuts.nav.open_close_sidebar'), + defaultMessage: 'Open or close the right sidebar\tCtrl|.', + }, + mac: { + id: t('shortcuts.nav.open_close_sidebar.mac'), + defaultMessage: 'Open or close the right sidebar\t⌘|.', + }, + }, + msgEdit: { + id: t('shortcuts.msgs.edit'), + defaultMessage: 'Edit last message in channel:\tUp', + }, + msgReply: { + id: t('shortcuts.msgs.reply'), + defaultMessage: 'Reply to last message in channel:\tShift|Up', + }, + msgReprintPrev: { + default: { + id: t('shortcuts.msgs.reprint_prev'), + defaultMessage: 'Reprint previous message:\tCtrl|Up', + }, + mac: { + id: t('shortcuts.msgs.reprint_prev.mac'), + defaultMessage: 'Reprint previous message:\t⌘|Up', + }, + }, + msgReprintNext: { + default: { + id: t('shortcuts.msgs.reprint_next'), + defaultMessage: 'Reprint next message:\tCtrl|Down', + }, + mac: { + id: t('shortcuts.msgs.reprint_next.mac'), + defaultMessage: 'Reprint next message:\t⌘|Down', + }, + }, + msgCompUsername: { + id: t('shortcuts.msgs.comp.username'), + defaultMessage: 'Username:\t@|[a-z]|Tab', + }, + msgCompChannel: { + id: t('shortcuts.msgs.comp.channel'), + defaultMessage: 'Channel:\t~|[a-z]|Tab', + }, + msgCompEmoji: { + id: t('shortcuts.msgs.comp.emoji'), + defaultMessage: 'Emoji:\t:|[a-z]|Tab', + }, + msgLastReaction: { + default: { + id: t('shortcuts.msgs.comp.last_reaction'), + defaultMessage: 'React to last message:\tCtrl|Shift|\u29F5', + }, + mac: { + id: t('shortcuts.msgs.comp.last_reaction.mac'), + defaultMessage: 'React to last message:\t⌘|Shift|\u29F5', + }, + }, + msgMarkdownBold: { + default: { + id: t('shortcuts.msgs.markdown.bold'), + defaultMessage: 'Bold:\tCtrl|B', + }, + mac: { + id: t('shortcuts.msgs.markdown.bold.mac'), + defaultMessage: 'Bold:\t⌘|B', + }, + }, + msgMarkdownItalic: { + default: { + id: t('shortcuts.msgs.markdown.italic'), + defaultMessage: 'Italic:\tCtrl|I', + }, + mac: { + id: t('shortcuts.msgs.markdown.italic.mac'), + defaultMessage: 'Italic:\t⌘|I', + }, + }, + msgMarkdownLink: { + default: { + id: t('shortcuts.msgs.markdown.link'), + defaultMessage: 'Link:\tCtrl|Alt|K', + }, + mac: { + id: t('shortcuts.msgs.markdown.link.mac'), + defaultMessage: 'Link:\t⌘|Alt|K', + }, + }, + filesUpload: { + default: { + id: t('shortcuts.files.upload'), + defaultMessage: 'Upload files:\tCtrl|U', + }, + mac: { + id: t('shortcuts.files.upload.mac'), + defaultMessage: 'Upload files:\t⌘|U', + }, + }, + browserChannelPrev: { + default: { + id: t('shortcuts.browser.channel_prev'), + defaultMessage: 'Back in history:\tAlt|Left', + }, + mac: { + id: t('shortcuts.browser.channel_prev.mac'), + defaultMessage: 'Back in history:\t⌘|[', + }, + }, + browserChannelNext: { + default: { + id: t('shortcuts.browser.channel_next'), + defaultMessage: 'Forward in history:\tAlt|Right', + }, + mac: { + id: t('shortcuts.browser.channel_next.mac'), + defaultMessage: 'Forward in history:\t⌘|]', + }, + }, + browserFontIncrease: { + default: { + id: t('shortcuts.browser.font_increase'), + defaultMessage: 'Zoom in:\tCtrl|+', + }, + mac: { + id: t('shortcuts.browser.font_increase.mac'), + defaultMessage: 'Zoom in:\t⌘|+', + }, + }, + browserFontDecrease: { + default: { + id: t('shortcuts.browser.font_decrease'), + defaultMessage: 'Zoom out:\tCtrl|-', + }, + mac: { + id: t('shortcuts.browser.font_decrease.mac'), + defaultMessage: 'Zoom out:\t⌘|-', + }, + }, + browserHighlightPrev: { + id: t('shortcuts.browser.highlight_prev'), + defaultMessage: 'Highlight text to the previous line:\tShift|Up', + }, + browserHighlightNext: { + id: t('shortcuts.browser.highlight_next'), + defaultMessage: 'Highlight text to the next line:\tShift|Down', + }, + browserNewline: { + id: t('shortcuts.browser.newline'), + defaultMessage: 'Create a new line:\tShift|Enter', + }, +}; diff --git a/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts_sequence.scss b/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts_sequence.scss new file mode 100644 index 000000000000..e3532fe3999c --- /dev/null +++ b/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts_sequence.scss @@ -0,0 +1,30 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +.shortcut-line { + margin: 16px 0; + + span { + &:first-child { + margin-right: 5px; + } + } + + .shortcut-key { + padding: 2px 5px; + + + .shortcut-key { + margin-left: 2px; + } + } +} + +.tooltip-inner .shortcut-line { + margin: 4px 0; +} + +@media (max-height: 800px) { + .shortcut-line { + margin-top: 14px; + } +} diff --git a/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts_sequence.test.tsx b/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts_sequence.test.tsx new file mode 100644 index 000000000000..79d841d2cdd9 --- /dev/null +++ b/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts_sequence.test.tsx @@ -0,0 +1,98 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import {mountWithIntl} from 'tests/helpers/intl-test-helper'; + +import KeyboardShortcutsSequence from './keyboard_shortcuts_sequence'; + +import KeyboardShortcutSequence, {KEYBOARD_SHORTCUTS} from './index'; + +describe('components/shortcuts/KeyboardShortcutsSequence', () => { + test('should match snapshot when used for modal with description', () => { + const wrapper = mountWithIntl( + , + ); + + const tag = {'Keyboard Shortcuts'}; + expect(wrapper.contains(tag)).toEqual(true); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('.shortcut-key--tooltip')).toHaveLength(0); + expect(wrapper.find('.shortcut-key--shortcut-modal')).toHaveLength(2); + }); + + test('should render sequence without description', () => { + const wrapper = mountWithIntl( + , + ); + + const tag = {'Keyboard Shortcuts'}; + expect(wrapper.contains(tag)).toEqual(false); + expect(wrapper).toMatchSnapshot(); + }); + + test('should render sequence without description', () => { + const wrapper = mountWithIntl( + , + ); + + const tag = {'Keyboard Shortcuts'}; + expect(wrapper.contains(tag)).toEqual(true); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('.shortcut-key--tooltip')).toHaveLength(2); + expect(wrapper.find('.shortcut-key--shortcut-modal')).toHaveLength(0); + }); + test('should render sequence hoisting description', () => { + const wrapper = mountWithIntl( + , + ); + + const tag = {'Keyboard Shortcuts'}; + expect(wrapper.contains(tag)).toEqual(false); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('.shortcut-key--tooltip')).toHaveLength(2); + expect(wrapper.find('.shortcut-key--shortcut-modal')).toHaveLength(0); + }); + + test('should create sequence with order', () => { + const order = 3; + const wrapper = mountWithIntl( + , + ); + expect(wrapper).toMatchSnapshot(); + const tag = {'Keyboard Shortcuts'}; + expect(wrapper.contains(tag)).toEqual(false); + expect(wrapper.find('.shortcut-key--tooltip')).toHaveLength(3); + expect(wrapper.find('.shortcut-key--shortcut-modal')).toHaveLength(0); + }); +}); diff --git a/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts_sequence.tsx b/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts_sequence.tsx new file mode 100644 index 000000000000..5a0e27b289c8 --- /dev/null +++ b/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts_sequence.tsx @@ -0,0 +1,67 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import {FormatXMLElementFn, PrimitiveType} from 'intl-messageformat'; +import React, {memo} from 'react'; +import {useIntl} from 'react-intl'; + +import {ShortcutKeyVariant, ShortcutKey} from 'components/shortcut_key'; +import {isMac} from 'utils/utils'; + +import {isMessageDescriptor, KeyboardShortcutDescriptor} from './keyboard_shortcuts'; + +import './keyboard_shortcuts_sequence.scss'; + +type Props = { + shortcut: KeyboardShortcutDescriptor; + values?: Record>; + hideDescription?: boolean; + hoistDescription?: boolean; + isInsideTooltip?: boolean; +}; + +function normalizeShortcutDescriptor(shortcut: KeyboardShortcutDescriptor) { + if (isMessageDescriptor(shortcut)) { + return shortcut; + } + const {default: standard, mac} = shortcut; + return isMac() && mac ? mac : standard; +} + +const KEY_SEPARATOR = '|'; + +function KeyboardShortcutSequence({shortcut, values, hideDescription, hoistDescription, isInsideTooltip}: Props) { + const {formatMessage} = useIntl(); + const shortcutText = formatMessage(normalizeShortcutDescriptor(shortcut), values); + const splitShortcut = shortcutText.split('\t'); + + let description = ''; + let keys = ''; + + if (splitShortcut.length > 1) { + description = splitShortcut[0]; + keys = splitShortcut[1]; + } else if (splitShortcut[0].includes(KEY_SEPARATOR)) { + keys = splitShortcut[0]; + } else { + description = splitShortcut[0]; + } + + return ( + <> + {hoistDescription && !hideDescription && description?.replace(/:{1,2}$/, '')} +
+ {!hoistDescription && !hideDescription && description && {description}} + {keys?.split(KEY_SEPARATOR).map((key) => ( + + {key} + + ))} +
+ + ); +} + +export default memo(KeyboardShortcutSequence); diff --git a/components/search_shortcut/search_shortcut.tsx b/components/search_shortcut/search_shortcut.tsx index 31fdb3779709..a7a2782bcca9 100644 --- a/components/search_shortcut/search_shortcut.tsx +++ b/components/search_shortcut/search_shortcut.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import React from 'react'; -import {ShortcutKey, ShortcutKetVariant} from 'components/shortcut_key'; +import {ShortcutKey, ShortcutKeyVariant} from 'components/shortcut_key'; import {isMac} from 'utils/utils.jsx'; import {isDesktopApp} from 'utils/user_agent'; @@ -13,9 +13,9 @@ export const SearchShortcut = () => { return ( - {controlKey} - {!isDesktopApp() && {'Shift'}} - {'F'} + {controlKey} + {!isDesktopApp() && {'Shift'}} + {'F'} ); }; diff --git a/components/shortcut_key/index.ts b/components/shortcut_key/index.ts index ddf42fff53b9..1b9505b2babf 100644 --- a/components/shortcut_key/index.ts +++ b/components/shortcut_key/index.ts @@ -1,5 +1,4 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {ShortcutKey, ShortcutKetVariant} from './shortcut_key'; -export {ShortcutKey, ShortcutKetVariant}; +export * from './shortcut_key'; diff --git a/components/shortcut_key/shortcut_key.scss b/components/shortcut_key/shortcut_key.scss index 5499770b3bd4..f029bb35c299 100644 --- a/components/shortcut_key/shortcut_key.scss +++ b/components/shortcut_key/shortcut_key.scss @@ -12,5 +12,22 @@ background-color: rgba(var(--center-channel-color-rgb), 0.08); color: var(--center-channel-color); } + + &.shortcut-key--tooltip { + padding: 2px 5px; + background-color: rgba(255, 255, 255, 0.08); + color: rgba(255, 255, 255, 0.72); + font-family: inherit; + font-size: 12px; + font-weight: 600; + line-height: 16px; + } + + &.shortcut-key--shortcut-modal { + font-family: inherit; + font-size: 12px; + font-weight: 600; + line-height: 16px; + } } } diff --git a/components/shortcut_key/shortcut_key.test.tsx b/components/shortcut_key/shortcut_key.test.tsx index eb0c37a0284e..c6149b52f49c 100644 --- a/components/shortcut_key/shortcut_key.test.tsx +++ b/components/shortcut_key/shortcut_key.test.tsx @@ -3,7 +3,7 @@ import React from 'react'; import {shallow} from 'enzyme'; -import {ShortcutKey, ShortcutKetVariant} from './shortcut_key'; +import {ShortcutKey, ShortcutKeyVariant} from './shortcut_key'; describe('components/ShortcutKey', () => { test('should match snapshot for regular key', () => { @@ -12,7 +12,7 @@ describe('components/ShortcutKey', () => { }); test('should match snapshot for contrast key', () => { - const wrapper = shallow({'Shift'}); + const wrapper = shallow({'Shift'}); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/components/shortcut_key/shortcut_key.tsx b/components/shortcut_key/shortcut_key.tsx index 3117066316e4..5bd1eaa5f452 100644 --- a/components/shortcut_key/shortcut_key.tsx +++ b/components/shortcut_key/shortcut_key.tsx @@ -1,26 +1,30 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React from 'react'; +import classNames from 'classnames'; import './shortcut_key.scss'; -export enum ShortcutKetVariant { +export enum ShortcutKeyVariant { Contrast = 'contrast', + Tooltip = 'tooltip', + ShortcutModal = 'shortcut', } export type ShortcutKeyProps = { - variant?: ShortcutKetVariant; + variant?: ShortcutKeyVariant; children: React.ReactNode; } -export const ShortcutKey = ({children, variant}: ShortcutKeyProps) => { - let className = 'shortcut-key'; - if (variant === ShortcutKetVariant.Contrast) { - className += ' shortcut-key--contrast'; - } - +export const ShortcutKey = ({children, variant}: ShortcutKeyProps): JSX.Element => { return ( - + {children} ); diff --git a/components/shortcuts_modal.jsx b/components/shortcuts_modal.jsx deleted file mode 100644 index 7655b37c7511..000000000000 --- a/components/shortcuts_modal.jsx +++ /dev/null @@ -1,516 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import PropTypes from 'prop-types'; -import React from 'react'; -import {Modal} from 'react-bootstrap'; -import {defineMessages, injectIntl} from 'react-intl'; - -import ModalStore from 'stores/modal_store.jsx'; -import Constants from 'utils/constants'; -import {intlShape} from 'utils/react_intl'; -import {t} from 'utils/i18n'; -import * as Utils from 'utils/utils'; - -const allShortcuts = defineMessages({ - mainHeader: { - default: { - id: t('shortcuts.header'), - defaultMessage: 'Keyboard Shortcuts\tCtrl|/', - }, - mac: { - id: t('shortcuts.header.mac'), - defaultMessage: 'Keyboard Shortcuts\t⌘|/', - }, - }, - navHeader: { - id: t('shortcuts.nav.header'), - defaultMessage: 'Navigation', - }, - navPrev: { - default: { - id: t('shortcuts.nav.prev'), - defaultMessage: 'Previous channel:\tAlt|Up', - }, - mac: { - id: t('shortcuts.nav.prev.mac'), - defaultMessage: 'Previous channel:\t⌥|Up', - }, - }, - navNext: { - default: { - id: t('shortcuts.nav.next'), - defaultMessage: 'Next channel:\tAlt|Down', - }, - mac: { - id: t('shortcuts.nav.next.mac'), - defaultMessage: 'Next channel:\t⌥|Down', - }, - }, - navUnreadPrev: { - default: { - id: t('shortcuts.nav.unread_prev'), - defaultMessage: 'Previous unread channel:\tAlt|Shift|Up', - }, - mac: { - id: t('shortcuts.nav.unread_prev.mac'), - defaultMessage: 'Previous unread channel:\t⌥|Shift|Up', - }, - }, - navUnreadNext: { - default: { - id: t('shortcuts.nav.unread_next'), - defaultMessage: 'Next unread channel:\tAlt|Shift|Down', - }, - mac: { - id: t('shortcuts.nav.unread_next.mac'), - defaultMessage: 'Next unread channel:\t⌥|Shift|Down', - }, - }, - teamNavPrev: { - default: { - id: t('shortcuts.team_nav.prev'), - defaultMessage: 'Previous team:\tCtrl|Alt|Up', - }, - mac: { - id: t('shortcuts.team_nav.prev.mac'), - defaultMessage: 'Previous team:\t⌘|⌥|Up', - }, - }, - teamNavNext: { - default: { - id: t('shortcuts.team_nav.next'), - defaultMessage: 'Next team:\tCtrl|Alt|Down', - }, - mac: { - id: t('shortcuts.team_nav.next.mac'), - defaultMessage: 'Next team:\t⌘|⌥|Down', - }, - }, - teamNavSwitcher: { - default: { - id: t('shortcuts.team_nav.switcher'), - defaultMessage: 'Switch to a specific team:\tCtrl|Alt|[1-9]', - }, - mac: { - id: t('shortcuts.team_nav.switcher.mac'), - defaultMessage: 'Switch to a specific team:\t⌘|⌥|[1-9]', - }, - }, - navSwitcher: { - default: { - id: t('shortcuts.nav.switcher'), - defaultMessage: 'Quick channel switcher:\tCtrl|K', - }, - mac: { - id: t('shortcuts.nav.switcher.mac'), - defaultMessage: 'Quick channel switcher:\t⌘|K', - }, - }, - navDMMenu: { - default: { - id: t('shortcuts.nav.direct_messages_menu'), - defaultMessage: 'Direct messages menu:\tCtrl|Shift|K', - }, - mac: { - id: t('shortcuts.nav.direct_messages_menu.mac'), - defaultMessage: 'Direct messages menu:\t⌘|Shift|K', - }, - }, - navSettings: { - default: { - id: t('shortcuts.nav.settings'), - defaultMessage: 'Account settings:\tCtrl|Shift|A', - }, - mac: { - id: t('shortcuts.nav.settings.mac'), - defaultMessage: 'Account settings:\t⌘|Shift|A', - }, - }, - navMentions: { - default: { - id: t('shortcuts.nav.recent_mentions'), - defaultMessage: 'Recent mentions:\tCtrl|Shift|M', - }, - mac: { - id: t('shortcuts.nav.recent_mentions.mac'), - defaultMessage: 'Recent mentions:\t⌘|Shift|M', - }, - }, - navFocusCenter: { - default: { - id: t('shortcuts.nav.focus_center'), - defaultMessage: 'Set focus to input field:\tCtrl|Shift|L', - }, - mac: { - id: t('shortcuts.nav.focus_center.mac'), - defaultMessage: 'Set focus to input field:\t⌘|Shift|L', - }, - }, - navOpenCloseSidebar: { - default: { - id: t('shortcuts.nav.open_close_sidebar'), - defaultMessage: 'Open or close the right sidebar\tCtrl|.', - }, - mac: { - id: t('shortcuts.nav.open_close_sidebar.mac'), - defaultMessage: 'Open or close the right sidebar\t⌘|.', - }, - }, - msgHeader: { - id: t('shortcuts.msgs.header'), - defaultMessage: 'Messages', - }, - msgInputHeader: { - id: t('shortcuts.msgs.input.header'), - defaultMessage: 'Works inside an empty input field', - }, - msgEdit: { - id: t('shortcuts.msgs.edit'), - defaultMessage: 'Edit last message in channel:\tUp', - }, - msgReply: { - id: t('shortcuts.msgs.reply'), - defaultMessage: 'Reply to last message in channel:\tShift|Up', - }, - msgReprintPrev: { - default: { - id: t('shortcuts.msgs.reprint_prev'), - defaultMessage: 'Reprint previous message:\tCtrl|Up', - }, - mac: { - id: t('shortcuts.msgs.reprint_prev.mac'), - defaultMessage: 'Reprint previous message:\t⌘|Up', - }, - }, - msgReprintNext: { - default: { - id: t('shortcuts.msgs.reprint_next'), - defaultMessage: 'Reprint next message:\tCtrl|Down', - }, - mac: { - id: t('shortcuts.msgs.reprint_next.mac'), - defaultMessage: 'Reprint next message:\t⌘|Down', - }, - }, - msgCompHeader: { - id: t('shortcuts.msgs.comp.header'), - defaultMessage: 'Autocomplete', - }, - msgCompUsername: { - id: t('shortcuts.msgs.comp.username'), - defaultMessage: 'Username:\t@|[a-z]|Tab', - }, - msgCompChannel: { - id: t('shortcuts.msgs.comp.channel'), - defaultMessage: 'Channel:\t~|[a-z]|Tab', - }, - msgCompEmoji: { - id: t('shortcuts.msgs.comp.emoji'), - defaultMessage: 'Emoji:\t:|[a-z]|Tab', - }, - msgLastReaction: { - default: { - id: t('shortcuts.msgs.comp.last_reaction'), - defaultMessage: 'React to last message:\tCtrl|Shift|\u29F5', - }, - mac: { - id: t('shortcuts.msgs.comp.last_reaction.mac'), - defaultMessage: 'React to last message:\t⌘|Shift|\u29F5', - }, - }, - msgMarkdownHeader: { - id: t('shortcuts.msgs.markdown.header'), - defaultMessage: 'Formatting', - }, - msgMarkdownBold: { - default: { - id: t('shortcuts.msgs.markdown.bold'), - defaultMessage: 'Bold:\tCtrl|B', - }, - mac: { - id: t('shortcuts.msgs.markdown.bold.mac'), - defaultMessage: 'Bold:\t⌘|B', - }, - }, - msgMarkdownItalic: { - default: { - id: t('shortcuts.msgs.markdown.italic'), - defaultMessage: 'Italic:\tCtrl|I', - }, - mac: { - id: t('shortcuts.msgs.markdown.italic.mac'), - defaultMessage: 'Italic:\t⌘|I', - }, - }, - msgMarkdownLink: { - default: { - id: t('shortcuts.msgs.markdown.link'), - defaultMessage: 'Link:\tCtrl|Alt|K', - }, - mac: { - id: t('shortcuts.msgs.markdown.link.mac'), - defaultMessage: 'Link:\t⌘|Alt|K', - }, - }, - filesHeader: { - id: t('shortcuts.files.header'), - defaultMessage: 'Files', - }, - filesUpload: { - default: { - id: t('shortcuts.files.upload'), - defaultMessage: 'Upload files:\tCtrl|U', - }, - mac: { - id: t('shortcuts.files.upload.mac'), - defaultMessage: 'Upload files:\t⌘|U', - }, - }, - browserHeader: { - id: t('shortcuts.browser.header'), - defaultMessage: 'Built-in Browser Commands', - }, - browserChannelPrev: { - default: { - id: t('shortcuts.browser.channel_prev'), - defaultMessage: 'Back in history:\tAlt|Left', - }, - mac: { - id: t('shortcuts.browser.channel_prev.mac'), - defaultMessage: 'Back in history:\t⌘|[', - }, - }, - browserChannelNext: { - default: { - id: t('shortcuts.browser.channel_next'), - defaultMessage: 'Forward in history:\tAlt|Right', - }, - mac: { - id: t('shortcuts.browser.channel_next.mac'), - defaultMessage: 'Forward in history:\t⌘|]', - }, - }, - browserFontIncrease: { - default: { - id: t('shortcuts.browser.font_increase'), - defaultMessage: 'Zoom in:\tCtrl|+', - }, - mac: { - id: t('shortcuts.browser.font_increase.mac'), - defaultMessage: 'Zoom in:\t⌘|+', - }, - }, - browserFontDecrease: { - default: { - id: t('shortcuts.browser.font_decrease'), - defaultMessage: 'Zoom out:\tCtrl|-', - }, - mac: { - id: t('shortcuts.browser.font_decrease.mac'), - defaultMessage: 'Zoom out:\t⌘|-', - }, - }, - browserInputHeader: { - id: t('shortcuts.browser.input.header'), - defaultMessage: 'Works inside an input field', - }, - browserHighlightPrev: { - id: t('shortcuts.browser.highlight_prev'), - defaultMessage: 'Highlight text to the previous line:\tShift|Up', - }, - browserHighlightNext: { - id: t('shortcuts.browser.highlight_next'), - defaultMessage: 'Highlight text to the next line:\tShift|Down', - }, - browserNewline: { - id: t('shortcuts.browser.newline'), - defaultMessage: 'Create a new line:\tShift|Enter', - }, - info: { - id: t('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, - isMac: PropTypes.bool.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 = () => { - //toggles the state of shortcut dialog - this.setState({ - show: !this.state.show, - }); - } - - handleHide = () => { - this.setState({show: false}); - } - - getShortcuts() { - const {isMac} = this.props; - 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(); - const {formatMessage} = this.props.intl; - - const isLinux = Utils.isLinux(); - - return ( - -
- - - {renderShortcut(formatMessage(shortcuts.mainHeader))} - - - -
-
-
-
-

{formatMessage(shortcuts.navHeader)}

- {renderShortcut(formatMessage(shortcuts.navPrev))} - {renderShortcut(formatMessage(shortcuts.navNext))} - {renderShortcut(formatMessage(shortcuts.navUnreadPrev))} - {renderShortcut(formatMessage(shortcuts.navUnreadNext))} - {!isLinux && renderShortcut(formatMessage(shortcuts.teamNavPrev))} - {!isLinux && renderShortcut(formatMessage(shortcuts.teamNavNext))} - {renderShortcut(formatMessage(shortcuts.teamNavSwitcher))} - {renderShortcut(formatMessage(shortcuts.navSwitcher))} - {renderShortcut(formatMessage(shortcuts.navDMMenu))} - {renderShortcut(formatMessage(shortcuts.navSettings))} - {renderShortcut(formatMessage(shortcuts.navMentions))} - {renderShortcut(formatMessage(shortcuts.navFocusCenter))} - {renderShortcut(formatMessage(shortcuts.navOpenCloseSidebar))} -
-
-
-
-
-
-

{formatMessage(shortcuts.msgHeader)}

- {formatMessage(shortcuts.msgInputHeader)} -
- {renderShortcut(formatMessage(shortcuts.msgEdit))} - {renderShortcut(formatMessage(shortcuts.msgReply))} - {renderShortcut(formatMessage(shortcuts.msgLastReaction))} - {renderShortcut(formatMessage(shortcuts.msgReprintPrev))} - {renderShortcut(formatMessage(shortcuts.msgReprintNext))} -
- {formatMessage(shortcuts.msgCompHeader)} -
- {renderShortcut(formatMessage(shortcuts.msgCompUsername))} - {renderShortcut(formatMessage(shortcuts.msgCompChannel))} - {renderShortcut(formatMessage(shortcuts.msgCompEmoji))} -
- {formatMessage(shortcuts.msgMarkdownHeader)} -
- {renderShortcut(formatMessage(shortcuts.msgMarkdownBold))} - {renderShortcut(formatMessage(shortcuts.msgMarkdownItalic))} - {renderShortcut(formatMessage(shortcuts.msgMarkdownLink))} -
-
-
-
-
-
-
-

{formatMessage(shortcuts.filesHeader)}

- {renderShortcut(formatMessage(shortcuts.filesUpload))} -
-
-

{formatMessage(shortcuts.browserHeader)}

- {renderShortcut(formatMessage(shortcuts.browserChannelPrev))} - {renderShortcut(formatMessage(shortcuts.browserChannelNext))} - {renderShortcut(formatMessage(shortcuts.browserFontIncrease))} - {renderShortcut(formatMessage(shortcuts.browserFontDecrease))} - {formatMessage(shortcuts.browserInputHeader)} -
- {renderShortcut(formatMessage(shortcuts.browserHighlightPrev))} - {renderShortcut(formatMessage(shortcuts.browserHighlightNext))} - {renderShortcut(formatMessage(shortcuts.browserNewline))} -
-
-
-
-
-
{formatMessage(shortcuts.info)}
-
-
-
- ); - } -} - -function renderShortcut(text) { - if (!text) { - return null; - } - - const shortcut = text.split('\t'); - const description = {shortcut[0]}; - - let keys = null; - if (shortcut.length > 1) { - keys = shortcut[1].split('|').map((key) => ( - - {key} - - )); - } - - return ( -
- {description} - {keys} -
- ); -} - -export default injectIntl(ShortcutsModal); diff --git a/components/shortcuts_modal.test.jsx b/components/shortcuts_modal.test.jsx deleted file mode 100644 index 954c58387754..000000000000 --- a/components/shortcuts_modal.test.jsx +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; - -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; -import ShortcutsModal from 'components/shortcuts_modal.jsx'; - -describe('components/ShortcutsModal', () => { - test('should match snapshot modal for Mac', () => { - const wrapper = mountWithIntl( - , - ); - - expect(wrapper).toMatchSnapshot(); - }); - - test('should match snapshot modal for non-Mac like Windows/Linux', () => { - const wrapper = mountWithIntl( - , - ); - - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/components/sidebar/channel_navigator/channel_navigator.tsx b/components/sidebar/channel_navigator/channel_navigator.tsx index 66fef9f9ae00..83f1c6a717e2 100644 --- a/components/sidebar/channel_navigator/channel_navigator.tsx +++ b/components/sidebar/channel_navigator/channel_navigator.tsx @@ -4,6 +4,7 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; import classNames from 'classnames'; +import {Tooltip} from 'react-bootstrap'; import {trackEvent} from 'actions/telemetry_actions'; import Constants, {ModalIdentifiers} from 'utils/constants'; @@ -13,6 +14,11 @@ import {isDesktopApp} from 'utils/user_agent'; import AddChannelDropdown from '../add_channel_dropdown'; import ChannelFilter from '../channel_filter'; import {AddChannelButtonTreatments} from 'mattermost-redux/constants/config'; +import KeyboardShortcutSequence, { + KEYBOARD_SHORTCUTS, + KeyboardShortcutDescriptor, +} from 'components/keyboard_shortcuts/keyboard_shortcuts_sequence'; +import OverlayTrigger from 'components/overlay_trigger'; export type Props = { addChannelButton?: AddChannelButtonTreatments; @@ -146,27 +152,52 @@ export default class ChannelNavigator extends React.PureComponent { /> ); } + const getTooltip = (shortcut: KeyboardShortcutDescriptor) => ( + + + + ); let layout; if (isDesktopApp() && !this.props.globalHeaderEnabled) { const historyArrows = ( <> - - + + - - + + ); diff --git a/components/sidebar/sidebar_category/__snapshots__/sidebar_category.test.tsx.snap b/components/sidebar/sidebar_category/__snapshots__/sidebar_category.test.tsx.snap index 24e9f32ba8d9..8c04cfa03f5e 100644 --- a/components/sidebar/sidebar_category/__snapshots__/sidebar_category.test.tsx.snap +++ b/components/sidebar/sidebar_category/__snapshots__/sidebar_category.test.tsx.snap @@ -316,6 +316,22 @@ exports[`components/sidebar/sidebar_category should match snapshot when sorting placement="right" > Create new direct message + } placement="top" @@ -426,6 +442,22 @@ exports[`components/sidebar/sidebar_category should match snapshot when the cate placement="right" > Create new direct message + } placement="top" diff --git a/components/sidebar/sidebar_category/sidebar_category.tsx b/components/sidebar/sidebar_category/sidebar_category.tsx index adde4fbc69c3..9f7fbe519c0f 100644 --- a/components/sidebar/sidebar_category/sidebar_category.tsx +++ b/components/sidebar/sidebar_category/sidebar_category.tsx @@ -10,23 +10,20 @@ import classNames from 'classnames'; import {CategoryTypes} from 'mattermost-redux/constants/channel_categories'; import {ChannelCategory, CategorySorting} from 'mattermost-redux/types/channel_categories'; import {localizeMessage} from 'mattermost-redux/utils/i18n_utils'; - import {trackEvent} from 'actions/telemetry_actions'; - import OverlayTrigger from 'components/overlay_trigger'; - import {DraggingState} from 'types/store'; - import Constants, {A11yCustomEventTypes, DraggingStateTypes, DraggingStates} from 'utils/constants'; import {t} from 'utils/i18n'; import {isKeyPressed} from 'utils/utils'; - import SidebarChannel from '../sidebar_channel'; import {SidebarCategoryHeader} from '../sidebar_category_header'; import InviteMembersButton from '../invite_members_button'; +import KeyboardShortcutSequence, { + KEYBOARD_SHORTCUTS, +} from 'components/keyboard_shortcuts/keyboard_shortcuts_sequence'; import SidebarCategorySortingMenu from './sidebar_category_sorting_menu'; - import SidebarCategoryMenu from './sidebar_category_menu'; type Props = { @@ -283,6 +280,11 @@ export default class SidebarCategory extends React.PureComponent { className='hidden-xs' > {addHelpLabel} + ); diff --git a/components/team_sidebar/components/team_button.tsx b/components/team_sidebar/components/team_button.tsx index 8c2a5e7c402c..443bbfd2ab7b 100644 --- a/components/team_sidebar/components/team_button.tsx +++ b/components/team_sidebar/components/team_button.tsx @@ -11,10 +11,13 @@ import classNames from 'classnames'; import {mark, trackEvent} from 'actions/telemetry_actions.jsx'; import Constants from 'utils/constants'; import {isDesktopApp} from 'utils/user_agent'; -import {isMac, localizeMessage} from 'utils/utils.jsx'; +import {localizeMessage} from 'utils/utils.jsx'; import CopyUrlContextMenu from 'components/copy_url_context_menu'; import OverlayTrigger from 'components/overlay_trigger'; import TeamIcon from '../../widgets/team_icon/team_icon'; +import KeyboardShortcutSequence, { + KEYBOARD_SHORTCUTS, +} from 'components/keyboard_shortcuts/keyboard_shortcuts_sequence'; interface Props { btnClass?: string; @@ -51,7 +54,7 @@ class TeamButton extends React.PureComponent { } render() { - const {teamIconUrl, displayName, btnClass, mentions, unread, isDraggable = false, teamIndex, teamId} = this.props; + const {teamIconUrl, displayName, btnClass, mentions, unread, isDraggable = false, teamIndex, teamId, order} = this.props; const {formatMessage} = this.props.intl; let teamClass: string = this.props.active ? 'active' : ''; @@ -119,36 +122,22 @@ class TeamButton extends React.PureComponent { let toolTip = this.props.tip || localizeMessage('team.button.name_undefined', 'This team does not have a name'); let orderIndicator: JSX.Element | undefined; if (typeof this.props.order !== 'undefined' && this.props.order < 10) { - let toolTipHelp; - if (isMac()) { - toolTipHelp = formatMessage({ - id: 'team.button.tooltip.mac', - defaultMessage: '⌘ ⌥ {order}', - }, - { - order: this.props.order, - }); - } else { - toolTipHelp = formatMessage({ - id: 'team.button.tooltip', - defaultMessage: 'Ctrl+Alt+{order}', - }, - { - order: this.props.order, - }); - } - toolTip = ( <> {toolTip} -
{toolTipHelp}
+ ); if (this.props.showOrder) { orderIndicator = (
- {this.props.order} + {order}
); } diff --git a/i18n/en.json b/i18n/en.json index 70633105c13b..ae6a96d34da5 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -4298,8 +4298,8 @@ "team.button.ariaLabel": "{teamName} team", "team.button.mentions.ariaLabel": "{teamName} team, {mentionCount} mentions", "team.button.name_undefined": "This team does not have a name", - "team.button.tooltip": "Ctrl+Alt+{order}", - "team.button.tooltip.mac": "⌘ ⌥ {order}", + "team.button.tooltip": "Ctrl|Alt|{order}", + "team.button.tooltip.mac": "⌘|⌥|{order}", "team.button.unread.ariaLabel": "{teamName} team unread", "terms_of_service.agreeButton": "I Agree", "terms_of_service.api_error": "Unable to complete the request. If this issue persists, contact your System Administrator.", diff --git a/sass/components/_tooltip.scss b/sass/components/_tooltip.scss index 5e15e392e468..9da25f16ce94 100644 --- a/sass/components/_tooltip.scss +++ b/sass/components/_tooltip.scss @@ -2,15 +2,16 @@ .tooltip { max-width: 220px; + font-family: inherit; pointer-events: none; word-wrap: break-word; .tooltip-inner { max-width: 100%; - padding: 5px 10px 6px; + padding: 4px 8px; box-shadow: 0 6px 14px rgba(0, 0, 0, 0.12); - font-size: 13px; - font-weight: 500; + font-size: 12px; + font-weight: 600; line-height: 18px; text-align: center; word-break: break-word; diff --git a/sass/routes/_module.scss b/sass/routes/_module.scss index b7ecc08e78c5..c0a5b19bca0e 100644 --- a/sass/routes/_module.scss +++ b/sass/routes/_module.scss @@ -11,6 +11,5 @@ @import 'loading'; @import 'print'; @import 'settings'; -@import 'shortcuts-modal'; @import 'signup'; @import 'statistics';